Skip to content

Availability and Performance Strategy

Status: tooling and operational strategy for ninaku-app-api.

This document defines how Ninaku should approach high availability, low latency and performance validation without turning the platform into unnecessary infrastructure complexity.

Add infrastructure because a measured risk or workload requires it, not because the tool exists.

Ninaku should scale progressively: simple and reliable now, with clear paths to higher traffic and stricter uptime requirements later.

See also NESTJS_STACK_GUIDE.md for the curated NestJS dependency strategy.


1. Reliability and performance are different problems

High availability asks whether Ninaku continues to operate when a process, deployment, dependency, database node or region fails.

Performance asks where time/resources are consumed under real workload and how latency/throughput change with concurrency.

Do not use caching as a substitute for database availability, or replicas as a substitute for query optimization.


LayerTool / capabilityPurposeAdoption
EdgeCloudflareDNS, TLS, CDN, WAF, coarse rate limitingEarly
API runtimeRailway replicasMultiple stateless NestJS instancesProduction baseline
Regional APIRailway multi-region replicasGeographic latency/resilienceWhen traffic/SLOs justify it
HTTP adapterNestJS + ExpressStable default HTTP platformKeep
DatabasePostgreSQL 18System of recordAlready selected
DB HAPostgreSQL HA / managed failoverDatabase availabilityProduction HA tier
DB recoverybackups + PITR + logical dumpsDisaster/data recoveryProduction baseline
DB poolingapplication pool, PgBouncer when neededControl connection pressureProgressive
DB profilingpg_stat_statementsFind expensive/frequent SQLEarly staging/production
Query analysisEXPLAIN (ANALYZE, BUFFERS)Validate plans/indexesDevelopment/performance workflow
Cache / ephemeral stateRedis/ValkeyCache/coordination when justifiedOptional
Background jobsBullMQ + RedisDurable retryable asynchronous workWhen required
TelemetryOpenTelemetry-compatible strategyTraces/metrics correlationFoundation target
Error monitoringSentry/Nest Observe/equivalentErrors/releasesEvaluate early
Load testingGrafana k6Smoke/load/stress/soak testsEarly
Availability monitoringExternal uptime/synthetic monitoringDetect runtime failuresProduction baseline

No row in this table is permission to add infrastructure without a concrete use case, test and operating plan.


3. Edge: Cloudflare

Recommended responsibilities:

  • managed DNS and TLS;
  • WAF/security rules;
  • edge rate limiting for abuse-prone/public endpoints;
  • CDN/cache for static or explicitly cache-safe content;
  • optional health-aware multi-origin routing only when Ninaku actually has independent origins/regions.

Do not blindly cache authenticated tenant API responses.

Orders, permissions, balances, stock and payment state default to non-cacheable unless an explicit design proves otherwise.

Railway already provides routing/load distribution for its replicas, so do not operate/pay for another load-balancing layer without a reason.


4. API runtime: stateless replicas

Any request must be able to reach any healthy API replica.

Therefore:

  • no in-memory session as source of truth;
  • no in-memory idempotency guarantee;
  • no process-local lock for cross-instance invariants;
  • no local filesystem dependency for persistent business data;
  • no assumption that a later request reaches the same process.

A sensible first HA topology is multiple replicas in one primary region before introducing multi-region complexity.

text
Cloudflare
    |
Railway routing
    |
+-----------+-----------+
|                       |
API replica A        API replica B
|                       |
+-----------+-----------+
            |
       PostgreSQL

Multi-region is introduced only after measuring customer geography and DB/network round trips.


5. NestJS HTTP performance: keep Express, optimize the real bottleneck

Decision: keep @nestjs/platform-express.

Ninaku does not currently have evidence that the HTTP adapter is a meaningful bottleneck. The project gains more from:

  • efficient PostgreSQL queries and indexes;
  • proper DB pooling;
  • avoiding N+1 queries;
  • sensible response payloads/pagination;
  • stateless horizontal scaling;
  • backgrounding slow external work;
  • caching only when a measured read path benefits;
  • avoiding CPU-heavy synchronous work on the event loop.

Preserve adapter portability by keeping Express-specific types/behavior at the outer Presentation/Foundation edge and avoiding direct @Res() usage unless necessary.

If a future production profile shows HTTP framework overhead is material, benchmark an adapter change using representative Ninaku business flows. Do not optimize the adapter from synthetic GET /health numbers.


6. PostgreSQL is the most important performance layer

For Ninaku, database performance and correctness matter more than controller micro-optimizations.

pg_stat_statements

Use it to identify:

  • high total execution-time queries;
  • high-frequency queries;
  • unexpectedly slow mean/max execution;
  • query patterns that worsen as traffic grows.

EXPLAIN (ANALYZE, BUFFERS)

Use it when validating critical query paths and indexes.

Critical flows should eventually include:

  • session/context resolution;
  • organization/outlet authorization;
  • catalog/POS workspace loading;
  • sale confirmation;
  • stock reservation/movement;
  • payment lookup/posting;
  • reconciliation/reporting paths.

Do not create indexes from intuition alone. Validate the read gain and write/storage cost.

Avoid N+1

Make query count observable in repository/read-model tests and staging traces.

Operational screen APIs should eventually define budgets for:

  • SQL query count;
  • response size;
  • p95/p99 latency;
  • rows scanned/returned where relevant.

7. Connection pools and PgBouncer

Horizontal API scaling multiplies potential PostgreSQL connections.

text
5 API replicas x pool of 20 = up to 100 DB connections

Start with an explicitly sized pg application pool. Introduce PgBouncer when measured connection pressure or replica count warrants it.

Before transaction pooling, verify assumptions around:

  • session settings;
  • prepared statements;
  • temporary tables;
  • advisory locks;
  • tenant/RLS context propagation;
  • transaction-scoped SET LOCAL behavior.

Ninaku must never break tenant/RLS context merely to add a pooler.


8. PostgreSQL HA and recovery

Availability requires both failover and recoverability.

They are not the same.

Failover

For a production tier requiring DB HA, use a managed PostgreSQL HA topology with automatic leader failover.

Recovery

Production should have multiple recovery layers:

  1. scheduled platform/volume backups;
  2. point-in-time recovery where supported;
  3. portable logical dumps stored separately;
  4. documented and periodically tested restore procedure.

A backup that has never been restored is not a proven recovery plan.

Migrations must consider compatibility with the API version still serving traffic during rolling/staged deployment.


9. Redis/Valkey: optional, never source of truth

Good potential uses:

  • cache of expensive reconstructable reads;
  • distributed throttling state;
  • BullMQ backing store;
  • selected ephemeral presence/realtime state;
  • carefully designed coordination.

Bad uses:

  • authoritative stock;
  • authoritative payment state;
  • authoritative sales state;
  • replacing PostgreSQL transactional invariants;
  • hiding a broken SQL/query model.

A cache needs a clear invalidation/expiration contract. If nobody can explain when data becomes stale, do not cache it.

If Redis becomes critical for queues/runtime behavior, its availability must also be designed; do not create a new single point of failure.


10. Background work: BullMQ when asynchronous work is real

Candidate workflows:

  • fiscal/SRI delivery/reconciliation;
  • document rendering;
  • email/WhatsApp/notification delivery;
  • integration/webhook delivery;
  • OTA synchronization;
  • analytics/projection refresh;
  • other retryable external-provider work.

Use the official Nest integration (@nestjs/bullmq) when adopted.

Business correctness must not rely on "the queue probably runs once".

Preferred pattern:

text
PostgreSQL business transaction
    ├─ domain writes
    └─ outbox write
          ↓ commit
outbox dispatcher

BullMQ

idempotent worker

external provider

Workers require stable identifiers, bounded retries/backoff, observability and reconciliation/dead-letter strategy where appropriate.


11. Observability

High availability without observability means customers discover failures first.

Track at minimum:

Application

  • request count;
  • error rate;
  • p50/p95/p99 latency;
  • route/use-case latency;
  • event-loop pressure where useful;
  • external dependency latency/errors;
  • queue lag/failures.

PostgreSQL

  • connection/pool usage;
  • wait/saturation;
  • query latency/top queries;
  • locks/deadlocks;
  • transaction duration;
  • replication/failover health where relevant;
  • storage growth.

Business signals

Correlate technical metrics with important flows:

  • failed sale confirmations;
  • failed payment attempts;
  • offline replay conflicts;
  • failed fiscal issuance;
  • undelivered integration events.

Evaluate one primary tracing/APM strategy instead of stacking multiple overlapping agents. The NestJS stack guide describes OpenTelemetry-compatible telemetry vs the official @nestjs/observe option.


12. Health endpoints

Use separate probes:

text
/health/live
/health/ready

live answers whether the process is alive enough that restarting may help.

ready answers whether the instance should receive production traffic.

Readiness may include a lightweight PostgreSQL check and mandatory initialization state. Do not call every external provider on every probe.

Use @nestjs/terminus, shutdown hooks and graceful connection draining.

Deployment health checks are not a replacement for continuous external monitoring.


13. Load and resilience testing

Use k6 as the default load-testing candidate.

Evolve tests through:

  1. smoke;
  2. expected load;
  3. peak load;
  4. stress/breakpoint;
  5. soak/endurance for critical flows.

Representative scenarios should test business behavior rather than only /health:

  • login/session context;
  • POS workspace load;
  • catalog search;
  • concurrent sale confirmation;
  • concurrent stock reservation;
  • offline command replay;
  • provider latency/timeouts.

Define thresholds based on product expectations and measured baselines:

  • error rate;
  • p95/p99 latency;
  • throughput/concurrency;
  • DB pool saturation;
  • queue lag.

14. Failure testing

Availability claims must be exercised.

Useful drills:

  • terminate one API replica under load;
  • deploy a version that fails readiness;
  • saturate/restrict a DB pool in staging;
  • simulate slow SQL;
  • interrupt optional cache infrastructure;
  • retry the same idempotent command concurrently;
  • kill a worker mid-job;
  • simulate provider timeout/429/500;
  • restore a database backup;
  • exercise DB HA failover when that tier exists.

Document expected behavior before the drill.


15. Adoption order

Phase A — foundation

Prepare/adopt:

  • Express-based stateless NestJS API;
  • /health/live and /health/ready;
  • graceful shutdown;
  • explicit PostgreSQL pool limits;
  • structured logs with request/trace IDs;
  • one observability strategy POC;
  • pg_stat_statements where hosting permits;
  • k6 baseline tests for first real API flows;
  • Cloudflare proxy/WAF/rate-limit plan;
  • backup/PITR strategy for staging/production.

Phase B — serious production workload

Add based on measured need:

  • at least two API replicas in the primary region;
  • PgBouncer when connection pressure warrants it;
  • external uptime/synthetic monitoring;
  • production error/APM monitoring;
  • tested restore drills;
  • Redis only for a defined cache/queue use case.

Phase C — stricter availability / larger scale

Consider:

  • PostgreSQL automatic failover;
  • Redis HA if Redis is critical;
  • multi-region API replicas;
  • external health-aware multi-origin routing when independent origins exist;
  • dedicated metrics/log/trace backend;
  • distributed k6 tests;
  • read replicas/specialized projections only after load/query evidence.

16. Anti-patterns

Avoid:

  • Kubernetes before Railway limitations create a concrete need;
  • microservices to "improve performance" without profiling;
  • Redis for every read;
  • caches with unclear invalidation;
  • unlimited pools per API replica;
  • assuming replicas fix slow SQL;
  • multi-region API against a distant single DB without measuring round trips;
  • synchronous external calls inside long PostgreSQL transactions;
  • blind retries of non-idempotent writes;
  • treating backup as HA or HA as backup;
  • optimizing the HTTP adapter before measuring real workflows;
  • claiming endpoints are fast from tiny local datasets.

17. Decision rule

Before adding a performance/availability tool, document:

  1. Which measured problem/failure mode does it solve?
  2. What new failure mode does the tool introduce?
  3. Is it on the critical path?
  4. How is it monitored?
  5. How is failure tested?
  6. What is the rollback/removal plan?
  7. Does it preserve tenant isolation, transactions and idempotency?
  8. What objective metric proves the change helped?

If those answers are unclear, the dependency is premature.

Application Foundation in progress. Tracked in issue #13.