Best way to store user-specific state between requests for 1-2 min

Hi -

I’m building a web app in phoenix. When a user signs up, they go through a few screens of onboarding. After the first screen, I want to kick off a task to fetch some data from an external service so that it’s available for the user when they get to the third screen. What is the best way to temporarily store some user-specific state between requests? I don’t want to store the state persistently in a DB since I won’t need the state after the finishes onboarding (so it’s also fine if I lose the state in case the server crashes during onboarding, etc.) I was thinking a GenServer might be the best way, but it seems like using GenServers are generally discouraged.

Thanks!

You might use Cachex for this.

Probably it meant don’t use GenServer for bad reason, but when You involve time, it is very appropriate to use it.

2 Likes

Cachex could be an option but you have to consider several things, most notably:

  • your state will be stored in memory and expire when you deploy new version of app, i.e. won’t be carried over
  • your state will not be shared across nodes in the cluster (if you have one)

I would just put the state on the background job maybe (?) that is gong to be called to do this task, or maybe in user session if the background job doesn’t have to have access to it, but honestly I’d probably put it in database.

3 Likes

I would store this in a GenServer. Or an Agent. You could just make sure to delete state for that user after they gone through the screens.

If you have a more complex setup (multiple servers), I would consider database.

Even with multiple servers you could load balance based on ip-hash and thus keep the same approach. (not sure if this is a good practice or not)

I’d personally use an agent / GS for this as well.

FWIW, Cachex now supports running distributed across multiple nodes (the nodes argument to Cachex.start_link) - I haven’t tried it in production, but it exists.

1 Like

I presume you’re already using a DB to store some other data. If that’s the case, then I’d also store this temp stuff in the DB, delete it after I don’t need it, and run a periodical job to delete all entries older than x (i.e. entries which were unclaimed/unused). This requires no extra library (or perhaps just one to simplify periodical job execution), should scale reasonably well, and most importantly will work correctly in cases where multiple instances of the beam are running.

If you’re running only one beam instance, then an ETS based cache with expiry, such as cachex or con_cache, will work too, and might even simplify things, but you need to remember to revisit the implementation if you ever decide to scale out.

I was thinking a GenServer might be the best way, but it seems like using GenServers are generally discouraged.

Not at all! For local ephemeral state, they’re perfect. You just have to be sure that your use case matches what they do.

In this case, you might consider using LiveView. It lets you keep state in memory, and unlike with HTTP, using web sockets means the user won’t accidentally be talking with a different BEAM node the next time they click.

If you don’t want to use LiveView, a disk-backed DB that’s accessible to all the BEAM nodes would be my approach. Even if you only have one instance right now, you probably shouldn’t design around that being true forever.

I did not know that. Probably would need to be re-adjusted/restarted whenever topology changes. May be interesting, I’ll look into that.