Designing a High-Throughput, Secure Key-Value Store: Inside PayPal’s JunoDB
When building a mission-critical distributed data store for a global financial platform like PayPal, engineering decisions are dominated by two conflicting constraints: ultra-low latency performance and non-negotiable enterprise security.
PayPal open-sourced JunoDB, their distributed key-value store powering core backend services, caching layers, and session storage. While standard distributed key-value stores often sacrifice security controls or treat encryption as an afterthought to maintain throughput, JunoDB was designed from the ground up to guarantee strict data protection in transit and at rest without degrading performance.
Before exploring its security guarantees, it is critical to understand JunoDB’s raw performance profile. In distributed storage, establishing quorums across replicas typically introduces network overhead and latency spikes. JunoDB counters this with the following design optimizations:
- Persistent Connections: Rather than repeatedly paying the cost of the TCP three-way handshake and TLS negotiation on every interaction, JunoDB maintains long-lived, persistent connection pools across client SDKs, proxies, and storage nodes.
- Lightweight Concurrency: Replicated writes and intra/cross-datacenter quorum requests are fired concurrently using lightweight Go routines rather than synchronous or blocked I/O.
- Quorum Coordination: The proxy broadcasts write requests in parallel and resolves as soon as write quorum is satisfied, masking tail latencies from slower individual nodes.
Benchmark Results
Under controlled benchmarks published in the JunoDB repository:
- P99 Latency: ~4 milliseconds
- Throughput: ~45,000 requests per second (RPS)
- Concurrency: 100,000 persistent connections
- Topology: 1 proxy node and 3 storage engine servers
Delivering a 4 ms P99 under high connection concurrency proves that end-to-end security layers do not inherently prevent distributed systems from achieving single-digit millisecond performance.
2. The Financial Security Threat Model
Financial platforms handle sensitive user data, payment tokens, and operational caches. A compromised node, an untrusted network segment, or a disk inspection should never expose plaintext data. JunoDB enforces a zero-trust model across two primary planes:
- Data in Transit (Eavesdropping & Man-in-the-Middle Protection):
- Client-to-Proxy communication is TLS encrypted.
- Proxy-to-Storage node communication is TLS encrypted.
- Inter-datacenter proxy-to-proxy traffic (used for asynchronous cross-cluster replication) travels over encrypted TLS tunnels.
- Data at Rest (Disk Inspection & Storage Compromise Protection):
- Key-value payloads are encrypted before being flushed to persistent media.
- Physical data blocks on disk are opaque ciphertext.
+----------------+
| Juno Client SDK|
+--------+-------+
| TLS (Payload encrypted on Client)
v
+--------+-------+ TLS (Cross-DC Replication)
| Juno Proxy | -------------------------------------> Remote Proxy
+--------+-------+
| TLS
v
+--------+-------+
| Storage Server | ---> Encrypted Payload on Disk
+----------------+
3. End-to-End Payload Encryption Pipeline
JunoDB partitions payload encryption responsibility to maximize safety without breaking backward compatibility:
- Client-Side Encryption (Primary Path): The Juno Client SDK fetches cryptographic material from an external Key Management System (KMS). Payloads are encrypted before they leave the application memory space.
- Proxy-Assisted Encryption (Fallback Path): If a legacy or specialized client cannot perform encryption locally, the Juno Proxy steps in, retrieves the appropriate encryption key, encrypts the payload, and forwards it to the storage tier.
- Storage Layer Encryption: The storage engine does not write raw byte arrays to disk; it wraps records with encryption headers (including metadata such as Key Version IDs) and persists the encrypted ciphertext.
4. Key Management and Zero-Downtime Key Rotation
Static encryption keys are a liability. If a key is compromised, every record encrypted under that key is exposed. A robust database must support regular key rotation without requiring downtime or multi-terabyte bulk re-encryption jobs.
JunoDB integrates directly with an external Key Management System (KMS) to orchestrate keys across the cluster.
Historical Key Tracking
When key K1 is rotated to key K2:
- The KMS generates a new active key (K2) with a unique identifier (Key ID).
- Any new write or update operation encrypts using K2.
- Historical keys (K1,K0,…) are retained inside the KMS in a read-only state. Existing records written with K1 remain valid and decryptable.
Every record stored on disk includes a lightweight metadata header pointing to the KeyID used to encrypt that specific entry:
+-------------+---------------+-----------------------+
| Key ID: K1 | Version: v2 | Ciphertext Payload... |
+-------------+---------------+-----------------------+
Lazy Re-Encryption
Re-encrypting billions of keys the moment a key is rotated would spike CPU, saturate disk I/O, and compromise read/write latencies. JunoDB avoids bulk sweeps by applying lazy re-encryption on read:
- A client issues a read request for a given key.
- The storage engine reads the payload and parses the
KeyID (e.g., K1).
- If K1 is detected as an expired or deprecated key version, the engine fetches K1 from the KMS historical store and decrypts the value.
- The engine re-encrypts the payload using the current active key (K2).
- The newly encrypted record is asynchronously or synchronously written back to storage.
- The decrypted value is returned to the client.
Over time, hot and warm data naturally transitions to the latest encryption key without batch background jobs degrading real-time traffic.
Client Read Request
|
v
Read Record [KeyID: K1]
|
Is K1 Current? ----(Yes)----> Decrypt with K1 ----> Return Plaintext
|
(No)
v
Decrypt with Historical K1
|
Re-encrypt with Active K2
|
Write Back [KeyID: K2] to Disk (Lazy Update)
|
Return Plaintext
5. Why Go? The CPU-Bound Nature of Cryptographic Workloads
Key-value stores are conventionally classified as I/O-bound or memory-bound workloads (dominated by network round-trips, RAM lookups, and disk flushes). Technologies like Redis leverage single-threaded event loops because memory lookups execute in sub-microsecond intervals.
However, ubiquitous cryptographic enforcement alters the fundamental resource bottleneck of the database:
- TLS termination and renegotiation on every hop.
- Payload encryption and decryption for reads, writes, and replications.
- Hash generation and key validation.
Under strict security policies, a key-value store transitions into a heavily CPU-bound workload.
The Concurrency Dilemma
- A single-threaded architecture (like Redis) running on a 32-core or 64-core enterprise server would saturate a single core performing cryptographic operations while the remaining cores sit idle, creating severe throughput bottlenecks.
- Thread-per-request models (like classic C++ or Java architectures) risk thread contention, memory bloat, and scheduling overhead under tens of thousands of simultaneous connections.
JunoDB’s Architectural Fit
JunoDB was built in Go (Golang) to handle this specific profile:
- Goroutine Scheduler (M:N Concurrency): Go maps hundreds of thousands of light goroutines across all available OS threads, spreading CPU-intensive AES and TLS computations evenly across all available hardware cores.
- Parallel Quorums: Intra-cluster quorum checks and cross-datacenter writes run as non-blocking concurrent routines without spawning heavy OS threads.
- Optimized Standard Library Crypto: Go provides highly optimized, hardware-accelerated cryptographic primitives (utilizing Intel AES-NI and ARM NEON instructions directly).
Summary of Architectural Lessons
- Zero Trust In Practice: Financial systems require end-to-end encryption at rest and in transit. Relying solely on network firewalls or disk-level full disk encryption (FDE) leaves internal hops vulnerable.
- Avoid Eager Re-Encryption: When handling key rotations across large-scale data stores, adopt a lazy re-encryption model indexed by
KeyID headers to prevent I/O and CPU spikes.
- Match Language Runtime to the True Bottleneck: Never choose an architectural runtime solely based on conventional workload labels. While raw key-value caching is I/O-bound, authenticated and encrypted key-value operations are CPU-bound, requiring true multi-core parallel runtime support.