Chapter 18 · Capstone: Design a Production-Ready Database
Add Transactions, Indexes, Security, and Evolution Plans
Complete the production design by adding transaction boundaries, concurrency controls, indexes, tenant/security rules, auditability, denormalized projections, and safe schema-evolution plans.
Learning outcomes
A normalized schema is necessary but not sufficient. Production design must also survive concurrent users, hot queries, tenant isolation, failures, schema evolution, and operational monitoring. This lesson turns the capstone schema into a production architecture.
Define transaction boundaries and concurrency controls.
Add indexes from measured access patterns.
Apply security, tenant isolation, audit, and data minimization.
Design denormalized projections and migration paths deliberately.
Command: OpenWorkOrder
Transaction:
- validate tenant, Asset, and current ownership/business rules;
- insert WorkOrder;
- insert initial status event;
- insert audit/outbox event;
- commit.
Command: AssignTechnician
Protect:
- WorkOrder must accept assignments;
- Technician belongs to same tenant;
- Technician is active;
- primary-assignment uniqueness;
- optional scheduling overlap rule.
Command: RecordPartUsage
BEGIN;UPDATE part_inventorySET available_qty = available_qty - :qtyWHERE tenant_id = :tenant AND part_id = :part AND available_qty >= :qty;-- verify one row changedINSERT INTO part_usage (...);INSERT INTO audit_event (...);COMMIT;This avoids a lost-update inventory race.
Command: CloseWorkOrder
Check within one protected transaction:
- current status allows closure;
- no active assignments remain;
- required fields/checklists complete;
- closed_at is set;
- status event and audit event are written.
Optimistic versioning
UPDATE work_orderSET problem_description = :new_text, version = version + 1WHERE tenant_id = :tenant AND work_order_id = :id AND version = :expected_version;Use for user edits where conflicts are relatively rare.
Pessimistic coordination
For invariant-heavy commands, lock the WorkOrder parent row or use serializable isolation if appropriate. Keep transactions short and never wait on external APIs while holding database locks.
Index portfolio
| Access pattern | Candidate index |
|---|---|
| WorkOrder by number | UNIQUE(tenant_id, work_order_number) |
| Asset history | (tenant_id, asset_id, opened_at DESC) |
| Technician schedule | (tenant_id, technician_id, started_at) |
| Active work queue | Partial/composite active-status index |
| Part lookup | UNIQUE(tenant_id, sku) |
Index restraint
PartUsage may be write-heavy. Do not add every reporting index to the OLTP primary. Keep integrity/hot-path indexes and move heavy analytics to materialized/warehouse structures when needed.
Tenant isolation
Use defense in depth:
- tenant_id on tenant-owned rows;
- tenant-aware FKs;
- tenant-scoped unique constraints;
- tenant-first indexes;
- application predicates;
- row-level security where appropriate;
- cross-tenant negative tests.
Least privilege
Separate service roles:
dispatch_serviceinventory_servicebilling_servicereporting_readonlymigration_adminDo not give runtime services DDL privileges.
Sensitive data
Keep tax identifiers, credentials, and unrelated billing secrets out of WorkOrder/technician operational views. Minimize copies into audit and analytics systems.
Auditability
Audit events should capture:
tenantactoractionentityrequest/correlation IDtimereason where neededAvoid unrestricted full-row sensitive snapshots.
Denormalized read projection
WorkOrderListProjection( tenant_id, work_order_id, work_order_number, asset_display, customer_display, status_display, opened_at, source_version)Use an outbox/event stream and define a freshness SLA, e.g. ≤5 seconds.
Dashboard summary
DailyWorkOrderSummary( tenant_id, day, status_code, work_order_count)Derived, rebuildable, and explicitly not the source of truth.
JSON strategy
For DiagnosticCapture:
- store raw/versioned payload;
- keep common fields typed;
- index only proven hot JSON paths;
- promote fields that become business-critical.
Retention plan
| Dataset | Illustrative policy |
|---|---|
| Diagnostic raw payload | Shorter operational retention |
| WorkOrder/history | Longer business retention |
| AuditEvent | Protected policy-driven retention |
| Cache/search projection | Rebuildable, delete with source lifecycle |
Evolution plan: add service_region_id
- add nullable column;
- deploy writers;
- backfill in batches;
- add/index FK;
- validate;
- enforce NOT NULL;
- remove compatibility code.
Evolution plan: rename concept
Use expand-and-contract for a customer/account rename rather than direct destructive rename during a rolling deployment.
Observability
Monitor:
- p95/p99 query latency;
- lock waits/deadlocks;
- serialization retries;
- replication lag;
- index usage and size;
- projection freshness;
- tenant-isolation policy failures.
Failure scenarios
Test:
- duplicate RecordPartUsage request;
- concurrent inventory decrement;
- two primary assignments concurrently;
- cache unavailable;
- projection lagging;
- backfill worker crash;
- old/new app versions overlapping.
Checkpoint
Reporting index request
An analyst asks for six new indexes on PartUsage to speed monthly reports. The table receives 40,000 inserts/minute. What should you do?
Review answer
Measure the reports and write impact first. Keep only indexes justified for operational integrity/hot paths on the OLTP table, and consider summaries, replicas, partitions, or a warehouse for scan-heavy monthly analytics.
Summary and next lesson
The capstone is now hardened around transactions, concurrency, indexes, tenant isolation, least privilege, auditability, projections, retention, and safe evolution. The final lesson tests and documents the design so it can be defended to engineers, stakeholders, and operators.
References
- Course Chapters 10–17.
- Martin Kleppmann, Designing Data-Intensive Applications.
- DBMS documentation on transactions, indexes, roles, and online migrations.