Grab Engineering - Counting Service for User Event Tracking
Here is my notes about the blog of Grab for Counting Service
-
Using Grab’s Trust Counter Service to Detect Fraud Successfully
-
Migrating Counter Service storage: Design choices and learnings
1. Overview
- Event Tracking Metrics (Flink, ScyllaDB) for Counter Service is used across Grab’s anti-fraud platform
2. High-level Architecture

2.1. Counter creation workflow
-
User opens the Counter Creation UI and creates a new key “fraud:counter:counter_name”.
-
Configures required fields.
-
The Counter service monitors the new counter-creation, puts a new counter into load script storage, and starts processing new counter events (see Counter Write below).
2.2. Counter write workflow
-
The Counter service monitors multiple streams, assembles extra data from online data services (i.e. Common Data Service (CDS), passenger service, hydra service, etc), so that rich dataset would also be available for editors on each stream resource.
-
The Counter Processor evaluates the user-configured expression and writes the evaluated values to the dedicated Grab-Stats stream using the GrabPlugin tool.
-
We use Grab-Stats as our storage service. Basically Grab-Stats runs above ScyllaDB, which is a distributed NoSQL data store
Reference: Why Grab use ScyllaDB
2.3. Counter read workflow

-
Read list of keys from endpoint /BatchRead
-
In comparison with in-memory storage like AWS elasticCache, it is 10 times cheaper and as reliable as AWS in terms of stability. The p99 of reading from ScyllaDB is less than 150ms which satisfies our SLA.
3. Dashboard
3.1. Streaming Dashboard (e.g. Flink)

3.2. Track Human Config Error

4. How the UI Tool Work
4.1. Create Metrics

4.2. View Metrics

5. Reading Path: How endpoint BatchRead work ?

6. Why rewrite this service from Go to Rust ?
6.1. Rust save more compute cost than Go
| Resource Metric | Rust Performance | Go (Golang) Performance | Technical Reason |
|---|---|---|---|
| RAM (Memory) | Very Low (typically 50–80 MB) | Moderate (typically 100–320 MB) | Go’s garbage collector requires additional memory overhead to operate efficiently. |
| CPU Usage | Highly efficient (compiled directly to native machine code) | Slightly higher | Go’s runtime periodically performs garbage collection, which consumes CPU cycles in the background. |
| Binary Size | Small to Medium | Larger base size | Go statically links its runtime into every executable, resulting in larger binaries compared to Rust. |
6.2. Rust is consistently faster than Go in almost every benchmark
6.3. How Rust make memory safety
| Vulnerability | How Rust Prevents It | Mechanism |
|---|---|---|
| Dangling Pointers | Prevents references from outliving the data they point to | Lifetimes + Borrow Checker |
| Double Free | Ensures every value has exactly one owner that frees it once | Ownership |
| Use After Free | Makes moved values unusable after ownership is transferred | Move Semantics |
| Buffer Overflow | Checks array/slice bounds before indexing | Runtime Bounds Checking (safe indexing) |
| Data Races | Allows either many readers or one mutable writer, never both | Borrow Checker (&T vs &mut T) |
| Null Pointer Exploits | Uses Option<T> instead of nullable references |
Type System |
7. Writing Path: Shadow Tables for new flow and how user read the query

7.1. How to optimize latency
- To raise the memory ceiling, we tried moving the primary index itself from RAM to local Non-Volatile Memory Express (NVMe) while keeping data on SSD.
8. Learning Notes
-
Learning about Flink.
-
Learning about ScyllaDB.
-
Learning about Streaming.
-
Learning about Rust.
-
Learning about Shadow Testing Tables.