Qqwy

Qqwy

TypeCheck Core Team

PersistentGenServer - Persist Your GenServers!

PersistentGenServer

PersistentGenServer makes your Generic Elixir (or Erlang) Servers Persistent!

It is currently in a relatively early development stage (I’d say about 75% towards a stable release).
As such, it is not yet available on Hex.

Example

At its essence, the only thing you need to do to make your GenServer persistent, is to replace the line

GenServer.start(YourModule, list_of_arguments, maybe_some_options)

with

PersistentGenServer.start(YourModule, list_of_arguments, maybe_some_options)

What does it do?

A Persistent GenServer will behave just like a normal GenServer, with the following differences:

  • It will keep track of the state changes that are the result of every call/cast made to it, and store these in the configured persistence layer (Which implements the PersistentGenServer.Storage behaviour)
  • It will automatically stop after a specified timeout.
  • If it is called after it has been stopped, a new server with the last persisted state will be started.
  • Only in the case of a normal shutdown, will the process’ state be removed from the persistent storage. As such, many kinds of crashes will result in the process being restarted with the last state before the crash.

Why is this useful?

Many systems are more easily modeled as a bunch of (potentially communicating) state machines, rather than modelling them around a relational database.

As a simple example, consider a (long-running) game: Handling player inputs is something that is much more natural in a state machine than it is with wrapping a database. However, we do (1) want to persist what the player does, but (2) not keep a GenServer running for all players, since only a fraction of those is playing at any single given time.

After having encountered about three situations in which I was re-implementing a very similar ‘persistence’ layer for my GenServers, I decided to extract this logic, and make it more general by hiding the persistency logic as much as possible from the user of the system.

How does it work?

Internally, a special process registry (which wraps the Elixir.Registry by default, but can use any other registry you desire as well) keeps track of which processes are currently started, and which only exist as data persisted to disk.

You do not receive a PID as response from PersistentGenServer.start. Rather, you receive a symbolic PID that includes all information to start the server again at a later time. (Essentially the arguments to PersistentGenServer.start).

Configuration

PersistentGenServer is configured through Specify and as such can be configured in many different ways, and multiple differently-configured variants of the PersistentGenServer can run alongside each-other.

To Dos before stable release

  • Stateful property tests to make sure there are no race conditions.
  • Swap out process registries that PersistentGenServer.Registry wraps.
  • Let users choose between: (temporary/transient/permanent)
    • Wipe persistency for GenServer when it stops normally or crashes.
    • Wipe persistency for GenServer only when it stops normally.
    • Even restart GenServer from persistency when it crashed before.
  • Other storage adapters.
  • Timeout length before a process petrifies itself.
  • Configurably, only write to cache on terminate vs during each handle_* for efficienty vs fault-tolerancy?
  • A mapping function between the actual state and the state-to-be-persisted/reloaded, to hide ephemeral parts.
  • Improving the documentation and examples.

Installation

PersistentGenServer is not yet available in Hex, and as such can be added to your deps as follows:

{:persistent_gen_server, git: "https://github.com/Qqwy/elixir_persistent_gen_server"}

I very much look forward for feedback from the community to see in which ways you would like this to be developed further :slight_smile: .

Most Liked

hubertlepicki

hubertlepicki

Do you intend the “save” to be always automatic? I think that’s going to be problematic to a lot of people who will point out that GenServer crashes for a reason, and that reason is usually bad state. GenServer should recover from last known good state which often is blank state or a state restored from database that provides strong data integrity guarantees.

I don’t necessarily think the above myself, as I can definitely see the usage examples where the above is a good fit. But I do fear people will abuse it, and then end up with GenServers that have persisted state, crash, restart, load up bad state and crash again - for ever. Or did you think of tackling that issue somehow?

Qqwy

Qqwy

TypeCheck Core Team

Work on this slowly but surely progresses…

Today I have added the possibility to specify a transformation_function that you can use to alter the state that your GenServer has before it is persisted. This is for instance useful if parts of the state actually ought to be ephemeral.


One of the things I am currently conflicted by a little is configuration: Currently the configuration of the GenServer is part of its identity. It is set when the GenServer is started, and is part of the symbolic PID that is passed around. That is probably suboptimal, if you want to change how an already-existing persistent GenServer behaves.

However, since some of these settings alter the way the PID lookup happens (and control things like ‘how-to-restart-if-persisted’), I see no other way to do this. :thinking:

More (hammock-driven :grin: ) thinking needed…

Phillipp

Phillipp

First: Your repo seems to be private. Link to GitHub - Qqwy/elixir_persistent_gen_server · GitHub results in a 404.

This will be a nice lib, provided the following could be added in the future:

  1. start_link function together with the ability to start it under a Supervisor. Not sure if that is possible if one wants to keep the “stop after timeout and restart when needed” functionality in its current form.
  2. Recover state on system restart. If stopped properly, your lib seems to remove the state for all GenServers but there might be cases when someone wants to recover full state after properly restarting the OTP app (e.g. deployment).

Where Next?

Popular in Announcing Top

tmbb
I’ve published the first version of my Makeup library. It’s a syntax highlighter for Elixir in the spirit of Pygments, Currently it highl...
New
restlessronin
The repo is at GitHub - cyberchitta/openai_ex: Community maintained Elixir library for OpenAI API · GitHub. Docs are at OpenaiEx User Gu...
152 10167 134
New
bryanjos
Hi, I wanted share a small library we at Revelry Labs made for rendering react components from the server side. There are instructions fo...
New
blatyo
The best overview for how things are tied together is this presentation. Modules and functions are pretty well documented at this point, ...
New
josevalim
Hello everyone, We have just released NimbleCSV which is a small and fast CSV parsing library for Elixir. It allows developers to define...
New
archan937
It is a well-know topic within the Elixir community: “To mock or not to mock? :)” Every alchemist probably has his / her own opinion con...
New
zachdaniel
Ash Framework What is Ash? Ash Framework is a declarative, resource-oriented application development framework for Elixir. A resource can...
New
Qqwy
TypeCheck: Fast and flexible runtime type-checking for your Elixir projects. Core ideas Type- and function specifications are const...
336 14327 100
New
kevinlang
Hey all, We have made an Ecto3 Adapter for SQLite3, ecto_sqlite3! We have successfully on-boarded the full suite of integration tests (...
New
wfgilman
I’ve cleaned up and open sourced three financial libraries I was using for my company. They are bindings for the APIs of these three comp...
New

Other popular topics Top

AstonJ
Posting this to see if we can make things easier for people to get into Neovim. If you use Neovim and have a favourite distro please let ...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
AngeloChecked
What learn first? Rust or Elixir Hi Elixir community! I’m here because i want learn a new language. I’m a junior developer and mainly i ...
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New
Emily
I have VueJS GUIs with the project generated using Webpack. I have Elixir modules that will need to be used by the VueJS GUIs. I forese...
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 52341 488
New
marick
I had some trouble figuring out how to make many-to-many associations work. Once I got it working, I wrote a blog post. Because I’m a nov...
New
hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" => #BSON.ObjectId<58eb1a7a9ad169198c3dXXXX>, "email" => ...
New
svb
Hi! Currently I want to submit a form by pressing the Enter key. However, since my input field is of type “textarea” this is just adds a...
New

We're in Beta

About us Mission Statement