A guide to understanding 'Persistent Context'

Don’t forget, the current implementation only gets flushed to disk every 30secs. It is there if you “get” it immediately( from the cache) but may not be in the file.

1 Like

@RogierQ, Here's a test I made:

contextStorage: {
    store: {
        module:"localfilesystem"
    },
    default: {
        module:"memory"
    }
},

Here's how my test flow looks:

Here's the code:

[{"id":"1b12c10.509463f","type":"tab","label":"Persistent Context Test","disabled":false,"info":""},{"id":"73e93758.9aa008","type":"inject","z":"1b12c10.509463f","name":"","topic":"","payload":"B","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":130,"y":140,"wires":[["532bf815.546508"]]},{"id":"7d06f012.51671","type":"inject","z":"1b12c10.509463f","name":"","topic":"","payload":"A","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":130,"y":100,"wires":[["532bf815.546508"]]},{"id":"5d36a28e.6f402c","type":"inject","z":"1b12c10.509463f","name":"","topic":"","payload":"C","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":130,"y":180,"wires":[["532bf815.546508"]]},{"id":"532bf815.546508","type":"function","z":"1b12c10.509463f","name":"Set context","func":"flow.set(\"cxTest\", msg.payload, \"store\");\nreturn msg;","outputs":1,"noerr":0,"x":470,"y":140,"wires":[["4fb35e5c.8cb35"]]},{"id":"4fb35e5c.8cb35","type":"debug","z":"1b12c10.509463f","name":"Set value","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":660,"y":140,"wires":[]},{"id":"667f1611.ddc8a8","type":"inject","z":"1b12c10.509463f","name":"Show stored context","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":170,"y":240,"wires":[["fce453bf.1b35a"]]},{"id":"fce453bf.1b35a","type":"function","z":"1b12c10.509463f","name":"Get context","func":"msg.payload = flow.get(\"cxTest\",\"store\") || \"empty\";\nreturn msg;","outputs":1,"noerr":0,"x":470,"y":240,"wires":[["5663ce9f.ca75b"]]},{"id":"5663ce9f.ca75b","type":"debug","z":"1b12c10.509463f","name":"Get value","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":660,"y":240,"wires":[]},{"id":"cecdc7e2.8718e8","type":"inject","z":"1b12c10.509463f","name":"Get stored context at deploy/startup","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":true,"onceDelay":"1","x":220,"y":280,"wires":[["fce453bf.1b35a"]]}]
1 Like

(did you start and end the flow with three backtic's on the line before and after it? I could not import it)

And??? I had to fudge to get your flow to import, but it is working for me. Are you saying that you have an issue? If so what is it?

@zenofmud, sorry, I was replying to @RogierQ to help show a test flow. Fixed the code as well. Thanks.

Is this configurable? There's no denying that this is essential behavior on SD but there are bound to be folks running NR on way bigger hardware. (For example 4 of my deployments are in ESX clusters with flash based SAN storage...)

Yes, that's why I said it was the default mode.

The documentation describes the various configuration options for the file store.

https://nodered.org/docs/api/context/store/localfilesystem

@knolleary can you explain further why the cache is set to true by default? Don't you think it should be false by default since it gives you more control to store the current value only on 'set'? Or is the intention/use case is to define the context as persistent (e.g. flow.set("temperature", "33", "storeInFile"); ), keep assigning values to the context by regular assignment (e.g. flow.temperature = "38"), and then auto save to file every flushInterval?

@rsuplido the main reason to enable the cache by default is that is allows all existing flows to continue to work without any changes.

If you disable the cache, then every context get or set needs to access the file on disk. This can only be done asynchronously. So existing synchronous code will not work.

For example:

var temperature = flow.get("temperature");

will not work with the cache disabled. Instead, it needs to do:

flow.get("temperature", function(temperature) {
    // do something with temperature
});

This is described in the Function node docs - https://nodered.org/docs/writing-functions#storing-data

Putting disk IO in the main path will also have a performance impact. Using the cache means the main path is unaffected and the flushing to disk can happen as a background action.

Any 3rd-party node that accesses context needs to be updated to support async access - that isn't going to happen overnight and may take some time for node maintainers to take action.

2 Likes

I figured as much, but with linguistic nuance I learned to resist assumptions long ago. Thanks for the link to documentation.

Thanks! Got it running now. Works great :wink:

Last idea / question, how can i clear all saved globals?
Just delete the file? I gues not because it is also in the memory?

you can delete a specific global by using the change node. Not sure how to do a blanket “delete all”

My last big project involved pulling in and parsing data from about 500 temp/humidity sensors to build a set of watchdog timers (that part is still in the works) and an alerting/silencing system for alarms associated with those values and their optimal ranges. Some of the sensors go online/offline for days at a time (part of the problem I'm wanting to pin down) so I had to programmatically define all of my flow variables and let the flow "settle" for a bit between attempts to get the parsing algorithm right for my data.

How nice it would have been to have a quick "delete field" button when I was doing cleanup. (Made do just fine with the change node trick, though. I'm assuming my use case was not close enough to normal to warrant priority on such a feature.)

1 Like

I would also vote for a button against each variable in the context data sidebar to have as a quick delete. Really helpful when developing but also helpful when you forgot to set the correct mode and you then end up with an extra copy of the variable in more than one place but with the same name.

2 Likes

You are saying that node id's don't change. I have been stumbling over this using subflows. It appears that once a subflow gets linked to a node, the nodes that are contained in the subflow seem to be created dynamically and such nodes will get a new node id on each deploy. That actually broke one of my implementation concepts trying to keep cache data for my nodes. That works fine unless nodes are contained in a subflow. In such case the node get’s the ‘_alias’ member which seems to refer to it’s subflow container – unfortunately, the actual node id changes on a deploy. That’s at least what I have experienced. I have not been able to track nodes that are contained in a subflow – any hint whether that’s possible is highly appreciated.

If you look at the documentation you will see:

Note : for nodes within a subflow, the 'flow' context is scoped to the subflow. The nodes cannot access the flow context of the flow containing the subflow instance node.

@zenofmud that isn't quite related to the question from @pelis.

@pelis you've raised a good question. The individual instances of nodes within a subflow may get different ids when they are instantiated - which will be an issue if you're trying to use node-scoped context. I don't have a quick answer for that - it's something we need to look at in the core of node-red.

Thanks - that would be really helpfull

I think one thing that needs to be clarified in this post or even in the PR is that any data set to contextStorage will only be available to the flow or tab you are working on and not globally.

Probably should go without saying but always good to clarify.

@dkomando one of the first sections in the docs is about the context scope - it explains the three different scopes of context available to you - https://nodered.org/docs/user-guide/context#context-scopes

Does that cover it for you?

Good point, so I changed one line to read:

As of v.0.19.0 you can now store node, flow and global context data in memory OR in a file which will persist over restarts.