Hands On Kafka

Hands On Kafka

Uber-Lite: Architecting High-Scale Geo-Spatial Matchmaking Systems

Lesson 64: Building the REST Controller

Module 5 — Interactive Queries & RPC

Sep 29, 2026
∙ Paid

The “Naive” Approach

The standard-issue instinct here is: add spring-boot-starter-web, slap @RestController on a class, write @GetMapping("/drivers/near/{lat}/{lon}"), and call store.get() inside the method body. It compiles, it demos, and it is wrong in three separate ways that only surface under load.

Thread pool exhaustion. Spring’s embedded Tomcat services requests from a bounded platform-thread pool — typically 200 threads by default. But a /drivers/near request is not a cheap local lookup; per Lesson 61, it’s a scatter-gather operation that fans out RPC calls to every host that owns a candidate partition and blocks waiting for responses. Each of those 200 Tomcat threads can be parked mid-request waiting on a network round-trip. At any meaningful concurrency — a few hundred simultaneous map-view refreshes — you exhaust the pool, and every subsequent request queues behind threads that are doing nothing but waiting. This is a classic thread-per-request blocking-I/O failure mode, and it is exactly the problem Java 21 virtual threads exist to solve.

Silent wrong answers. A naive controller calls store.get(key) against the local RocksDB instance only. It has no concept of HostInfoRouter (Lesson 62) — it doesn’t know or care that the partition it needs might live on a different instance entirely. It returns whatever the local store happens to contain, which for driverId values not hashed to this instance’s partitions is simply nothing, with no error, no partial: true flag, nothing to indicate the response is incomplete. This is strictly worse than the honest-degradation failure modes we’ve built everywhere else in this course — it’s not a flagged partial result, it’s a confidently wrong empty result.

Framework overhead hiding the physics. DispatcherServlet, Spring’s argument resolvers, and its classloading machinery add real latency and, more importantly, obscure the fact that what you’re building is a transport adapter over an RPC protocol you already wrote by hand in Lesson 61. Wrapping that in Spring doesn’t simplify anything — it hides the one part of the system (network fan-out, timeout handling) where visibility matters most.

The “Uber-Lite” Architecture

User's avatar

Continue reading this post for free, courtesy of Kafka.

Or purchase a paid subscription.
© 2026 SystemDR · Privacy ∙ Terms ∙ Collection notice
Start your SubstackGet the app
Substack is the home for great culture