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
josevalim
EDIT: since Ecto 3.0 final version is out, this post was amended to use the final versions in the instructions below. Hi everyone, We a...
New
mikehostetler
I’m excited to announce Jido, a framework providing foundational primitives for building autonomous agent systems in Elixir. While develo...
New
tmbb
I’ve been working on two packages (not on hex.pm yet) to build admin interfaces for phoenix apps: bureaucrat - which contains a bunch ...
New
oltarasenko
Dear Elixir community, After a year of development, bug fixes, and improvements, we are proudly ready to share the release of Crawly 0.1...
New
michalmuskala
Another small library today. PersistentEts Hex: persistent_ets | Hex GitHub: GitHub - michalmuskala/persistent_ets · GitHub Ets table ...
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
michalmuskala
Hello everybody. I have just released Jason - a new JSON library. You might be wondering, why do we need a new library? The primary foc...
New
zachdaniel
Ash Framework What is Ash? Ash Framework is a declarative, resource-oriented application development framework for Elixir. A resource can...
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

Other popular topics Top

Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID<0.412.0> terminating ** (Postgrex.Error) FATAL...
New
JeremM34
Hello, how can I check the Phoenix version ? Thanks !
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 43757 214
New
gausby
I asked this very same question on twitter and got some interesting feedback, but I thought it would be a good question to ask here as we...
1207 39467 209
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
boundedvariable
I am going through the kafka architecture. All the features what the kafka is providing are already in Erlang. I would like hear your opi...
New
hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" => #BSON.ObjectId<58eb1a7a9ad169198c3dXXXX>, "email" => ...
New
sergio
Kind of like when jquery came out, it was super necessary. Existing drag and drop libraries have a bunch of baggage to support old browse...
New

Latest on Elixir Forum

Elixir Forum

We're in Beta

About us Mission Statement