Use Geospatial Tracking Platform as the Foundational Architecture Pattern
Deterministic ADR derived from topology, simulation, and advisor intelligence for Geospatial Tracking Platform. Traceable to YAML knowledge entities.
Context
Tracking systems generate a continuous, high-volume write stream where the business value is in the current position (proximity queries, dispatch routing, ETA calculation) and the historical record (route replay, SLA verification, compliance). These two consumers have fundamentally different requirements: the current-position consumer needs sub-10ms query latency against the latest known position of every entity in a geographic radius; the historical consumer needs efficient time-range queries over months of location data per entity. A 10,000-entity fleet updating at 1Hz generates 864 million location records per day: storing all records in PostgreSQL is not sustainable past 30 days without aggressive partitioning and archival. Geofence evaluation at 10,000 updates/second requires that the fence evaluation code path not be in the synchronous API response path. Primary operational risks include: Redis memory pressure from unbounded geospatial key growth: each tracked entity occupies a slot in a Redis ZSET (sorted set backing GEOADD). For 100,000 tracked entities, the live position surface is modest in memory. But if entities are not cleaned up when they go offline, the ZSET grows indefinitely. A fleet management system that creates new entity keys per trip rather than per vehicle will exhaust Redis memory within weeks.; TimescaleDB chunk creation storm on sudden entity fleet expansion: TimescaleDB creates hypertable chunks per time interval per partition key. A fleet that doubles overnight triggers simultaneous chunk creation across many partition keys, stalling writes for 2–10 seconds per chunk creation event. Under 10,000 writes/second, a chunk creation storm produces a visible latency spike that operations teams misdiagnose as a database overload rather than a DDL side effect.; Geofence evaluation consumer falling behind during geofence configuration reload: when the geofence configuration is reloaded (new zones added, zone boundaries changed), the evaluation consumer must re-evaluate recent position history against the new zones. This causes a temporary evaluation throughput drop that allows the Kafka consumer to fall behind. If the lag exceeds the Kafka topic retention window, geofence events from the lag period are permanently lost..
Decision
We will adopt the **Geospatial Tracking Platform** architecture pattern. This is a high-complexity architecture appropriate for teams at experienced backend team level or above. The advisor rates this pattern as 'advanced' operational maturity.
Rationale
A real-time location tracking platform for moving entities: vehicles, delivery couriers, field workers, and assets: receiving position updates at 1–30 second intervals from thousands of simultaneously tracked objects. Redis geospatial indexes (GEOADD / GEOSEARCH) serve sub-10ms proximity queries against the live position surface; PostgreSQL stores durable entity metadata and historical location records; TimescaleDB stores high-frequency time-series location data with automatic hypertable chunking and retention policies; Kafka streams location events to downstream consumers (dispatch systems, customer tracking apps, analytics pipelines). Geofence evaluation runs at ingestion time: each incoming position update is tested against active geofences for the entity's region, and geofence entry/exit events are emitted as Kafka messages to downstream consumers. Core technology stack: redis, postgresql, kafka, timescaledb.
Accepted Tradeoffs
- ⚠Redis GEOSEARCH provides sub-10ms proximity query latency for the live position surface, but Redis memory is the binding constraint for entity scale: each active entity's position costs approximately 64 bytes in the Redis ZSET, which is negligible at 10,000 entities but requires capacity planning at 1,000,000+ entities
- ⚠TimescaleDB automatic hypertable chunking by time interval enables fast time-range queries and efficient chunk-level data retention (DROP CHUNKS instead of DELETE), but the chunk boundary is a hard query boundary: range queries spanning chunk boundaries require cross-chunk scans that do not benefit from within-chunk indexing
- ⚠Write-behind cache pattern (write to Redis first, flush to TimescaleDB asynchronously) reduces write latency for the ingestion path below what direct TimescaleDB writes can sustain, but introduces a data loss window equal to the flush interval if Redis fails before the flush completes
- ⚠Geofence evaluation at ingestion time provides real-time geofence event generation but adds evaluation latency to every location update write path: a complex geofence topology (10,000+ zones) can make evaluation the dominant cost per update, not the database write
Risks
One partition (a database shard, a Kafka topic partition, a Redis hash slot) receives traffic so far above its peers that it saturates while the others sit idle. Aggregate capacity looks healthy, but the hot partition throttles or lags, and everything routed to it degrades. The cause is skew in how keys map to partitions, and the fix depends on whether the skew is spread across many keys or concentrated in one.
Each logical application write triggers multiple physical writes through index maintenance, WAL generation, MVCC versioning, and replication, causing actual disk IOPS to exceed the provisioned I/O ceiling while the logical write rate appears modest.
When total memory demand from a process or the entire host exceeds available physical RAM plus swap, the Linux OOM killer terminates one or more processes to reclaim memory, causing immediate connection loss, data corruption risk if in-flight writes are lost, and process restart overhead.
The storage device reaches its IOPS or throughput ceiling, causing all disk- dependent database operations to queue behind I/O requests, driving latency from sub-millisecond to hundreds of milliseconds and degrading all database operations simultaneously.
A single consumer instance in a Kafka consumer group processes messages significantly slower than its peers, causing partition lag to accumulate on its assigned partitions and triggering consumer group rebalances that temporarily suspend all partition consumption.
Alternatives Considered
AI Retrieval-Augmented Generation Platform shares core technology (kafka, postgresql) with the chosen architecture but applies different structural patterns; Geospatial Tracking Platform is a better fit for the identified workload profile.
Analytics Data Platform shares core technology (kafka, postgresql) with the chosen architecture but applies different structural patterns; Geospatial Tracking Platform is a better fit for the identified workload profile.
API Gateway Platform shares core technology (kafka, postgresql) with the chosen architecture but applies different structural patterns; Geospatial Tracking Platform is a better fit for the identified workload profile.
Audit and Compliance Platform shares core technology (kafka, postgresql) with the chosen architecture but applies different structural patterns; Geospatial Tracking Platform is a better fit for the identified workload profile.
Scaling Thresholds
Signals indicating the architecture is approaching its scaling limits:
Tier 1: Redis Geospatial Memory Pressure
Signal: Redis used_memory > 75% of maxmemory; Redis evictions appearing in INFO stats; GEOSEARCH returning stale or missing entity positions; Redis OOM errors in application logs during fleet expansion events; proximity query latency increasing above 10ms baseline
Evolution: Implement entity-scoped Redis key TTL tied to the last received update timestamp. Entities that have not sent a position update in > 5 minutes are expired from Redis automatically (Redis EXPIRE on the ZSET entry using a per-entity auxiliary key pattern, since ZSET members do not support per-member TTL natively). Alternatively, introduce a background reconciliation job that removes entities from the live position surface after an inactivity threshold. Shard the geospatial index across multiple Redis instances by geographic region using consistent hashing on the region key.
Tier 2: TimescaleDB Write Throughput Ceiling
Signal: TimescaleDB write p99 > 50ms; WAL volume > 200MB/minute sustained; disk I/O utilization > 80% on TimescaleDB data volume; chunk creation log entries during fleet expansion events correlated with write latency spikes; TimescaleDB worker queue depth growing during ingestion bursts
Evolution: Tune TimescaleDB chunk_time_interval to match the ingestion rate: smaller chunks (1-hour intervals instead of 1-day) reduce per-chunk write volume but increase chunk creation frequency. Use timescaledb-parallel-copy for bulk historical ingestion. Move the TimescaleDB WAL to a dedicated NVMe volume. Introduce write batching at the application layer: buffer 500ms of position updates per entity and write as a single multi-row INSERT, reducing the per-update overhead from N single-row INSERTs to N/batch_size batch INSERTs.
Tier 3: Geofence Evaluation Throughput Saturation
Signal: Location update p99 rising correlated with geofence zone count increases; geofence evaluation CPU > 50% of the ingestion service CPU budget; geofence entry/exit event latency > 5s from position update time; evaluation consumer Kafka lag growing steadily during peak fleet activity
Evolution: Move geofence evaluation off the synchronous write path entirely. Publish raw location updates to Kafka with zero evaluation; a separate geofence evaluation consumer reads the location topic and evaluates zones asynchronously. This decouples ingestion latency from evaluation complexity. Use a spatial index (R-tree or QuadTree) in the evaluation service to reduce per-update zone candidate evaluation from O(n) to O(log n) in zone count.
Tier 4: Fleet Scale Exceeding Single-Region Redis Capacity
Signal: Single Redis instance memory > 50GB with live fleet positions; GEOSEARCH latency rising above 20ms at the cluster boundary; Redis replication lag during high write periods causing follower reads to return stale positions; cross-region fleet tracking requiring multiple Redis instances with no unified proximity query surface
Evolution: Shard the live position surface by geographic region: each region has its own Redis geospatial index. Proximity queries that span region boundaries require fan-out to multiple regional Redis instances with result merging. Alternatively, evaluate a distributed geospatial database (PostGIS with read replicas per region) for the proximity query surface, accepting higher query latency (10–50ms) in exchange for a unified query API.
Migration Path
PostgreSQL with PostGIS extension for both live position queries and historical storage → Redis geospatial index for live positions, TimescaleDB for historical time-series
PostGIS proximity query p99 > 100ms under concurrent fleet tracking; PostgreSQL table size for location_history exceeding 500GB; VACUUM unable to keep pace with high-frequency insert + update pattern on the live position table; live position queries competing with historical analytics queries on the same table
Synchronous geofence evaluation in the HTTP write handler → Kafka-based asynchronous geofence evaluation consumer
Location update API p99 > 200ms correlated with geofence count growth; geofence evaluation CPU dominating ingestion service profiling; requirement to independently scale ingestion throughput from geofence evaluation capacity
Location history stored in PostgreSQL with monthly manual archival → TimescaleDB with automatic retention policy and S3 archival
PostgreSQL location_history table exceeding 100GB with query performance degradation on time-range queries; manual archival process failing to keep pace with ingestion rate; compliance requirement for 12-month location history retention that is not feasible in PostgreSQL at current ingestion rates
Operational Requirements
- Minimum team maturity: Experienced Backend Team: This scenario has high operational complexity. It is recommended for Experienced Backend Team teams or higher.
- Runbooks and alerting for high-severity risks: 4 high-severity risks identified. Each requires a documented runbook, alerting threshold, and on-call response procedure before running in production.
- Event stream operations expertise: This architecture includes event stream infrastructure (Kafka, Kinesis, or similar). Operations requires consumer group management, partition assignment, dead-letter handling, and lag monitoring.
- Cache sizing and eviction policy configuration: Redis or equivalent cache requires correct maxmemory configuration, eviction policy selection (allkeys-lru is common), and cold-start warming strategy after restarts.