The tuned profile¶
aiodrf's defaults behave exactly like DRF. The options on this page trade some of that behaviour for speed. Together they form the tuned profile, the configuration aiodrf's benchmarks use to measure its fastest setup:
AIODRF = {
"SERIALIZER_BACKEND": "msgspec",
"SERIALIZER_BACKEND_PARITY": "strict",
"SERIALIZER_BACKEND_FALLBACK": "error",
"CACHE_SERIALIZER_FIELDS": True,
"FIELD_COPY_MODE": "compiled",
"REPRESENTATION_MODE": "inline",
"REQUEST_THREADS": 32,
}
REST_FRAMEWORK = {
"DEFAULT_RENDERER_CLASSES": [
"aiodrf.contrib.msgspec.renderers.MsgspecJSONRenderer"
],
}
# Views return aiodrf.response.DataResponse instead of Response.
Each option is independent of the others. Enable them one at a time and run your own tests after each; the sections below describe what each option does, how it differs from DRF, where it does not apply and what can go wrong.
Where each option can be set¶
| Option | Project (AIODRF) |
View | Serializer (Meta) |
Notes |
|---|---|---|---|---|
SERIALIZER_BACKEND |
yes | — | serializer_backend |
Limited by ALLOWED_SERIALIZER_BACKENDS |
SERIALIZER_BACKEND_PARITY |
yes | — | — | Project-wide only |
SERIALIZER_BACKEND_FALLBACK |
yes | — | serializer_backend_fallback |
|
CACHE_SERIALIZER_FIELDS |
yes | serializer_field_cache |
cache_fields |
A serializer's Meta takes precedence over the view |
FIELD_COPY_MODE |
yes | serializer_field_copy_mode |
field_copy_mode |
Requires the field cache |
REPRESENTATION_MODE |
yes | — | — | Project-wide only |
MsgspecJSONRenderer |
DEFAULT_RENDERER_CLASSES (DRF) |
renderer_classes |
— | |
DataResponse |
— | returned by the handler | — | Per response |
REQUEST_THREADS |
yes | — | — | Process-wide; used only by aiodrf.asgi.get_asgi_application() |
SERIALIZER_BACKEND_PARITY and REPRESENTATION_MODE cannot be limited to a
view or a serializer. If only some endpoints should represent on the event
loop, keep the default "thread" mode: it already represents loaded objects on
the event loop when it can prove that no query is needed (see
inline representation below).
Compiled serializers¶
SERIALIZER_BACKEND, SERIALIZER_BACKEND_PARITY, SERIALIZER_BACKEND_FALLBACK
What it does. aiodrf compiles a serializer's output, and the validation of
well-formed input, to msgspec or Pydantic when the result is known to be
identical to DRF's. SERIALIZER_BACKEND = "python" compiles the same output
without either library, and leaves input to DRF. The serializer backend guide explains
the details.
Differences from DRF. With "strict" parity there are none: a serializer
whose output could differ is not compiled. "fast" parity accepts a few
documented differences, such as decimals rendered with str().
Limitations. Strict parity compiles model-backed strings, integers, floats,
booleans, UUIDs, dates, times, datetimes, decimals output as strings, choices,
file fields, primary keys and slugs of forward and to-many relations, dotted
sources through foreign keys that cannot be null, and nested serializers for
forward and to-many relations. It leaves other fields to DRF, among them JSON,
method and custom fields, and serializers that define their own
to_representation. The output section
of the serializer guide has the complete list. msgspec or Pydantic must be
installed for their backends.
Risks. With SERIALIZER_BACKEND_FALLBACK = "error", a serializer that
cannot be compiled raises ImproperlyConfigured instead of falling back to DRF.
An instance that a compiled serializer cannot read is represented by DRF either
way.
Use it only when every serializer is expected to compile, and exempt the others
with Meta.serializer_backend_fallback = "drf". Run
python manage.py aiodrf_inspect_serializers to see which serializers compile
and why the others do not.
Field cache¶
CACHE_SERIALIZER_FIELDS, FIELD_COPY_MODE
What it does. A ModelSerializer whose fields depend only on its class
builds them once; each instance receives its own copy, made with deepcopy or
with the faster "clone" and "compiled" copy plans.
Differences from DRF. None, as long as the serializer's declarations do not change at run time.
Limitations. Serializers with field-building hooks (__init__,
get_fields, build_field and similar), with Meta.depth, or whose model
fields call project code (callable choices, for example) are built per
instance as in DRF.
Risks. Changes to model fields or Meta made after a serializer class was
first used, for example by a test that patches them, are not picked up. See
selective serializer optimization.
Representation on the event loop¶
REPRESENTATION_MODE = "inline"
What it does. Serializers produce their output on the event loop instead of in a worker thread. By enabling it, you state that your serializers only read data that is already loaded and never block.
Differences from DRF. None in the output.
Limitations. The setting applies to the whole project. Without it, aiodrf
still represents on the event loop whatever it can prove needs no query: model
instances whose serializer reads only loaded columns, select_related
relations and prefetch_related lists of up to 32 objects, and compiled
encoders over loaded columns.
Risks. Anything that performs I/O during representation raises Django's
SynchronousOnlyOperation instead of running in a thread:
- a relation missing from
select_relatedorprefetch_related; - a model property or
SerializerMethodFieldthat queries the database or calls a service; - a file field whose storage performs I/O to build a URL, such as django-storages' S3 backend when it refreshes its credentials;
- the many-to-many relations of an object just created by a serializer that also has async fields. (Serializers without async fields represent a newly created object in the worker thread that saved it.)
Make sure your tests serialize every relation. A large response also occupies the event loop while it is produced, which delays other requests handled by the same process.
MsgspecJSONRenderer¶
What it does. Renders JSON with msgspec.json.
Differences from DRF's JSONRenderer. timedelta values are rendered as
ISO 8601 durations instead of seconds and bytes as base64; NaN and infinity
become null instead of raising an error; plain Decimal values keep their
digits (1.50 where DRF writes 1.5). DecimalField output is a string in
both renderers when COERCE_DECIMAL_TO_STRING is enabled. Indented output (the
indent media type parameter, the browsable API) uses DRF's renderer.
Risks. Clients that parse the forms listed above. msgspec must be installed.
DataResponse¶
What it does. A handler returns aiodrf.response.DataResponse(data, status,
headers, content_type) instead of DRF's Response. The view renders it
immediately with DRF's JSON renderer and returns a Django HttpResponse with
the same status, content and headers DRF would produce.
Differences from DRF. It is not a DRF Response: there is no render()
step, so process_template_response middleware does not see it; of DRF's
attributes it only has renderer_context; and data is None once the
response is rendered, because the payload is released before the response is
sent. In tests, read the content with response.json().
Limitations. When the negotiated renderer is not a JSON renderer (the
browsable API, templates), or when the view defines finalize_response, the
view converts it to a regular DRF Response, with no speed benefit. Returned by
a view that is not an aiodrf view, its content raises ContentNotRenderedError.
Risks. Middleware, decorators or tests that read response.data or other
Response attributes.
Request threads¶
REQUEST_THREADS
What it does. For each request, Django's ASGI handler starts a thread to run
synchronous code and waits for it to finish. With this setting,
aiodrf.asgi.get_asgi_application() keeps those threads for later requests:
each thread serves one request at a time, and at most the configured number are
kept idle.
Differences from Django. Thread-local state outlives a request, as in a
threaded WSGI server. This includes threading.local values and, when
CONN_MAX_AGE keeps connections open, one database connection per kept thread
and database.
Limitations. Applies only to the application returned by
aiodrf.asgi.get_asgi_application(), for the whole process.
Risks. Code that relies on each request running in a new thread, and the memory used by idle threads. The feature uses asgiref's executor mapping, which is not a documented asgiref API; it is tested with asgiref 3.8.1 and 3.12.1.
Adopting the profile¶
- Measure your own workload first, as described in the performance guide. Benchmark gains depend on the benchmark's scenarios.
- Start with the options whose risks you can check most easily:
MsgspecJSONRenderer(check your clients), the field cache (check that your serializers are static) andDataResponseon selected JSON endpoints. - Enable
SERIALIZER_BACKENDwith strict parity and the default"drf"fallback. Switch the fallback to"error"only afteraiodrf_inspect_serializersshows that everything you expect compiles. - Enable
REPRESENTATION_MODE = "inline"last, once your tests serialize every relation.
Third-party packages¶
With the options above and SERIALIZER_BACKEND_FALLBACK = "drf", the tested
third-party packages work unchanged, with two exceptions
caused by inline representation: S3 file URLs from django-storages, and
serializers that read relations the view does not load (see the risks of
inline representation).
With SERIALIZER_BACKEND_FALLBACK = "error", the serializers of these packages
raise ImproperlyConfigured, because their fields or serializers cannot be
compiled: django-money (MoneyField), django-phonenumber-field, django-taggit
(TagListSerializerField), django-polymorphic, drf-flex-fields,
django-restql, django-rest-framework-json-api (ResourceRelatedField),
djangorestframework-dataclasses, the image fields of drf-extra-fields,
django-pydantic-field (SchemaField), and the embedded models and arrays of
django-mongodb-backend. Its ObjectId fields compile with
aiodrf.contrib.mongodb installed. Set Meta.serializer_backend_fallback = "drf" on those
serializers, or keep the project-wide default of "drf".