# Migrating Counter Service storage: Design choices and learnings

[Grab](https://yomu.fyi/company/grab) · Jia Long Loh · Jul 3, 2026

## Summary

Grab migrated its Counter Service storage backend from a legacy wide-column database to Aerospike to support real-time anti-fraud windowed aggregations across tens of thousands of queries per second. To decouple storage from the Rust reader service, engineers introduced a storage facade using enum dispatch, avoiding the per-query heap allocations of boxed trait objects. The reader used configuration-driven operating modes to support shadow reads and deterministic traffic splitting without requiring code updates. On the write path, the schema was redesigned to collapse multiple bucket records into a single sorted map per counter, using atomic server-side operations to increment counters and prune expired entries. This data model redesign reduced total record counts and in-memory primary index usage by over an order of magnitude while enabling a zero-downtime transition.

## Takeaways

- To avoid per-query heap allocations associated with boxed trait objects at high query volumes, the Rust reader service implemented its storage abstraction using a concrete facade with enum dispatch.
- Collapsing time-series buckets into a single KEY\_ORDERED\_MAP record per counter resolved memory bottlenecks caused by Aerospike's 64-byte in-memory primary index per record.
- Instead of relying on record-level expiry that would drop an entire counter's history, the write path explicitly pruned stale bucket entries using atomic MapRemoveByKeyRangeOp calls during increments.

**Tags:** [Architecture](https://yomu.fyi/topic/architecture), [Migrations](https://yomu.fyi/topic/migration), [Performance](https://yomu.fyi/topic/performance), [Rust](https://yomu.fyi/topic/rust), [Scalability](https://yomu.fyi/topic/scalability)

[Read original post](https://engineering.grab.com/counter-service-storage-migration)
