Skip to content

Application performance

Measure a representative application path before changing it. Record the data volume, query count, latency distribution, concurrency, database role, and runtime versions. A local smoke check is not a throughput guarantee.

Bound database work

Use explicit QuerySet limits and deterministic ordering. The querying guide demonstrates these operations against the tutorial Ticket model. Start pagination with a QuerySet, not a manager-level limit() call or a slice of the async .all() method.

For generated APIs, use the actual ViewSet limits and supported filter backends. page_size is not a general ModelViewSet switch. Avoid unbounded reads merely to compute a count or check existence: use the terminal .count() or .exists() query methods.

The ordinary Manager/QuerySet does not offer only() or defer(). Shaping a small result into a dictionary in Python does not reduce columns fetched by SQL. Do not describe that transformation as database projection.

Forward foreign-key attributes hold identifiers. Accessing or awaiting post.author does not lazily fetch an author object. Use an explicit query or select_related() followed by get_related() as described in relations.

Use only supported prefetch paths and accessors. Do not assume arbitrary nested prefetch traversal or collection iteration matches another ORM. Measure actual queries on the relationship shape you use; an illustrative query count is not a guarantee for every combination of relations and filters.

Index the workload

Declare supported indexes through model metadata and apply them through migrations. An index that helps one read pattern can add write and storage cost. Inspect the deployed PostgreSQL schema and query plan before concluding an index is present or used.

There is no documented QuerySet .explain() shortcut here. Use PostgreSQL planning tools through your database tooling for the actual SQL and bound values. EXPLAIN ANALYZE executes its statement; use an appropriate test environment for statements with effects. Do not include credentials or sensitive values in profiling artifacts.

Keep serialization predictable

Use the supported serializer hooks and relation expansion contract. DRF-style SerializerMethodField and declarative serializers.String(source=...) recipes are not Aksara APIs. Avoid hidden per-row database operations in application transformation code, and measure both database work and output size.

Concurrency and transactions

Async functions allow other work to proceed while awaiting I/O. They do not make one database connection safe for overlapping queries. Inside transaction.atomic(), run participating database operations sequentially on the pinned connection; do not use asyncio.gather() to share it across child tasks.

Independent pool connections can execute independent work, but do not join the same atomic transaction. Keep transaction duration short and avoid waiting on remote services while holding database locks. See transaction limits.

Caching and background work

Aksara has no public general-purpose aksara.cache API. Application caches need explicit tenant keys, invalidation, lifetime, and authorization rules; see caching. Cache hits must not substitute stale access decisions for current policy.

Use ordinary tasks when their execution semantics fit. Use Durable Operations when recovery and authorization across time are required. Moving work to a worker does not reduce its resource cost or prove a performance improvement; measure queue delay and completion latency as well as request latency.

Validate the change

Compare the same workload before and after, including error rates, pool waits, and database resource use. Preserve authorization, tenant isolation, validation, and transaction guarantees in the test. Do not remove those checks to produce a faster benchmark.

The production guide and diagnostics cover operational checks. Doctor readiness is not a load-test result or a capacity estimate.