Scaling
Generated code gives you a standard framework service. Capacity planning still depends on request shape, database queries, tenant distribution, extension behavior, deployment resources, and reliability requirements.
Measure before changing the architecture
Collect evidence for:
- Request rate by endpoint and tenant
- Latency percentiles by endpoint
- Error and timeout rates
- Database query duration and rows scanned
- Connection pool use and wait time
- CPU and memory per application instance
- Background job depth and age
- Cache hit and eviction rates when a cache exists
Start with the slowest user workflow and the queries it produces. Adding instances will not repair an unindexed tenant query or an external provider call made in the request path.
Application instances
A generated service can run behind a load balancer when request state is externalized. Before adding replicas:
- Store shared sessions in the configured database or session store.
- Keep uploads in object storage rather than the instance filesystem.
- Keep scheduled work in a queue or scheduler with clear ownership.
- Configure graceful shutdown and readiness checks.
- Ensure each instance uses a bounded database pool.
Database capacity
The database is commonly the first shared limit.
Index tenant and lookup paths
Use .apsorc indexes for fields that appear in frequent filters, joins, or uniqueness checks. For a multi-tenant list route, the tenant field often belongs at the start of a composite index with the status or sort field used by the query.
{
"name": "Task",
"scopeBy": "workspaceId",
"fields": [
{ "name": "status", "type": "enum", "values": ["Todo", "Done"], "index": true },
{ "name": "dueDate", "type": "date", "nullable": true, "index": true }
]
}Review generated indexes and query plans against the actual filters. A single-field index is not always enough for a tenant-scoped sort.
Bound connection pools
Each application replica can open its own pool. Multiply the per-instance maximum by the maximum replica count and leave capacity for migrations, administration, and workers. Use a database pooler when many short-lived processes would otherwise consume too many connections.
Add replicas for measured read pressure
Read replicas require explicit application routing and consistency decisions. Put reporting or tolerant read workloads on a replica after verifying that replication lag is acceptable. Keep writes and read-after-write workflows on the primary.
Caching
Add a cache around repeated, expensive reads with a clear invalidation rule. Good candidates include stable reference data or expensive aggregates. Tenant-owned records require tenant-aware cache keys.
workspace:{workspaceId}:project:{projectId}Do not cache authorization decisions longer than the underlying membership or role can safely remain stale.
Background work
Move email, webhooks, exports, media processing, and other retryable work out of the HTTP response path when the product does not require synchronous completion. Implement producers and workers in extensions/ and define:
- Idempotency key
- Retry policy
- Dead-letter handling
- Tenant context
- Observability fields
- Maximum execution time
Load testing
Use a representative schema and data distribution. Include joins, tenant scope, authentication, and extension calls in the test. Increase load gradually and record the first constrained resource. Test deployment and database limits in a non-production environment with the same relevant configuration.
Do not adopt generic latency, throughput, or replica targets from documentation. Set service objectives from the product workflow and validate them in the environment that will run the service.
Scaling checklist
- Define a user-facing service objective.
- Add tracing or structured timing around the workflow.
- Inspect database query plans and indexes.
- Bound application and database concurrency.
- Add replicas only when the process is ready for shared-state execution.
- Add cache or workers for a measured workload.
- Load test the change and compare the same metrics.
- Document rollback and failure behavior.