Patterns from this design

Geospatial proximity search at scale

geo

Index points in a geo set and answer radius queries directly

When
You need 'all entities within R of a point' under tight latency and you don't want to roll your own spatial index, scan-and-filter the whole table, or stand up a search cluster for what is fundamentally a sorted-set lookup. Reach here for the live 'now' index, not for cold analytics.
AWS
Store points with GEOADD on ElastiCache for Redis and answer with GEOSEARCH ... BYRADIUS (the successor to GEORADIUS). Redis encodes lon/lat as a 52-bit geohash inside a sorted set, so a single-cell radius query is range scans over the set — sub-millisecond and built in. Rejected alternatives: Amazon Location Service Trackers (no radius-query API, no per-asset TTL, no p99 SLA at high write rates) and Amazon OpenSearch (segment-refresh lag fights sub-10 s freshness, no sub-second per-document TTL, segment merges wreck p99 under heavy writes).
Trade-off
GEOSEARCH returns everything in a circle/box but can't express arbitrary polygons or 'inside this delivery zone' — anything beyond a radius needs a point-in-polygon post-filter in your service, and the set holds only point geometry, not the rich attributes you must join back from another store. GEOSEARCH is also a single-key command: a query whose radius spans many sharded cells needs the scatter-gather pattern, not one call.
geo

Scatter-gather across sharded geo cells, merge in-process

When
You shard a geo index by fixed-area cell (to spread dense-city write/read load) but a query radius covers many cells across many shards. A single-key radius command can't cross shard slots, so the 'one round trip' latency story is false the moment the radius exceeds one cell.
AWS
In the query service, compute the cell covering of the query circle (S2/H3), fire one GEOSEARCH per covering cell in parallel — pipelined per shard node — then merge and re-sort the candidate sets by true distance and apply attribute filters in-process. Co-locate any per-cell attribute lookup (e.g. an availability HMGET) on the same shard via a matching hash tag so each shard answers in one pipelined round trip.
Trade-off
A single query becomes dozens of parallel commands (read amplification), and p99 is bounded by the slowest shard in the fan-out, not the average — so the cell size must be chosen against the radius to keep the covering in the tens, not hundreds. Coarsen the shard cell if the covering blows up, trading write-load distribution for a tighter fan-out.
geo

Always query the cell and its 8 neighbors

When
You shard or index by geohash/grid cell and answer proximity with a prefix or single-cell lookup — two entities 10 m apart but across a cell boundary get different prefixes, so a single-cell query silently drops half of them right where density matters most.
AWS
Expand the target cell to its 3x3 (N+8) neighbor set before querying, or let Redis GEOSEARCH do the expansion for you — it computes the covering neighbor cells internally so the boundary bug never reaches your code.
Trade-off
Correctness costs you up to a 9x read amplification per query (nine cell ranges instead of one), and at fine resolutions the radius may still spill past the first neighbor ring, so the cell size must be chosen against the query radius.
storage

Shard by spatial cell, not by region name

When
Geographically clustered load (downtown SF, downtown NYC at rush hour) concentrates every write and read for a city onto one shard when you shard by city/region — Lyft hit exactly this ceiling at ~100k ops/s per region shard.
AWS
Use a fixed-area spatial cell (S2 level 5 ≈ 1 km², or an H3 res) as the shard key and hash-tag the Redis key by that cell, so the ElastiCache Cluster's CRC16 slotting spreads a dense city across dozens of shards while keeping nearby data co-located for radius queries.
Trade-off
Cell population is not uniform — a stadium at game time still hot-spots its own cell — and changing the cell-to-shard mapping is a reshard, so you must pick a resolution and online-reshard during low-traffic windows rather than re-key on the fly.
storage

Let ephemeral presence expire instead of deleting it

When
You track 'who is here right now' (online drivers, live cursors, active sessions) and a client that vanishes without a goodbye — a dead phone, a dropped socket — would otherwise linger in the index forever and poison results.
AWS
Write each presence record to ElastiCache with a short TTL (30 s) refreshed on every heartbeat; absence of a refresh expires the record automatically, so you never need a separate reaper job or an explicit 'I'm leaving' message you might never get.
Trade-off
A network partition that drops heartbeats expires healthy clients into false absence, and the TTL is a direct freshness/load knob — shorter means fewer ghosts but more write pressure to keep everyone alive.
ingestion

Batch at an aggregation tier, buffer through a stream, fan out from a consumer

When
A huge, spiky fleet of writers (millions of mobile devices) would overwhelm a stateful store that can't hold millions of connections or absorb sudden bursts, and you need durable replay if a downstream consumer falls over. Watch the per-request edge cost: a managed API at millions of req/s and one-record-per-call stream PUTs can each be a seven- to eight-figure monthly bill before any compute runs.
AWS
Front the stream with an NLB + stateless connection-server fleet (ECS Fargate) that holds the persistent device connections and batches 50-100 records per Kinesis PutRecords call — cutting PUT units and per-request charges ~100x versus a direct API Gateway POST. Land writes in Kinesis Data Streams (1 MB/s per shard) and run a Lambda consumer fleet (enhanced fan-out for the latency-critical consumer, standard polling for lag-tolerant ones; explicit BatchSize and reserved concurrency) that drains micro-batches and fans out to ElastiCache and an S3 firehose. Report per-sink batch-item failures independently and buffer failed cache writes to a short-TTL SQS retry queue so one sink's failover never stalls the shard iterator.
Trade-off
You add up to a couple hundred ms of buffering latency to the write path and inherit shard-count capacity planning (and resharding), in exchange for protecting the store from connection storms, collapsing edge cost by two orders of magnitude, and getting durable replay for free. Stream replay is at-least-once, so every sink must be idempotent or dedup by per-shard sequence number.