
Building a System for Billions of Notifications Daily (1): How Kakao Bank Built a New Framework for its Sending Platform
Key Points
- 1KakaoBank successfully redesigned its high-performance messaging platform by implementing a layered architecture that decouples complex system-level optimizations from simple business logic interfaces.
- 2By utilizing a Lock-Free Work-Stealing queue mechanism and garbage collection optimizations, the new system effectively achieves over 10,000 TPS while maintaining low P99 latency.
- 3This structural transition minimizes developers' cognitive load by allowing them to focus solely on service-specific requirements, significantly improving both system scalability and developer productivity.
This paper details the architectural redesign of Kakao Bank's Unified Messaging System (UMS), which handles over 100 million notifications daily. The team sought to overcome the "performance vs. simplicity" trade-off inherent in high-throughput systems by decoupling complex core mechanics from business logic.
1. Structural Redesign: Layered Architecture
The system is divided into two distinct layers to simplify the developer’s mental model:- Service Layer: Focuses on business requirements. Developers implement only three methods (
availableHandle,handle, andalias) within aRequestHandlerinterface and defineSenderimplementations for specific channels (e.g., Push, Email). This allows developers to focus on "what" to do rather than "how" the system handles concurrency or distribution. - Core Layer: Encapsulates high-performance mechanics, including concurrency control, thread management, and load balancing, keeping these implementation details invisible to Service Layer developers.
2. Core Methodology: Work-Stealing and Performance Optimization
To achieve >10,000 TPS while maintaining low P99 latency, the system addresses the inefficiencies of legacy single-queue designs—where slow tasks blocked subsequent operations—by implementing an Age-Based Work-Stealing queue mechanism.- Work-Stealing Mechanism: Instead of a single queue, the system utilizes multiple channel-based queues. A central manager scans these queues every 100ms. If a queue's age (waiting time of the oldest message) exceeds 500ms, the system triggers a steal to distribute the workload to idle workers.
- Lock-Free Concurrency: To avoid the performance overhead of traditional locking, the
WorkStealQueueusesAtomicReferenceArrayandCAS(Compare-And-Set) operations. Thestealmethod is defined as:
The system uses a
barrier (an AtomicInteger) to designate the split point, allowing workers to steal tasks without blocking others.
- Zero-Copy Operations: The architecture avoids array copying during work-stealing by updating indices and markers (barriers) within a fixed-size
AtomicReferenceArray. This minimizes memory allocation and reduces GC (Garbage Collection) pressure, which is critical for high-throughput JVM environments.
3. GC and Computational Efficiency
To minimize Full GC pauses, the system employs the following:- Immediate Object Clearing: After a task is polled or stolen, the reference is set to
null(tasks.set(index, null)) to allow the GC to reclaim memory immediately. - Template Pre-processing: To improve message rendering speed, the system caches template structures, reducing the complexity of message assembly from to , where is the number of substitution variables and is the template length.
- Inline Classes: Kotlin's
inline classfeature is used to prevent the creation of wrapper objects for primitive types, significantly reducing heap memory overhead.
By isolating performance-critical, complex logic within the Core Layer and providing a declarative, simple interface in the Service Layer, the team successfully eliminated structural bottlenecks and reduced the time required to add new notification channels.