How to maintain state in production and recover from unexpected shutdown?

How do you automate maintaining the state of an app in production?

How do you recover from an outage, if the machine running your application is shut down without warning?

Taking the last full snapshot and replaying the events since then seems simple enough, are there tools to implement this process with filesystems or databases?

What you are describing seems like it’s beyond the scope of Elm’s typical use case (application is embedded in a web page, and the application is designed in a way that when the page is refreshed, the entire state is reinitialized). Can you provide more details as to how you are using Elm? That may make it easier to help you.

2 Likes

You could return a Cmd from your update function to backup the state to some persistent storage on every update. That storage could be either in the browser (via localStorage or indexedDB) or on your server via HTTP request.

If it’s one of the browser storage APIs then you’ll need to use a port and some JavaScript code. It could look something like this:

port sendToJs : Model -> Cmd Msg

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    let
        newModel =
            case msg of
                Msg1 ->
                    -- your usual update stuff
    in
        (newModel, sendToJs newModel)

This assumes that your Model can be automatically serialized by the Elm runtime. If you have union types or something in there, you may need to Json.Encode it yourself instead.

EDIT: I forgot to mention how to actually recover! If you’re using ports to store data in the browser, you can retrieve it again on page reload with some JavaScript code before you start Elm. Then pass the initial state into the Elm.Main.fullscreen function. Inside your Elm program, use Html.programWithFlags and then handle the initial data in your init function. More details in the Elm guide.
Alternatively, in the case where you backup to the server rather than localStorage, you can return a Cmd from your init function that does an HTTP request to retrieve the data.

2 Likes

We use a combination of the URL (e.g. path and query string), LocalStorage, Cookies (for the session) and our Database. Anything that is not stored in any of those will just reset to the initial state when refreshing the app.

At the moment, the setup looks like this:

The elm code of the web app is compiled to javascript using elm-make 0.18. After some minor adaptions of the javascript code, it is loaded into a JavaScript engine. For each Msg, the webserver which hosts the web app calls into the JavaScript engine and supplies the serialized Msg to an update function.
So currently the state of the web app is modeled in memory in the JavaScript engine. I have not yet found a suitable JavaScript engine which supports storing and loading its contents.

@Brian_Carroll thank you, In case I do not find a better solution, I will introduce persistence with this approach of implementing it from scratch and serializing and storing the complete state after each update. I will see how far I get with that.

This assumption surprises me. Did you see a function available to serialize Elm objects in the runtime? If yes, how can I find it, and the counterpart for deserializing?
What is this serializing function normally used for?

I’m just talking about ports. When you define a port like
port sendToJs : (Int, String) -> Cmd msg
The Elm runtime will automatically convert that to a JS value (in this case, an array with a number and a string). You don’t have to write a JSON encoder. But if you have some types that are not built-in, then it can’t do it automatically, and you do have to write an encoder.

Actually now that I’m writing this, I realize that “serialise” was the wrong term to use. It’s just converting to a JS value, not an actual JSON string, so it’s not serialising. Sorry for the confusion.

1 Like

In this case I don’t think you can do anything particularly fancy. I would just use ports (for saving) + flags (for restoring) like @Brian_Carroll describes. The only difference to his suggestion I would make is splitting the update function up into two functions, so that you can abstract away your backup call and ensure it runs after every Msg, while still using your regular update function normally:

Html.programWithFlags =
    { ...
    , update = updateWithBackup
    , ...
    }

updateWithBackup : Msg -> Model -> (Model, Cmd Msg)
updateWithBackup msg model =
    let
        (retModel, retCmd) = 
            update msg model
    in
        -- prepend your port function call to the list
        -- of Msg returned from your internal update
        -- NOTE: code not actually tested
        (retModel, Cmd.batch (sendToJs retModel)::[retCmd])

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
   case msg of
      ... -- use this update function normally; the backup process is abstracted
1 Like

@Brian_Carroll thank you for the clarification. Then I will maintain the generation of the serializing code myself for now.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.