Chapter 12 · Time Series and Geospatial Workloads
Combine Time/Geo Data with Search, Streams, and Application-Side Aggregation
Compose Time Series, GEO, Streams, Search, and application logic without confusing storage roles, atomicity boundaries, security filters, or derived state with source-of-truth state.
Learning outcomes
AtlasMart now has three different data questions: current courier proximity, historical telemetry, and durable operational events. Trying to force them into one structure creates either missing history, poor queries, or security mistakes. The capstone composes each structure for the role it actually provides.
Assign current location, metric history, durable events, searchable metadata, and business joins to distinct layers.
Apply tenant/security filters before candidate data leaves the Redis/application trust boundary.
Explain cross-structure consistency and retry gaps without claiming atomicity that the commands do not provide.
Build reconciliation keys/events so derived GEO/Search projections can be repaired from authoritative state.
Measure query selectivity, backlog, memory, and latency while preserving rollback and migration paths.
All Chapter 12 mandatory labs reuse the disposable Chapter 01
environment: Redis Open Source 8.10.1 from Docker
Official Image redis:8.10.1, container
atlasmart-redis-ch01, standalone topology, host
publication 127.0.0.1:6379, TLS disabled only
because traffic stays on loopback, default ACL user disabled,
named users atlasmart-app and
academy-admin, logical database 0, AOF with
appendfsync everysec plus RDB snapshots,
persistent /data, and no explicit
maxmemory limit or eviction policy. Redis 8
integrates Time Series into Redis Open Source; geospatial
commands are core Redis commands. Fixtures stay under
atlasmart:ch12:*. Mandatory work is local and
uses synthetic telemetry/coordinates only; no paid service,
production endpoint, or real credential is required.
1. One domain, several structures, different guarantees
| Question | Structure | What it gives | What it does not give |
|---|---|---|---|
| Where is courier-42 now? | GEO sorted set | Low-latency current proximity | Historical route or acknowledgment |
| How did temperature change? | Time Series | Timestamped numeric samples + aggregation | General event payload/replay semantics |
| What business events occurred? | Stream | Persistent ordered entries + consumer groups | Spatial proximity or numeric rollup by itself |
| Which warehouses match metadata/text filters? | JSON/Hash + Search | Secondary indexed discovery | Authoritative source migration or security by filter alone |
| How do we combine all of these? | Application/service layer | Authorization, business joins, idempotency, reconciliation | Automatic atomicity across unrelated Redis keys/services |
2. Build a tenant-scoped current-location projection
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app GEOADD atlasmart:ch12:geo:tenant-a:couriers 49.8671 40.4093 courier-42docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app GEOADD atlasmart:ch12:geo:tenant-a:couriers 49.8495 40.3777 courier-43docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app GEOSEARCH atlasmart:ch12:geo:tenant-a:couriers FROMLONLAT 49.8671 40.4093 BYRADIUS 5 km WITHDIST ASC COUNT 10
The tenant is part of the key boundary, not merely a post-query attribute. If tenant B must not see tenant A members, do not retrieve a global candidate set and remove B-ineligible rows only after it has crossed an API/logging boundary.
3. Store historical numeric telemetry independently
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app TS.CREATE atlasmart:ch12:ts:tenant-a:courier-42:battery RETENTION 3600000 DUPLICATE_POLICY LAST LABELS tenant tenant-a entity courier-42 metric battery unit pctdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app TS.MADD atlasmart:ch12:ts:tenant-a:courier-42:battery 1700000000000 88 atlasmart:ch12:ts:tenant-a:courier-42:battery 1700000060000 86 atlasmart:ch12:ts:tenant-a:courier-42:battery 1700000120000 84docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app TS.RANGE atlasmart:ch12:ts:tenant-a:courier-42:battery - +docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app TS.RANGE atlasmart:ch12:ts:tenant-a:courier-42:battery - + AGGREGATION avg 120000
Battery history is numeric time-series state. The current GEO location and battery samples can share an entity ID but have different retention and query semantics.
4. Record durable operational events in a Stream
A location update can also produce a durable event for downstream billing, notifications, or analytics. Redis Streams retain entries and support consumer groups; GEO does not.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app XADD atlasmart:ch12:stream:tenant-a:location-events '*' entity courier-42 lon 49.8671 lat 40.4093 event_ts 1700000120000 source mobile-appdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app XRANGE atlasmart:ch12:stream:tenant-a:location-events - + COUNT 10
Quoting * prevents host-shell glob expansion. The
Stream entry is evidence of an event, not proof that every
downstream side effect occurred exactly once. Chapter 07’s
at-least-once/idempotency rules still apply.
5. Wrong approach: assume GEOADD + TS.ADD + XADD form one atomic business transaction
An application sends three separate commands: update current GEO, append telemetry, append event. If the connection fails after the first command, retrying blindly can create a partially updated state or duplicate event. Command atomicity applies per command, not automatically across this workflow.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app GEOPOS atlasmart:ch12:geo:tenant-a:couriers courier-42docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app TS.GET atlasmart:ch12:ts:tenant-a:courier-42:batterydocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app XREVRANGE atlasmart:ch12:stream:tenant-a:location-events + - COUNT 1
The repair options depend on the invariant: idempotency keys, a durable source event plus rebuildable projections, a transaction/function when all keys are compatible and co-located, or an external workflow. Transactions/functions/Cluster locality are covered in Chapters 13–14 and 21; do not smuggle those guarantees into Chapter 12.
6. Prefer one authoritative event and rebuildable projections when consistency dominates
A common design is: write a durable location event first, then consumers update GEO current state and Time Series history idempotently. This creates eventual consistency but gives a replay/reconciliation path. Another design makes the application database authoritative and Redis projections rebuildable. Choose explicitly.
If a structure can be rebuilt from an authoritative log/database, document the rebuild procedure, lag metric, checkpoint/idempotency key, and cutover/rollback process. If it cannot be rebuilt, it must receive stronger backup/durability treatment.
7. Search is a secondary index, not a replacement for time/GEO state
AtlasMart might index warehouse JSON documents by region, capability, or text fields using Redis Search, then use GEO for proximity. Search schema/index visibility, GEO member state, and Time Series samples remain different data structures. A Search filter can narrow business candidates but does not migrate or authorize source documents automatically.
docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin COMMAND INFO FT.CREATE FT.SEARCH FT.INFO
Search administration remains an admin concern in this disposable lab. The mandatory Chapter 12 workflow still works with Time Series, GEO, Streams, and application filtering even if a client wrapper lacks Search helpers.
8. Application-side join: bound candidates before fetching more state
Do not fetch every courier’s Time Series and then calculate distance. First use GEO to produce a bounded tenant-authorized candidate set, then fetch only the needed Time Series/current metadata for those IDs. This is a selectivity-first join plan.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app GEOSEARCH atlasmart:ch12:geo:tenant-a:couriers FROMLONLAT 49.8671 40.4093 BYRADIUS 5 km WITHDIST ASC COUNT 5docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app TS.GET atlasmart:ch12:ts:tenant-a:courier-42:battery
The application still must verify authorization and freshness. A nearest courier with a stale position or expired shift status is not a valid dispatch candidate simply because distance is small.
9. Freshness belongs in the query contract
Current GEO has no built-in per-member timestamp. Store freshness in authoritative event metadata, a companion key, or a model that can be checked before dispatch. Time Series can store a location-update timestamp metric, but that still does not atomically bind to GEO unless you deliberately add a stronger mechanism.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app SET atlasmart:ch12:fresh:tenant-a:courier-42 1700000120000 EX 300docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app GET atlasmart:ch12:fresh:tenant-a:courier-42docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app TTL atlasmart:ch12:fresh:tenant-a:courier-42
TTL is a freshness aid, not historical retention and not a guaranteed member-deletion scheduler. The application must decide what to do when freshness is absent.
10. Reconciliation detects projection drift
A periodic reconciler can compare the newest authoritative event with current GEO and latest Time Series state. Do not hide drift by simply overwriting; record the mismatch rate and investigate retry/crash patterns.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app XREVRANGE atlasmart:ch12:stream:tenant-a:location-events + - COUNT 1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app GEOPOS atlasmart:ch12:geo:tenant-a:couriers courier-42docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app TS.GET atlasmart:ch12:ts:tenant-a:courier-42:battery
These outputs are intentionally different data shapes. Application code must parse and compare the fields that share an invariant; Redis does not infer the relationship from key names.
11. Capacity and observability sheet
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app MEMORY USAGE atlasmart:ch12:geo:tenant-a:couriersdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app MEMORY USAGE atlasmart:ch12:ts:tenant-a:courier-42:batterydocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app MEMORY USAGE atlasmart:ch12:stream:tenant-a:location-eventsdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app XLEN atlasmart:ch12:stream:tenant-a:location-eventsdocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin INFO commandstatsdocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin INFO latencystatsdocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin INFO persistencedocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin INFO replication
Track GEO member cardinality/update rate, Time Series series/sample/label cardinality, Stream length/consumer lag/PEL state, Search index memory if enabled, client timeout/retry counts, p50/p95/p99 latency, AOF/replication pressure, and reconciliation drift. One aggregate “Redis CPU” graph cannot explain this composition.
12. Cluster, persistence, and security boundaries
Standalone success does not prove Cluster behavior. Multi-key transactions/functions and cross-key operations can require same-slot placement. Persistence settings apply to all these Redis structures but do not make side effects in external systems atomic. ACL patterns protect keys, not individual GEO members or Time Series samples. TLS/network policy protects transport/perimeter. Search filters and labels are query semantics, not an authorization substitute.
All mandatory work uses Redis Open Source 8.10.1 locally. Redis Cloud, Redis Software, or third-party managed services may change networking, TLS, persistence, active-active behavior, quotas, observability, command rollout, and operational responsibility; validate the actual service before carrying over standalone assumptions.
13. Controlled failure drill: stale projection after event write
Simulate an event accepted into the Stream while the GEO projection is intentionally left unchanged. Then detect the mismatch and apply the projection idempotently.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app XADD atlasmart:ch12:stream:tenant-a:location-events '*' entity courier-42 lon 49.8800 lat 40.4180 event_ts 1700000180000 source mobile-appdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app GEOPOS atlasmart:ch12:geo:tenant-a:couriers courier-42docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app GEOADD atlasmart:ch12:geo:tenant-a:couriers 49.8800 40.4180 courier-42docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app GEOPOS atlasmart:ch12:geo:tenant-a:couriers courier-42
This is safe because only synthetic Chapter 12 keys are used. The lesson demonstrates eventual projection repair; it does not claim the pattern alone provides exactly-once processing.
14. Cleanup
docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin UNLINK atlasmart:ch12:geo:tenant-a:couriers atlasmart:ch12:ts:tenant-a:courier-42:battery atlasmart:ch12:stream:tenant-a:location-events atlasmart:ch12:fresh:tenant-a:courier-42
UNLINK targets only the named Chapter 12 fixtures.
Do not replace this with FLUSHDB,
FLUSHALL, or broad pattern deletion on a shared
Redis instance.
15. Production judgment
Compose Redis structures only when each one has a precise responsibility and observability contract. Prefer bounded candidate generation, explicit tenant keys/ACLs, freshness checks, idempotent projection updates, reconciliation, and rebuildable derived state. Budget write amplification across GEO/Time Series/Streams/Search, persistence and replication, and monitor tail latency separately. For migration, dual-read/dual-write only with a reconciliation plan; for rollback, keep the authoritative source and schema/version markers. Do not treat Redis as an automatic distributed transaction coordinator across these layers.
Check your understanding
- Which structure should answer “nearest courier now”?
- Which structure should answer “what events happened and can a consumer replay them”?
- Why is post-filtering tenant results after a global GEO search risky?
- What makes a Redis projection operationally safer?
- Do three successful Redis commands prove one atomic business transaction?
Review the answers
A tenant-authorized GEO current-position index.
A Redis Stream or another durable event log.
Unauthorized candidate data may already have crossed an API/logging/tracing boundary before it is removed.
An authoritative source, idempotent update rule, lag/drift metrics, reconciliation, and a tested rebuild/rollback path.
No. Each command is atomic individually; cross-command/business atomicity needs an explicit stronger mechanism and topology-compatible design.
16. Summary and next step
Chapter 12 ends with clear roles: Time Series for numeric history/aggregation, GEO for current proximity, Streams for durable event/replay semantics, Search for secondary discovery, and application code for authorization, joins, freshness, idempotency, and reconciliation. Chapter 13 now introduces MULTI/EXEC and WATCH so you can reason precisely about when Redis can—and cannot—strengthen multi-command atomicity.
Authoritative references
- Redis Open Source 8.10 release notes
- Redis 8.10 command reference
- Redis Time Series data type
- Time Series configuration
- TS.CREATE
- TS.ADD
- TS.INFO
- TS.RANGE
- TS.MRANGE
- TS.CREATERULE
- TS.QUERYINDEX
- Redis geospatial data type
- GEOADD
- GEOSEARCH
- GEODIST
- MEMORY USAGE
- INFO
- Redis Streams
- XADD
- Redis Search indexing
- Redis transactions