On behalf of the RabbitMQ team, I’m proud to release Khepri 0.6.0! It follows Khepri 0.5.0 which I forgot to talk about here. Therefore I will cover both in this post.
In Khepri 0.5.0, we focused on refining the public API again. It was already the topic of version 0.3.0 but I was still not convinced by the result.
This time, the khepri
module exposes functions which should be straightforward and boring to use. Things like getting a value from the store should be mlore obvious now. Return values of khepri:put()
and khepri:delete()
are very simple too now (they just indicate if the operation succeeded or not).
Here is an example:
ok = khepri:put(StoreId, "/path/to/tree/node", Value),
{ok, Value} = khepri:get(StoreId, "/path/to/tree/node").
the khepri_tx
module exposes the same API as khepri
, but for transaction functions. If an API is missing in khepri_tx
compared to khepri
, it is either because it doesn’t make sense in the context of a transaction, or it is a bug.
To get more details for returned tree nodes, there are now “advanced” modules: khepri_adv
and khepri_tx_adv
. They are the advanced counterpart of khepri
and khepri_tx
respectively. They only exports functions which have an advanced use case, not all of them.
Error handling was improved as well:
- Functions which have room to return an error in an
{error, Reason}
tuple return errors for situations where the caller is expected to handle failures. For instance, a tree node doesn’t exist or there is a timeout with the underlying Ra cluster.
- When an error happens because of a misuse of the library, an exception is thrown using
erlang:error()
.
- All error and exception reasons have the same form:
{khepri | khepri_ex, Name, Props}
. The khepri
or khepri_ex
atoms help to distinguish errors and exceptions from Khepri from other sources. Name
is the actual reason and Props
help qualify Name
further.
There is still room for improvement in this error handling, like documenting possible errors (including in function specs) or providing a function to format errors for human beings.
The release notes of Khepri 0.5.0 give a before/after example which should help understand this breaking change in the public API.
In Khepri 0.6.0, the focus was put on new features:
- The ability to import and export a store (or a part of it). I implemented @LeonardB’s idea of using Mnesia’s backup and restore backend API. The library itself comes with a single backend module which uses a plaintext file with formatted opaque Erlang terms in it, but we plan to add more as separate Erlang applications to not add too many dependencies to Khepri itself.
- A new mechanism called “projections” to cache data for fast queries with a low latency. This relies on ETS tables: queries are indeed fast, at the cost of an increased memory footprint and eventually consistent responses.
- New
maps:fold/3
-like functions. We have khepri:fold()
, khepri:foreach()
, khepri:map()
and khepri:filter()
. The same functions exist in khepri_tx
for transactions. We should probably add khepri:filtermap()
, perhaps more, in future releases.
- The ability to use stored procedures as transaction functions. This is an important addition because it allows to “pay” the price of function extraction once. This makes transactions faster and consume less memory.
Again, the release notes go in greater details about these new features and even more changes I didn’t repeat in this post.
We have a few more improvements in mind — how we handle errors as mentioned above, how frequently we perform snapshots, an alternative to global
locks, etc. — but we are close to a beta for this library. I mean, the API should see less breaking changes in the near future.
Did some of you already try to use Khepri? I would be super happy to know what you think