It's about time that I did this, all these years of cobbling together and duplicating this code and now I've formalized it into a crate!
So, if you've been following my coding adventures, you know I'm a big fan of Rust. And like many developers, I often find myself needing a fast, reliable local database. Enter RocksDB – it's powerful, battle-tested (used by giants like Meta!), and integrates nicely with Rust thanks to the rocksdb
crate.
But... here's the thing. While RocksDB is great, using it directly often involves a bit of repetitive boilerplate. Every time I started a new project needing key-value storage, I found myself writing the same kind of code:
- Figuring out the
Options
struct again. - Writing serialization/deserialization logic (turning my nice Rust structs into
Vec<u8>
and back). - Carefully handling transactions to make sure multiple operations were atomic.
- Setting up batch writes for performance.
- Maybe even implementing custom merge logic for things like counters.
It wasn't hard, exactly, but it was... tedious. It felt like I was reinventing the wheel (or at least, the same five spokes) every time, pulling focus away from the actual application logic I wanted to build.
Enough was enough!
Introducing RockSolid 🗿
I decided to consolidate all that common code, best practices, and helper patterns I'd developed into a new Rust crate: RockSolid.
The goal wasn't to hide RocksDB entirely, but to provide a more ergonomic, opinionated layer on top of it, making the common stuff really easy while still allowing access to the underlying power when needed.
What does it do?
- Handles Serialization: You give it your Rust structs (that derive
Serialize
andDeserialize
), and it handles turning them into bytes for RocksDB using efficient formats (MessagePack for values, bytevec for keys). No more manualserde_json::to_vec
or similar scattered everywhere. - Simplifies CRUD: Basic
set
,get
,delete
, andexists
operations become one-liners. Getting a value? Juststore.get("my_key")?
and you get back anOption<YourStruct>
. Sweet! - Makes Transactions Less Scary: Need to update two keys atomically? RockSolid offers a
transaction_context()
that lets you stage operations clearly and handles the commit or rollback for you. It feels much more natural:// Pseudo-code vibe let mut ctx = store.transaction_context(); ctx.set("key1", &value1)? .set("key2", &value2)?; // maybe check something... // if error { ctx.rollback()?; return; } ctx.commit()?; // All done atomically!
- Easy Batching: Need to write a bunch of keys efficiently without a full transaction? The
BatchWriter
lets you chain operations together and commit them in one go:// Pseudo-code vibe store.batch_writer() .set("key_a", &val_a)? .delete("key_b")? .set_raw("raw_key", b"bytes")? .commit()?;
- Merge Operations & Routing: Need atomic counters or set unions? RockSolid supports RocksDB's merge operators and even includes a nifty "Merge Router" so you can define different merge logic based on key patterns (like
"/counters/*"
vs"/sets/*"
).
Why Bother?
For me, it's about reducing friction and cognitive load. I want to think about my application's domain, not the intricacies of byte arrays and transaction lifecycles every single time. RockSolid lets me get data in and out of a persistent store quickly and safely, using familiar Rust types and patterns.
It's still built on the solid foundation of RocksDB, so you get the performance and reliability, just with a friendlier face.
Check it Out!
I've open-sourced RockSolid under the MPL 2.0 license. It's still evolving, but it's already saving me time in my own projects.
If you find yourself doing the RocksDB boilerplate dance in Rust, maybe it can help you too!
You can find the code, more detailed examples, and documentation over on GitHub:
Feel free to take a look, kick the tires, open issues, or even contribute if you find it useful. I'd love to hear what you think!