What is kivo?

kivo is a lightweight, Redis-compatible, in-memory datastore written in Go. It started as a weekend experiment, and over a few weekends turned into a genuinely usable option for most of the caching and storage needs Redis covers. It is not distributed - think of it as an alternative to Redis standalone. If it sounds useful to you, give it a try - github.com/itsmunim/kivo

How it all started

One fine weekend evening, I was working with a NestJS application for one of my pet projects. I needed a very small, expirable state to be persisted in memory. Initially I kept it in the service code itself - whenever one instance updated it, it broadcast a message for the others to update their copies too. It soon turned into a nightmare: mutations were not getting received, and discovering healthy instances meant keeping a registry on each service side, which then needed syncing and so on. It naturally came to me that I needed a separate key-value / memory store running alongside my service instance - and the de-facto standard for that is Redis.

But Redis comes with a lot: all the advanced functionality, RDB snapshotting, and so on - easily taking up quite a bit of resources. And here's the thing - the whole setup was running inside a VM, with 3 service instances running with docker compose and an nginx in front of them. My VM was already running at almost capacity; the last thing I wanted was one more component eating significant resources. That's when I asked myself - what if I create a tiny, Redis-API-compatible (i.e. RESP) cache component myself, just for my use case? And with the essence of AI these days, my reaction was essentially - why not?

A minimal cache or a key value storage

The first thing I wrote was a single-file service in Go - a parser for the RESP input/output and data saved in a map. That was obvious, since I only needed key and value, nothing else. I added an expiry system too - quite basic: while looking up a key, if the stored time is older than its expiry, we return an empty result. No background expiry mechanism, more like on-demand, runtime decision making.

It worked well, but soon I had values like list to store, so I ended up implementing a basic one using a slice. Expected functionalities worked. And with AI helping, it took roughly a few hours of back-and-forth to get it right, with all the tests in place.

What if - it could do (part of) what redis can?

I forgot about this just like I forget so many other pet projects I start and never finish. Then I came across the news of how Dragonfly is using multi-threaded architecture compared to Redis, and claimed higher throughput. That seemed interesting, and it clicked immediately - what if I try some other stuff on my Redis clone, but this time supporting all the core commands it has?

I deliberately decided not to provide pub-sub, RDB snapshotting, sharding, clustering etc. Just a single node setup like Redis standalone, with AOF persistence for reliability.

I spent another weekend building out the necessary Redis capabilities - strings, sets, sorted sets, hashes, lists. As I was building each command set, I was testing on my own and with unit tests generated by my agent. While doing that, it hit me: for testing Redis-compatible stores, people always need redis-cli installed - what if there was a web console accessible from the browser, shipped with the server itself? Sounded interesting, so I noted it down as a feature I would like my store to have.

After a couple of days of development, I was happy with what I had. By that time, I had started thinking - if it performs well, I would open source it. Even if someone could go through the same learning I had, or use it for caching/kv-store use cases, it would be worth it. For that, it needed to perform well enough to hold its own against Redis standalone.

This is when I decided to come up with a name and push all the changes in one commit under a repository. I was brainstorming with ChatGPT, and that's when it struck me: I wanted a KV store, and fast enough. If you pronounce KV really fast, it starts sounding like kivo. Well, that's how the name stuck.

Benchmarking begins

Now that I had a working standalone kivo, naturally I wanted to benchmark it against Redis standalone for the same functions. redis-benchmark is quite handy - it comes with your Redis installation - so that's what I decided to use.

Initial benchmarking showed good results: it was close to Redis throughput (ops/sec) for most commands, except LPUSH.

LPUSH was ~4.2K ops/sec for kivo vs 75K+ for Redis. The reason: at the naive stage, lists were a plain Go slice - RPUSH (append) is amortized O(1), but LPUSH copied every existing element to the right as the list grew, so it was O(n) per push and O(n²) overall. Profiling confirmed it. I went with a ring-buffer deque - O(1) on both ends. LPUSH immediately jumped to ~77K ops/sec, almost matching Redis. A huge win.

There is an honest catch in the recent benchmarks though: kivo uses 2.2x more CPU and 3.3x more memory, measured over 3 rounds in a linux/arm64 docker setup against the same Redis standalone. Do I stop here? Not really - I'm profiling what can be improved, and the next version will definitely be better. That's possibly my next post, when I manage to improve it.

Read benchmark.md for all the details behind the progressive improvements, profiling, etc.

The obvious - why not Rust?

I have done Go before, so the bias was there. At the same time, I was looking for a balance - fast enough, gets my job done well, and I can iterate fast. To tell the truth, the idea of making it open source and a properly performing Redis cache alternative came to me at a much later stage. And since then it has become quite a great hobby work for me - different challenges to solve, different things to add every time. Back to the fun of actively developing something.

I definitely thought about using this to learn Rust, but there were a few caveats which pushed me the other way. At the end of the day, Redis is just one big in-memory state which is mutable. A single map initially gets the job done - simple, efficient. Rust's ownership model fights the architecture: you either accept coarse locking, pull in external crates, or redesign the whole concurrency model. That's valuable learning, but it adds friction when the goal here is to build and ship, not to master memory ownership patterns.

There are a bunch of other reasons I explored with the help of my AI agent - you can find them in the kivo v1 design doc. There are so many great distributed systems built using Go; if anything, it would be fun to face certain blockers and figure out how to solve them in a language I have good familiarity with, and with decent performance. The prototyping speed definitely mattered a lot.

Last notes - learnings so far

AI agents have made development really easy. As a manager I do not actively code at my workplace, but I have always experimented with different AI workflows for all these pet projects.

A few learnings, if they help you -

  1. Always define your guardrails ahead of development - what you expect the AI to do, and how, should be well written as part of requirement spec or some steering rules.
  2. Always define what behaviours you want to test, and definitely review the test scenarios.
  3. All the basics - data structures, algorithms, distributed system nuances - are even more important now. Depending on what you are building, you won't be able to differentiate certain suggestions from AI without that knowledge. Or you can't even explain your agent what is your focus. You have to clearly understand the tradeoffs of different directions. Go deep into things and understand how they may change what you expect. Without the depth, you won't be able to deal with them.
  4. AI is enabling us differently - all our crazy ideas and things we wanted to try can now become a reality over a few weekends. This is a great time to live in, to build and learn.
  5. Producing code has always been the part of our work which is like glorified typing. Now that the burden of figuring out syntax, a library interface, SDK etc. is gone, you can spend time where it is really required: to plan what you want, how you want it, what are non-negotiables and what are not - and also to review the artefacts to ensure security, reliability and quality is not compromised. The responsibilities are different, and these are the right ones for software engineers. Thinking has always been the expensive one.
  6. For best outcome, use a powerful model with thinking power to work with you as an architect of the whole solution, and dump an implementation plan. Make sure each task in the plan is small enough, then use less powerful models as working agents to execute different non-competing portions in parallel. Create tester personas for covering enough use cases without bias.

Closing note

If you need caching, or simply a kv store, or any use case that matches what's mentioned here, feel free to give it a try. Even if you don't have a use case right now, testing it out using the web console or simply redis-cli would be a help.

If you have any questions or suggestions, feel free to raise an issue here - https://github.com/itsmunim/kivo/issues - or leave a comment below.

The code is really, really simple, so reading it should be fun. And if you liked the work, don't forget to hit a star :)