christhekeele

christhekeele

Mnemonix

After a couple years of playing with the concept and learning my way around ever-evolving idiomatic Elixir and venerable OTP patterns, I’ve finally sat down and implemented an Elixir library with an architecture I’m pleased with.

Rubyists might be familiar with Moneta; this is my Elixir approach to that. Mnemonix is a standardized Map-compatible API for interacting with key-value stores in an implementation-agnostic way.

The intent is to define an abstract API a key-value store should offer, and iron out discrepancies between implementations and Elixir wrappers such that they all comply and behave predictably behind it.

If you’re developing an application, it lets you trivially experiment with different backends to find the right implementation for your use-case, and unifies access to different backends chosen for different reasons behind a common, familiar API.

If you’re building a library, it lets you defer the implementation decision of what key-value store to support to your end-users.


Current State

As of v0.7.1, I’m beginning to move towards optimization and better error reporting. The public API has now stabilized and only new capabilities will change public function signatures.

Currently, it offers parity with the Map module, and builds upon that with extra functions for common key-value store needs: incrementing and decrementing integer values, and setting explicit keys to expire after so many seconds.

It supports several backends:

  • Map
  • ETS
  • DETS
  • Mnesia
  • Redis
  • Memcache

I intend to improve the existing backends, add a few more, and add a few more features to all stores before v1.0.0.

Check out the documentation to learn more, let me know of any issues you run into!

Showing Posts 1 to 10

Qqwy

Qqwy

TypeCheck Core Team

This a wonderful idea. I am definitely going to use this library in the near future. :smiley:

swelham

swelham

This looks really cool :thumbsup:

jxxcarlson

jxxcarlson

I used Mnemonix tonight to solve a problem I had with the app I am working on. Mnemonix is a really useful and elegant tool. I put this code

{:ok, store} = Mnemonix.Store.start_link({Mnemonix.Map.Store, initial: %{active_notes: []}}, name: Cache)

in the APP_NAME.exs file and within two hours the job was done. Three cheers for Mnemonix!

jxxcarlson

jxxcarlson

Hi Chris, some info about my use case, of which there are two, both in Phoenix – one present and one future.

  1. Lookup.

This is an app for storing short snippets of information – from things like the speed of light and Planck’s constant to links to various things I find on the web: political articles, github repositories, articles about code, you name it. Just two fields: title and description. URLs in the description field are rendered as links, e.g, http:foo.bar.io/yada/yada?baz=123 is rendered with link text “foo.ba.io”.

I use Mnemonix at the moment for just one thing: to record a list of IDs of records in the database result from the most recent relevant operation – search, create, edit, etc. In the case of deleting a record, the list is the empty one. This is needed to give the user a pleasant and efficient experience.

I plan to add users and authentication and then put this up on the web for all to enjoy. It will also help me, since I will then have access to my data anywhere there is a connection. I plan to use Mnemonix to hold the user’s JWT authentication token, the id list, and maybe more, e.g, preferences.

The code is at GitHub - jxxcarlson/lookup_server: A Phoenix server for Lookup · GitHub

  1. Rewrite the backend for Manuscripta.io:

http://www.manuscripta.io/

Manuscripta is an app for creating, editing, and distributing lecture notes, although it can be used for other things. For writing techical docmentation (code, math, physics), it uses Asciidoctor-LaTeX (GitHub - asciidoctor-contrib/asciidoctor-latex: 📐 Add LaTeX features to AsciiDoc & convert AsciiDoc to LaTeX · GitHub), of which I am the principal developer.

Here is an example --my course notes – http://www.manuscripta.io/documents/jc.qft?toc

I am a refugee from the Rails world. I used Rails for the first version of Manuscripta, but (a) it was too slow, (b) the code got completely out of control. Partly my fault. It was my first real software development experience. Subsequently I split the app into a REST back end written with Hanami, a Ruby web framework which I really like (http://hanami.org). The current front end is written in Angular1 and I have an Angular2 version in the works. But I love elixir/phoenix and have found the functional programming style to my liking (I used to program in Scheme some time ago). Hence the plan to redo the backend in Phoenix. (1) Above was my starter project to learn Elixir and Phoenix. (I also wrote a command-line version of lookup – GitHub - jxxcarlson/Lookup: Command line program (Elixir) to create and search for records of the form [title, note] · GitHub)

One challenge (for me) of the backend project is that Phoenix will have to communicate with other processes: ruby for rendering Asciidoc-LaTeX documents into HTML and/or LaTeX, and for converting (when necessary) LaTeX docs to PDF.

I plan to rewrite the front end in Elm, but if you have thoughts on front end technologies, I am interested.

This is probably way too much info, but there you go!

PS – I’ll comment on docs, etc. tomorrow. Late here in Ohio!

christhekeele

christhekeele OP

For those interested in this project, I’ve just published Mnemonix v0.6.2!

v0.6.1 represents a stabilization of the public interface. Functionally, it’s identical to v0.4.0, aside from adding Mnemonix.put_and_expire/4. However, the internals have been completely re-arranged to use less meta-programming, better module names, and have become much easier to maintain and test.

Consequentially, if you were calling Mnemonix.Store.start_link, you’ll need to update your code to use Mnemonix.Store.Server.start_link with slightly different parameters, and all stores now live under the Mnemonix.Stores namespace, like Mnemonix.Stores.Map. Otherwise everything should function identically.

These breaking changes have enabled a few benefits:

  • Navigating the documentation is much easier through better namespaces, and internals have been hidden.
  • The functions available on Mnemonix have been split up into different Mnemonix.Features docs for simpler browsing of related functions.
  • There are quite a few new ways to manage stores beyond the humble store server:
    • Bring up and manage multiple stores with the new supervisor
    • Describe stores in your application config and have them start alongside your application without writing a line of code
    • Define your own module to interface with stores by using the builder macros

The only reason why this stabilized interface isn’t a v1.0.0 is because I want to co-ordinate an announcement around that release and would prefer having more goodies implemented.

Until then, I’ll just be adding more tests, more features, more stores, and improving performance and error handling around existing ones. Update to v0.6.2 and your upgrade path shouldn’t contain breaking changes until a far-off v2.0.0 release!

codecakes

codecakes

Amazing! Integrates with Memcache and Redis? On my list to use!

christhekeele

christhekeele OP

v0.6.3 contains experimental support for using Mnemonix stores to back Plug sessions. Try it out in your Plug-powered apps and let me know if anything breaks!

christhekeele

christhekeele OP

v0.7.1 transparently* serializes and deserializes all keys and values in and out of erlang’s external term format for every operation.

This means that you can, true to Elixir’s maps, use any term as a key or value for all Mnemonix operations, including the features beyond Map (currently increment/decrement, expire/persist). Some out-of-memory stores like memcachex threw a fit if your keys or values weren’t strings or atoms; no more.

This also allows you to provide any store type (not just a Mnemonix.Stores.Map) with an :initial map of entries to populate the underlying store with. Each entry of the map is simply serialized and sent to the store before the store becomes available.

This incurs about a 4% performance penalty to common store operations for out-of-memory stores, but that should be mitigated by all the low-hanging out-of-memory-store optimization I keep putting off. In-memory stores skip serialization/deserialization.

* Transparent serialization/deserialization means that:

  • Mnemonix users don’t need to know about it, even error messages come out deserialized.
  • Mnemonix store developers don’t need to know about it, they don’t have to implement anything or reference the implementation in their store’s callbacks.
    • However, serialization/deserialization logic is an overridable callback so store developers can optimize it if they wish.
    • This is how the in-memory stores avoid the external term format serialization.
    • But no other store callbacks in Mnemonix’s architecture requires knowledge of the serialization/deserialization mechanism.

Really, I’m running out of cool things to implement–I might even get around to those optimizations soon. Although I do have a new programmable mechanical keyboard arriving tomorrow, so all bets are off.

christhekeele

christhekeele OP

Just released v0.8.1!

The minor bump from 7 to 8 is due to a small breaking change: the :transaction option for Mnemonix.Stores.ETS has been renamed to :concurrent to much more accurately reflect the ETS table option it corresponds to.

The teeny bump to from 0 to 1 is because there was a bug with the singleton builder.

Analogs for Map.drop/2, Map.split/2, and Map.take/2 have been added to the Mnemonix API, and to all stores.

The first outside contribution to Mnemonix (thanks @rzane and @coderrick!), a new Mnemonix.Stores.Null store has been added. This makes it trivial to test against a no-op Mnemonix store!

The star of the show is the experimental release of the Mnemonix.Features.Enumerable API. The v0.8 line will continue to experiment with this capability.

The Enumerable feature is interesting for three reasons:

  • It’s the first optional feature.

    Only Map, ETS, and Null stores currently support the Enumerable functions. Interleaving the priorities of a unified Mnemonix API, default implementations for all but the most essential behaviours (fetch/2, put/3, delete/2), per-implementation overrides, and in-caller error raising were all at odds with optional features, but I found a decent way to do it. This opens the door for other Mnemonix.Features that are only applicable to a subset of stores.

  • It fleshes out the Map API.

    The Enumerable API allows the supported stores to iterate over their key/value pairs, which ushers in analogs for Map.equal?/2, Map.keys/1, Map.to_list/1, and Map.values/1. This is handy if you’re using Mnemonix explicitly for in-memory caching.

    It also means only 4 remaining map functions are unsupported: Map.merge/{2,3}, (which I’m not sure make sense), Map.from_struct/1 (which will never make sense in Mnemonix), and Map.size/1 (which is set to be deprecated in Elixir 2.0).

  • It brings us closer to protocol implementations.

    Both Mnemonix.Features.Enumerable and Mnemonix.Store.Server now support the calls necessary to power the Enumerable and Collectable protocols, there’s just no struct for the defimpl to latch on to. This would involve transitioning from a PID/GenServer based Mnemonix interface to a struct-based one, and I’m not yet sure how I want to do that for v0.9.0. But now there’s a motivation to explore that.

Thanks for watching, and stay tuned for more!

christhekeele

christhekeele OP

I finally found some time to pick this back up again, after a busy few months. v0.9.0 is now released! It’s another small public API change:

  • The start_link functions on Mnemonix.Store have migrated to the main Menmonix module.
  • The Mnemonix.Store.Supervisor module has been renamed to Mnemonix.Supervisor.

These changes are in service to finally fully hiding the Mnemonix.Store namespace as a private API, making usage of v0.9.0 and above much more resilient to API changes in the future.

Furthermore, the start_link functions in Mnemonix are now furnished by the new Mnemonix.Features.Supervision module, which is also used in the Mnemonix.Builder module. This means if you’re creating your own store modules, you no longer need to implement start_link yourself.

Finally, all functions provided by the Mnemonix.Builder are overridable, meaning if you are fluent in the Mnemonix feature APIs you can redefine them for your custom store modules yourself. This also means if you already implemented start_link yourself you do not need to make any changes.

I know it’s more bookkeeping than exciting features. However:

  • It unifies almost all Mnemonix functions and grants them to custom stores. That work should be complete with a final addition of setting a default adapter in the builder, allowing the new functions to move into their own feature and make their way into custom stores. This will be a backwards-compatible change.

  • It finalizes the module namespaces for the project’s initial v1.0.0 release, allowing me to make some of the really fun optimization changes with confidence behind Mnemonix.Store.

Also up for the next release is adding warning infrastructure to the private Store API, allowing store implementations to choose to support a feature, even if it is unwise, but emit warnings in the caller when it does so (rather than just having the option to continue or raise). This is in service to allowing stores that you probably shouldn’t enumerate over to be enumerable none-the-less.

Where Next? Top

Trending in Announcing Top

woylie
Flop is an Elixir library that applies filtering, ordering and pagination parameters to your Ecto queries. offset-based pagination with...
New
MRdotB
I needed to reuse React components from my Chrome extension in my Phoenix/LiveView backend. I noticed that for Svelte/Vue, there are live...
New
woylie
I released Doggo, a collection of unstyled Phoenix components. https://github.com/woylie/doggo Features Unstyled Phoenix components....
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
anuaralfetahe
Hello Published a new library - ProcessHub! ProcessHub is a library designed to manage process distribution within the Elixir cluster. ...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New
sergio
It’s not that it’s vocabulary is too advanced. It’s something worse. I get lost trying to follow even a paragraph written by Claude. It’...
New
AstonJ
This showed up on my feed.. anyone heard of it? Just hype? Ox Alpha is a reasoning model designed for coding, sustained ag...
New
bartblast
Hey folks, I just published a post about Hologram’s funding and where the project goes next - the short version: Curiosum as Main Spons...
New
sorenone
Today we’re releasing Oban for Python. Not an Oban client in Python. Not a pythonx wrapper embedded in Elixir. Nope, it’s a fully operati...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews