Technical Architecture
Technical architecture of the ID Generator service — async stack, database design, concurrency patterns, and Kubernetes deployment.
Project structure
id-generator/
├── config/
│ └── default.yaml # Default configuration
├── src/
│ └── id_generator/
│ ├── main.py # FastAPI app entry point
│ ├── config.py # Pydantic Settings (YAML + env vars)
│ ├── db.py # Async SQLAlchemy engine
│ ├── models.py # Table creation (table-per-ID-type)
│ ├── api/
│ │ ├── router.py # API endpoints
│ │ └── schema.py # Response envelope
│ ├── generator/
│ │ ├── engine.py # ID generation pipeline
│ │ ├── filters.py # 10 filter implementations
│ │ └── verhoeff.py # Verhoeff checksum
│ └── pool/
│ ├── manager.py # Background replenishment
│ └── issuer.py # Issue & mark TAKEN
├── tests/ # Integration test suite
├── charts/ # Helm chart
├── Dockerfile
└── pyproject.tomlDesign principle: The generator package (pure functions, no DB dependency) is separated from the pool package (DB-dependent). Filters and Verhoeff are independently testable.
Database design
Table-per-ID-type strategy
Each ID type gets its own table, auto-created on startup:
Why table-per-ID-type
Query performance
All types share one B-tree index
Each table has its own smaller index
Bulk insert
Inserting into 200M+ row table
Inserting into 50M row table — lighter
VACUUM/maintenance
One large table to vacuum
Smaller tables vacuum faster
Dropping a type
Slow DELETE + WAL bloat
Instant DROP TABLE
Issuing IDs — concurrency-safe path
SQL pattern
Both statements execute in a single transaction.
Why FOR UPDATE SKIP LOCKED
With plain
FOR UPDATE: if Pod A locks a row, Pod B waits (blocks).With
FOR UPDATE SKIP LOCKED: Pod B skips the locked row and grabs the next one.Result: zero contention between pods.
Deadlock retry
In rare cases under heavy load, deadlocks can occur between issuing and replenishment. The issuer includes retry logic: up to 3 retries with 100ms delay.
Pool replenishment
Background task
Every pod runs a periodic background task (default: every 30 seconds):
Why PostgreSQL advisory locks
No leader election needed — any pod can generate, but only one at a time per ID type.
pg_try_advisory_lockis non-blocking — if another pod holds the lock, skip instantly.Database is the coordinator — no Redis, ZooKeeper, or K8s leader election needed.
Per-ID-type locks — Pod A generates for
farmerwhile Pod B generates forhouseholdsimultaneously.
Sub-batch insertion
IDs are inserted in smaller sub-batches (100 rows per transaction) rather than one large batch:
Avoids long-running transactions that hold locks.
Reduces WAL pressure.
Allows issuing operations to interleave without being blocked.
ID generation pipeline
Startup behaviour
The service blocks until all ID types have their minimum pool generated. This ensures no IDG-001 errors immediately after deployment. Initial startup may take time for large pools — the Kubernetes startup probe allows up to 5 minutes.
Kubernetes deployment
Every pod is identical — runs both the API server and the background pool manager.
No leader pod — PostgreSQL advisory locks coordinate generation naturally.
Horizontal scaling — add pods for more API throughput.
Probes
Startup
GET /v1/idgenerator/health
Waits up to 5 min for pool generation
Readiness
GET /v1/idgenerator/health
Only ready after startup is complete
Liveness
GET /v1/idgenerator/health
Detect unhealthy processes
Key architectural decisions
1
Async stack
FastAPI + asyncpg + SQLAlchemy async
I/O-bound workload, no thread pool overhead
2
Table strategy
Table-per-ID-type
Clean isolation, better maintenance, instant DROP
3
Issuing concurrency
SELECT ... FOR UPDATE SKIP LOCKED
Zero contention between pods
4
Generation coordination
PostgreSQL advisory locks
No external coordinator needed
5
Bulk insert
Sub-batches (100 rows per transaction)
Avoids long transactions, reduces WAL pressure
6
Duplicate handling
INSERT ... ON CONFLICT DO NOTHING
Silently skips, no error handling needed
7
Filter execution
In-memory, cheapest-first, before DB insert
Fail fast, no wasted DB writes
8
Startup behaviour
Block until minimum pool generated
No IDG-001 errors immediately after deployment
9
Config management
Pydantic Settings: YAML + env var overrides
Flexible for K8s (ConfigMap + env vars)
Last updated
Was this helpful?