nika-industries / nika

An affordable bring-your-own-storage Nix binary cache service
GNU Affero General Public License v3.0
0 stars 0 forks source link

KV Store Transition #16

Closed johnbchron closed 1 month ago

johnbchron commented 1 month ago

I've decided to switch to a key-value store instead of SurrealDB.

Why Not Surreal?

Surreal adds a decent amount of complexity even if you don't use their fancy features, which is fine, except:

  1. They don't have strict enough "correctness" policies to constrain their behavior, and they have a lot of behavior that needs constraint.
  2. The more complexity you have and the less "correctness" constraint you have, the more your bug attack surface increases.

Another piece of the puzzle is that Surreal is a large codebase broken into only two crates, and the most basic form of it requires an additional 350 dependencies. You have to use weird serialization techniques to involve types of theirs -- which are required to build a decent model paradigm -- without bringing in all 350 deps and also invalidating WASM interoperability.

The Desired Solution

I'd like to have a solution that:

  1. Is fast.
  2. Is generic on the actual store.
  3. Adds (ideally) less than 40 third-party crates.

At the moment, I'd like (the ability) to support Redis and TiKV. TiKV is mainly focused on transactions and scaling, and has a smaller features outside of that. Redis handles more advanced usages but doesn't scale optimally beyond a single node.

Design Constraints?

There's sort of two "interface constraints" at play here.

The first is that the primitive operations we choose to support need to be a rough subset of the intersection of all the features of the backing stores we intend to support, which means we need to pick primitives common to our backing stores. We can step over the bounds a little bit if necessary by manually implementing store features.

The second is code reuse. We currently have a db crate that contains all of the Surreal access we perform. This is a system boundary, and a good one, because it means we could theoretically perform no more changes to our code than to adapt the db crate to whatever we're using. We're going to build more than that, but if we choose to maintain the db "interface", we have the opportunity to not change any application code.

Again, in practice, if the performance characteristics of the methods in the db crate change significantly, we may choose to adapt the shape of the db system boundary, but it's not required for the platform to function.

Implementation Plan

I want to add two pieces.

The first is a "primitive" interface for our backing stores. This will contain things like "get", "set", "optimistic transaction", etc. and will be implemented individually for each backing store.

The second is an "extension" interface. This will be for advanced "store-level" features and also larger first-party operations (like dealing with indexed collections), but everything it includes needs to be possible to implement with only the primitives. It will have default method implementations, with a bound for the primitive interface. This will let us manually override the implementation when the backing store has direct support.

Operations

I want to add a kv crate that contains the (private) store clients, the KvPrimitive trait, the KvExtension trait, and the implementations. We should test as much as possible here, similar to the per-implementation kraglin testing. We'll use features to gate implementations and keep the dependency count down.

We'll remove the ssr stuff from the core_types crate and just use plain Ulid as IDs everywhere.

We'll remodel the db crate to use the kv crate. There will be a bunch of operations meant for SQL-like databases that we'll have to add more logic than we had before for, and any time that comes up, I want us to generalize those operations and put them in the KvExtension crate.

We'll also propagate the kv feature gates to the db crate. We may want to use the database from a cloudflare worker.

The system boundary here is that we'll never use the kv crate by itself to perform database operations, and instead we'll add that logic to the db crate and use that.

Final Thoughts

I'm purposely not discussing how we'll bridge the gap between KV and SQL-like, because that involves a number of decisions that I haven't finalized yet.

johnbchron commented 1 month ago

This was closed with #17