The Code

My Use Case Link to heading

This was designed for a custom JWT validation system I wrote for one of my projects. The main API runs on AWS Lambda, and I needed a super fast caching layer for basic data. I literally needed a bare-bones key/value store, no need for persistence, replication, modules, Lua scripting, eviction policies, fine-grained ACLs, or the universe of configuration flags that comes with general-purpose data stores.

The combination of small values, tight latency, and owned endpoints made existing options feel like a mismatch. Systems like Redis and Valkey are excellent; they’re also designed to solve larger, broader problems than mine. Even with the features turned off, we still inherit the operational surface.

What I actually needed Link to heading

My workload is very read-heavy. Think on the order of 10,000 reads for every write. My data is also small and boring, a handful of bytes to a few hundred characters. The consumer and the producer are both mine. That single ownership is the key enabler; it lets me dictate the wire format and API contract without worrying about third-party clients.

I don’t need data to survive a process restart, and I don’t need cross-region failover. What I do need is low and predictable latency.

Feature Requirements Link to heading

  • Read-heavy: ~10,000 reads for every write

  • Small data: a handful of bytes to a few hundred characters

  • Single ownership: both the consumer and producer are mine, so I dictate wire format and API contract

  • No persistence: data doesn’t need to survive restarts

  • No cross-region failover

  • Latency: low and predictable

Why UDP and fixed frames? Link to heading

The first architectural decision was the transport layer. I chose UDP for one reason: one datagram equals one operation. There’s no connection handshake or connection state to manage, and for tiny messages the kernel does less work. The usual caveats about UDP, packet loss and reordering, are real, but in our environment the network is stable, the messages are small, and the consumer is prepared to time out and retry. I place a strict requirement on callers; they must provide a context with a deadline. If the request doesn’t complete within those bounds, the call fails. That simple rule eliminates an entire class of mystery lags.

On top of UDP, I defined a compact, fixed-size frame. I encrypt the entire frame using AES-256-GCM with a random 96-bit nonce per message. Random nonces are safe for at most 2^32 messages under one key, so the key must be rotated by restarting the server with a new key before that count is reached.

The Frame Link to heading

  • 1 byte per command limits us to 256 commands ever

  • 4 bytes for flags limits us to a maximum of 32 flags

  • 128 bytes for the key

  • 863 bytes for the value (chosen so the full frame fits within 1024 bytes)

This gives us a total of 996 bytes

The Encryption Link to heading

  • 12 bytes for the nonce

  • 16 bytes for the tag

This gives us a total encrypted packet size of 1024 bytes, comfortably below the typical 1500-byte Ethernet MTU. That ensures a single packet per operation without fragmentation.

One note on the layout: the value sits at the end of the frame. If I ever need an additional field, I can carve it out of the value without moving anything else.

Because I own both ends, I can keep security simple and non-optional. Every request and response is encrypted with AES-256-GCM using a 32-byte key.

The developer experience Link to heading

A system this small should be easy to use without memorizing a manual. The client library is a slim wrapper over the wire format. Callers construct a context with a timeout and call Set, Get, Delete, or Exists. For Set, there are two flags that cover the only interesting write behaviors I need: Overwrite (allow an existing key to be replaced) and Old (return the previous value if I want it). Encryption, frame building, and deadlines applied to read and write are all handled internally. For local testing, there’s a CLI that mirrors the library and makes it trivial to poke the server from a shell.

More importantly, the library has sharp edges where they help. If we forget to set a deadline, the call fails immediately. If a packet can’t be decrypted, the server drops it without replying and the call times out. If it decrypts but isn’t a valid frame, the server replies with an error.

What this wins us Link to heading

The obvious win is latency. A full encrypted round trip over loopback, including a fresh UDP socket per call, measures about 15 microseconds on a laptop-class Intel i7. In practice that means the latency of a call is the network’s, not the store’s. Operationally, there’s less to configure and fewer background behaviors to learn. Auditing is easy because the code is small. Tracing a request from socket read to map write is trivial. When something fails, it fails in a small number of explicit places. Fixed size, explicit deadlines, and a tiny command set mean the system behaves the same way everywhere. We aren’t juggling persistence modes, eviction policies, or subtle differences in server versions. The store either answers inside the deadline or it doesn’t, and both outcomes are observable and expected. Security also follows the environment, not the public internet. The system relies on a shared secret and a trusted network boundary.

Where the tradeoffs land Link to heading

I was deliberate about what I didn’t build. There is no durability; if the process restarts, the data is gone. There’s no replication or clustering; one instance serves one purpose. UDP doesn’t guarantee delivery or ordering; I accept that and designed the callers accordingly. The command set is intentionally narrow, there are no data structures, transactions, or scripts. If any of those are requirements, this tool isn’t the right fit, and that’s fine. Mature systems exist to solve those problems, and they should be used in those circumstances.

Known limitations Link to heading

These are the gaps a UDP or AEAD reader would expect me to name. They are acceptable in my environment because the network is trusted and both ends are mine.

  • Replay: encryption authenticates each datagram but nothing stops a captured SET, DELETE, or response from being sent again later. There is no sequence number or timestamp.

  • Flooding: the server takes one of its 1000 in-flight slots before it decrypts a packet, so anyone on the network can hold every slot with junk datagrams until the handlers finish, key or no key.

  • Resends: if a response is lost and the client resends a SET with both Overwrite and Old, the server executes it twice and the second response reports the first write as the previous value. Without Overwrite a resend is harmless.

Why build instead of buy Link to heading

The honest answer is that the delta between “what I need” and “what an off-the-shelf system provides” was larger than it looked. Even when the features are disabled, we still inherit their operational surface area. There is configuration to keep them off, documentation to read to be sure, and knowledge to maintain just in case. The domain didn’t justify that overhead. By writing something minimal, encrypted UDP, fixed frames, a guarded map, four commands, I ended up with software that is easier to run, easier to test, and easier to understand than any trimmed-down general solution I tried.

I didn’t build this to be clever. I built it because it was the most boring way to solve my specific problem. Boring is good. Boring is reliable.

Should you use this? Link to heading

Probably not. This is a small personal project that may not be maintained. However, if the use case looks like mine, small values, read-heavy, owned endpoints, trusted network, tight latency budgets, then a tiny encrypted UDP key-value store can be the pragmatic choice. We get simplicity, predictability, and a short mean-time-to-understanding when something breaks. If we need durability, multi-tenant security, complex data structures, or internet-facing hardening, we shouldn’t roll our own. The battle-tested tools that exist for exactly those needs are the better choice. Boring is still good, but only if it’s boring in the right problem space.

Closing Link to heading

Building my own store wasn’t about outperforming Redis on benchmarks or reinventing the wheel. It was about picking, or in this case building, the right tool for the job. By sticking to a fixed frame, enforcing deadlines, and making encryption mandatory, I built a tool that does one job quickly and predictably, with almost nothing to babysit. That’s the kind of system we can keep in our heads and that’s exactly what I wanted. And if my needs grow, migrating to Redis or Valkey later is straightforward because the client API is deliberately minimal.