Irmin Retrospective [irmin-retro]
- August 7, 2025
- Patrick Ferris
Irmin is an OCaml library for building branchable and mergeable data stores. The data is mergeable in the sense of mergeable replicated data types.
I have been using Irmin for over five years to build different kinds of interesting data stores including:
Mentoring an intern who worked on Irmin in the browser.
Most recently, an Irmin-backed shell session manager.
I was asked to provide some feedback recently on Irmin, so I thought writing a little retrospective here would be a good way to do that. The remit for the retrospective was about improving Irmin, so the content is focussed on pain points and areas of improvement.
(* An in-memory Irmin store *)
module Store = Irmin_mem.KV.Make(Irmin.Contents.String)
What is Irmin?
- August 7, 2025
- Patrick Ferris
Irmin, at its simplest, is a key-value database. Users associate keys with values and can query and update these bindings.
Additionally, this database supports versioning. This means independent snapshots of the database can coexist and users can switch between them and update them without fear of interfering with other versions.
Different versions of the database can be combined by merging. When you set up your instance of an Irmin database, you also provide it with a merge function.
# #show_type Irmin.Merge.f;;
type nonrec 'a f =
old:'a Irmin.Merge.promise ->
'a -> 'a -> ('a, Irmin.Merge.conflict) result Lwt.t
Design and API
- August 7, 2025
- Patrick Ferris
Irmin's API is very git-inspired. There is a large overlap of shared vocabulary and concepts: repositories, branches, commits, heads etc.
Probably the most confusing aspect of this is the notion of a Store. When I was describing Irmin above, I used the term database to help distinguish between some of these concepts. In Irmin's documentation, it is used for multiple related (but different) concepts. In "Creating a Store" stores refer to the entire database, whereas in the Irmin API docs we have that:
There are two kinds of store in Irmin: the ones based on persistent named branches and the ones based temporary detached heads.
For Irmin library users, a Store.t can be thought of as a checkout of the database at a particular revision. This may be from a branch or a specific commit.
# Store.of_branch;;
- : Store.repo -> string -> Store.t Lwt.t = <fun>
# Store.of_commit;;
- : Store.commit -> Store.t Lwt.t = <fun>
The overloading of the term is confusing. I think it leaves users unsure about what other people might mean when they say "store". Being careful with these terms, and the contexts in which they are used, would help avoid this confusion.
Module and Functor Soup
- August 7, 2025
- Patrick Ferris
Undoubtedly for a majority of use-cases and users, Irmin is over-functorised. Nearly every module requires that you must apply some functor to access any useful code. In general, a user will have to interact with the Schema module when describing the types they want to instantiate their store with.
To counteract this, Irmin has plenty of KV modules that provide a Make functor that only requires a user to provide a suitable content module for their store (i.e. something that provides a type, a runtime representation of that type and a merge function).
# #show_module Irmin_mem.KV;;
module KV :
sig
type endpoint = unit
type metadata = unit
type hash
type info = Store.info
type ('h, _)contents_key = 'h
type 'h node_key = 'h
type 'h commit_key = 'h
module Make : (C : Irmin__.Contents.S) ->sig ... end
end
I think it is fair to say the documentation is hard to follow as it is a module and functor soup. For example, looking at irmin.3.11.0, starting at the toplevel documentation page our path to finding this paricular module and functor is as follows:
We jump into
Irmin_memfrom the nicely written landing page.We scroll down to find the KV module.
We now make sense of the module's signature by following the KV_maker link.
We see a
Make (C : Contents.S) : sig ... endat the end of the module, and we navigate through that.Finally we have come to our journey's end, and find the schema module with type constraints like
type Branch.t = string.
There is a big assumption there that you know what the Schema module is telling you and how it relates to your "store".
There is a counter-argument here, in that Irmin is incredibly flexible in terms of what you can use as types for your keys, branches, hashes etc. I was quite easily able to produce Irmin stores that use CIDs (self-describing content identifiers) for example.
I think there is a middle ground. For Irmin to be usable it needs to reduce the complexity of the API. The complexity is due to, in large part, over-functorisation. If the functors cannot be removed then perhaps better documentation or more introductory libraries like irmin-containers would be helpful. Perhaps a standalone library that acts as an interface to Irmin stores would be helpful. I find myself time and again implementing something like that.
Backends Galore
- August 7, 2025
- Patrick Ferris
Irmin has plenty of backends including a git-compatible one, a MirageOS backend, an in-browser IndexedDB backend and even a FoundationDB backend.
Irmin needs to have fewer, better tested backends. Of course, some of these are likely driven by funding and use-case specific details. However, I think by focusing on a few key backends and striving for solid performance but also strong consitency guarantees, Irmin would become a more viable candidate for potential users. I also believe the browser backend plays a key part to making this work in order to facilate local-first applications.
Syncing Remote Stores
- August 7, 2025
- Patrick Ferris
The synchronisation mechanisms in Irmin are powerful, but often the API is very confusing. For example, with the git-compatible Unix backend, nested deep in the documentation is the function required to create a remote endpoint. Once they have found this function, using it is not easy as it requires them to learn about the Mimic.ctx, which is an abstraction of the networking stack!
Do Fewer Things and Do Them Well
- August 7, 2025
- Patrick Ferris
Irmin is simultaneously a database that supports: content-addressing, merging, key-value lookup, branches, transactions, complete OS portability etc. I think, in short, it tries to do too many things and comes up short on some of them in ways that really matter.
When building the content-addressed database I needed strong guarantees about some atomic actions to perform on the underlying store. For example, setting the value and accessing the commit associated with it which was not possible with the API at that time in an atomic way. This kind of missing functionality is probably only understood after users stumble across it, but it also makes the library feel less focused and ready for production use-cases.
Documentation
- August 7, 2025
- Patrick Ferris
It will probably come as no surprise that one of the main limitations of using Irmin is the lack of documentation and tutorials. This is a particular pain point as the API is not very straightforward.
A while back, I started an Irmin book intending to help document the kinds of things real-world users would need in order to use Irmin in earnest. For example: ways to deal with type migrations or primers on runtime types. I still believe this sort of work would be invaluable, but there is a large cost to completing it, and it is not clear if there is enough interest in the project to warrant such an effort.
The tutorials on the irmin website are still good starting points for most new users. But they quickly lack the depth for real-world scenarios.
Feature Wishlist
- August 7, 2025
- Patrick Ferris
What follows are additional ideas for improving Irmin.
Heterogeneous Stores
- August 7, 2025
- Patrick Ferris
For all of [Irmin]'s abstraction and functorisation, there is a very clear missing feature for lots of first-time users of the library: you can only store values of a single type.
This leads to a few common workarounds:
Storing a serealised version of your values, essentially escaping the type-system and implementing a form of dynamic typing.
Growing your value to hold lots of different types via some large variant type.
Both of these options are feasible and have been used in practice. However, they are workarounds. A long time ago CraigFe created an RFC for heterogeneous stores, which I think about a lot. The main idea is to augment Irmin paths (keys) to be GADTs that carry type information about the kinds of values they access (similar to heterogeneuous variants of other data structures). Something like this alongside a simplified API would make Irmin more appealing as a library for persistent data storage.
Real-world Retrospectives
- August 7, 2025
- Patrick Ferris
This echos some thoughts from the Documentation section. There are some very real-world Irmin use-cases out there. For example, for a long time (I'm not sure if this is still the case) parts of the Tezos blockchain were using Irmin which uses the irmin-pack backend. Does this make irmin-pack the best tested backend and perhaps should be the default backend for new users? Or is it hyper-specific to the Tezos use case? There are blogs on tarides.com about the irmin-pack backend, but they might be outdated and should not be the first place to find advice on which Irmin backend to use.
Active Development and Engagement
- August 7, 2025
- Patrick Ferris
As far as I know, Irmin was at the forefront of technologies that have come to be described as local-first. There is a growing interest in this area (particularly as it acts as a counter-argument to an increasingly online, centralised model). I highly recommend reading Ink & Switch's essay on the matter. And it would be great to see more research via Irmin at things like the lofi unconference and the PLF workshop at SPLASH 2023!
Thank you for reading! And thank you to all the Irmin contributors. I hope this might be useful in the future for building the next-generation Irmin!