Novedades en 4.2¶
Versión menor. Sin cambios incompatibles vs 4.1 — toda novedad es opt-in.
Contenido¶
- PG advisory locks
- EXPLAIN automático para slow queries
- SQL real en
dorm sqlmigrate - Stats agregadas por template
- Correlación logs ↔ trazas OTel
- ALTER COLUMN TYPE con lock acotado
- Rollback dry-run
- Rotación claves EncryptedField
- PII auto-mask en streaming
- Tablas append-only
- Saturación pool
- Circuit breaker replicas
- Grafo migraciones
dorm resetcached()sugarDEBUG_NPLUSONEauto- DataLoader async
- Plan drift
- LISTEN/NOTIFY broadcaster
F.apply()chainableQuerySet.lookup()Manager.union_with()- SQL allow-list
dorm versiondorm doctorauditorías v4.2- querystats en
metrics_response - DataLoader
prime()/clear_all() - Plan-drift async
- Helpers capture-mode de allow-list
1. PG advisory locks¶
from dorm.contrib.advisory import (
advisory_lock,
try_advisory_lock,
advisory_xact_lock,
)
# Bloquea hasta adquirir; libera al salir del bloque.
with advisory_lock("nightly-report"):
run_nightly_report()
# Variante no bloqueante:
with try_advisory_lock("nightly-report") as got:
if got:
run_nightly_report()
# Alcance de transacción — se libera en commit/rollback.
with dorm.transaction.atomic():
with advisory_xact_lock(("orders", 42)):
...
Claves: int / str (hash blake2b-8 determinista) / tupla
(int, int). Async: aadvisory_lock / atry_advisory_lock.
2. EXPLAIN automático para slow queries¶
Toda query > 200 ms se re-explica automáticamente. El plan se loguea
en dorm.db.slow_explain (WARNING) y se adjunta como evento al
span OTel activo. Solo SELECT / WITH; mutaciones omitidas.
3. SQL real en dorm sqlmigrate¶
dorm sqlmigrate <app> <migration> ahora captura el DDL real vía la
conexión dry-run (antes solo volcaba describe()).
4. Stats agregadas por template (Prometheus + JSON)¶
from dorm.contrib import querystats
querystats.collector().enable()
return Response(querystats.render_text(), media_type="text/plain")
# o JSON:
return querystats.render_json()
Expone dorm_template_count / dorm_template_total_ms /
dorm_template_{p50,p95,p99}_ms por template. Reservoir acotado
(1000 muestras por defecto).
5. Correlación logs ↔ trazas OTel¶
import logging
from dorm.contrib.otel import install_log_correlation
install_log_correlation()
logging.basicConfig(
format="%(asctime)s [trace=%(otel_trace_id)s span=%(otel_span_id)s] %(message)s"
)
Une logs y traces. No-op cuando OTel SDK no está instalado
(placeholders "-" mantienen el formatter vivo).
6. ALTER COLUMN TYPE con lock acotado¶
from dorm.migrations.operations import AlterColumnTypeOnline
operations = [
AlterColumnTypeOnline(
"User", "age", "BIGINT",
lock_timeout="5s",
old_type="INTEGER",
),
]
PG-only. Envuelve el ALTER en transacción corta con
SET LOCAL lock_timeout — aborta bajo contención en lugar de
bloquear writers.
7. Rollback de migraciones dry-run¶
Imprime el SQL exacto del rollback sin ejecutarlo.
MigrationExecutor.rollback(..., dry_run=True) devuelve tuplas
(sql, params).
8. Rotación de claves de EncryptedField¶
from dorm.contrib.encrypted import rotate_encryption_keys
rotate_encryption_keys(User, batch_size=1000)
# Async:
await arotate_encryption_keys(User)
Re-encripta cada fila con la clave nueva en bloques dentro de
atomic(). Callback progress opcional para hooks tipo tqdm.
9. PII auto-mask en streaming¶
from dorm.contrib.streaming import stream_jsonl, astream_jsonl
for chunk in stream_jsonl(qs, mask_pii=True):
yield chunk
async for chunk in astream_jsonl(aqs, mask_pii=True):
yield chunk
Campos pii=True se reemplazan con el sentinel antes de
serializar.
10. Tablas append-only para auditoría¶
from dorm.migrations.operations import MakeTableAppendOnly
operations = [
MakeTableAppendOnly("article_history"),
MakeTableAppendOnly("audit_log", allow_delete=True),
]
Trigger DB-level que rechaza UPDATE / DELETE. PG (PL/pgSQL) y
SQLite (RAISE(ABORT)). MySQL / DuckDB loguean warning y omiten.
11. Gauge + warning de saturación de pool¶
Métrica dorm_pool_saturation{alias} (= in_use / max_size).
WARNING en dorm.contrib.prometheus.pool al superar umbral.
12. Circuit breaker para read replicas¶
LagAwareReadRouter(
primary="primary",
replicas=["r1", "r2"],
failure_threshold=3,
cooldown_seconds=30.0,
)
Tras N fallos consecutivos abre breaker por replica y omite incluso el probe durante cooldown.
13. Grafo de dependencias de migraciones¶
dorm migrations-graph --format=mermaid > graph.mmd
dorm migrations-graph --format=dot | dot -Tpng -o graph.png
Camina migraciones por AST sin importarlas — deps runtime ausentes no rompen la visualización.
14. dorm reset (solo dev)¶
Rollback a zero + re-aplica forward. Refuse a producción salvo
--force.
15. Model.objects.cached(timeout=...)¶
Sugar de .all().cache(timeout=60).filter(...).
16. DEBUG_NPLUSONE auto-detector¶
dorm.configure(
DEBUG=True,
DEBUG_NPLUSONE="raise",
DEBUG_NPLUSONE_THRESHOLD=10,
)
from dorm.contrib.nplusone import install_debug_global
install_debug_global()
Instala detector N+1 global. "raise" aborta la request;
True / "log" loguea sin interrumpir.
17. Async DataLoader¶
from dorm.contrib.dataloader import DataLoader
loader = DataLoader(lambda pks: Author.objects.filter(pk__in=pks))
results = await asyncio.gather(*(loader.load(pk) for pk in pks))
# N loads concurrentes coalescen en UN fetch
Patrón DataLoader de Facebook — GraphQL resolvers, RPC fan-out.
18. Detección de plan drift¶
from dorm.contrib.plan_drift import record_baseline, compare, diff_text
record_baseline("orders.by_customer", sql, params=[1])
result = compare("orders.by_customer", sql, params=[1])
if result.drifted:
alert.fire("plan drift:\n" + diff_text(result))
Costes, row-estimates y timings se eliminan antes de comparar — solo cambios estructurales (tipos de nodo, estrategias de scan) lo disparan.
19. LISTEN/NOTIFY broadcaster¶
from dorm.contrib.listen_notify import Broadcaster
async with Broadcaster(["orders"]) as bcast:
async with bcast.subscribe("orders") as queue:
async for n in queue:
...
Una conexión LISTEN sirve N suscriptores async con su queue acotada.
20. F.apply() chainable¶
from dorm.expressions import F
from dorm.functions import Lower, Trim, Substr
F("name").apply(Lower).apply(Trim).apply(Substr, 1, 10)
Lee top-down en vez de inside-out. Disponible en F y todas las
subclases de Func.
21. QuerySet.lookup(column=None)¶
top10 = Article.objects.order_by("-score")[:10]
Comment.objects.filter(article_id__in=top10.lookup("pk"))
Sugar para Subquery(qs.values(column)).
22. Manager.union_with() polimórfico¶
Source.objects.union_with(Other.objects, all=True, order_by=["created_at"])
Source.objects.union_with(
(Other.objects, {"label": F("title")}),
all=True,
)
UNION entre modelos heterogéneos con mapping por rama.
23. SQL allow-list¶
from dorm.contrib.sql_allowlist import install, uninstall, rejected_templates
# Fase canary:
install(allow_list, raise_on_violation=False, allow_ddl=True)
print(rejected_templates())
# Fase enforcement:
install(curated_list, raise_on_violation=True, allow_ddl=False)
Rechaza queries fuera de la lista curada. Defensa en profundidad junto a validación de input.
24. dorm version¶
Línea trivial — para tickets de soporte y logs CI.
25. dorm doctor auditorías v4.2¶
dorm doctor ahora detecta misconfigs específicos de v4.2:
DEBUG_NPLUSONEactivo fuera de modoDEBUG.SLOW_QUERY_EXPLAIN=TrueconSLOW_QUERY_MSdemasiado bajo.POOL_SATURATION_WARNfuera del rango(0, 1).LagAwareReadRouterconcooldown_seconds=0(breaker desactivado).sql_allowlistsin instalar, o conallow_ddl=Trueen producción.
Ejecuta antes de cada deploy y bloquea según exit code.
26. querystats en metrics_response¶
El snapshot por-template de dorm.contrib.querystats ahora se
integra automáticamente en prometheus.metrics_response() cuando
el collector está habilitado:
from dorm.contrib import prometheus, querystats
querystats.collector().enable()
return Response(prometheus.metrics_response(), media_type="text/plain")
27. DataLoader prime() / clear_all()¶
prime tras fan-out write evita SELECT redundante en resolver
siguiente.
28. Plan-drift async¶
await plan_drift.arecord_baseline("orders.by_customer", sql, params=[1])
result = await plan_drift.acompare("orders.by_customer", sql, params=[1])
Simétrico al sync. Mismas baselines, mismo CompareResult.
29. Helpers capture-mode de allow-list¶
from dorm.contrib import sql_allowlist
# Canary:
sql_allowlist.install([], raise_on_violation=False, allow_ddl=True)
# ... tráfico real ...
sql_allowlist.dump_captured("allowlist.json")
# Enforcement (tras curar):
sql_allowlist.uninstall()
sql_allowlist.load_from_file(
"allowlist.json",
raise_on_violation=True,
allow_ddl=False,
)
dump_captured escribe JSON con allowed + rejected;
load_from_file reinstala desde el payload curado.
allowed_templates() devuelve snapshot actual.
Cambios completos en el CHANGELOG del proyecto. Subir 4.1 → 4.2 no necesita modificar código.