Giter Club home page Giter Club logo

Comments (2)

xendk avatar xendk commented on July 23, 2024

Well, the first thing to consider is: how to deal with concurrency? Reading the session on the start of the request and writing it before sending the response will work as long as the handler is pure Crystal, but quickly gets messy if Crystal calls another service and multiple fibers gets the chance to run.

Is session locking something to consider, or is it just up to the user to deal with? Storing values individually would avoid the problem somewhat, but not totally.

Real atomic sessions probably requires rethinking session from "here's a bag of values" to a service of some kind.

But that's overkill for most real-world usage. Real-time update of session derived variables is only going to complicate most pages without any real gain. In practise, "this is the session data as it looked at the start of the request" is quite sufficient in all but the oddest of cases.

Which means the real problem is writing. Some data is fine just to overwrite, while other is not. A flash message bag comes to mind, if the user loads two pages at the same time, you don't want to have the message from one overwrite the other, even if you don't care which window actually displays them both.

I checked up on PHP to see if they did anything smart, but apparently the session handling code just locks the session for the duration of the request, per default. User code can then close the session early if they have no need for writing. And the redis extension has a long issue because it didn't support locking in the old days and people ran into all sorts of problems.

So I guess that's probably the best bet.

Regarding your wish for typed session objects, I had a thought (and it's probably not thought through):

Imagine Session::Handler and Session::Store as generics that takes a session class (anything json-able). And a Session::register macro that defines HTTP::Server::Context#session to be of a given session class and Armature::Request#session as a way to get at it from a route handler?

Session::Store (well, it's concrete subclasses) just loads and saves the session class, locks/unlocks it, and have a #gc for optional housecleaning. Session::Handler handles the cookie stuff, passes data from/to the Context and the store.

The session class could have an abstract superclass that adds #close, #save, #delete and what else user code might need (#regenerate?) which is forwarded to the handler.

from armature.

jgaskins avatar jgaskins commented on July 23, 2024

Cookie-based sessions, where the session payload is serialized entirely inside the cookie, have the same issue with concurrent session mutations. That sort of session storage is the default in Rails and it looks like it's what is still in use on GitHub.

Storing values individually would avoid the problem somewhat, but not totally.

Depending on the storage backend, we could store them together but query/update them separately.

I've experimented with storing them separately — using a NATS KV, the user_id and csrf token were stored in two separate keys. This meant I didn't need to batch query/update like the Redis store currently does, but then the expiration timestamps are different for keys in the same session. This can be useful if you're storing a short-lived value on that key (redirect_uri after a successful login, one-time password, flash message, etc) but also surprising in some awkward ways.

In Redis, at least, we have several options and I've been considering providing different session backends to allow someone to choose which scenario they want to optimize for:

the current "fetch everything, mutate, then store everything" approach

  • pros
    • easy to grok
    • you can see everything inside a session all at once
  • cons
    • checking any key in the session loads all the keys in the session, regardless of how many there are or how large the values are
    • high-concurrency use cases can lead to inconsistency — it's like developing via SFTP where the last save wins

store session keys in separate Redis keys

  • pros
    • each key is updated without stepping on other keys in the same session
    • you don't have to load every session key on every request
    • session keys expire independently of each other
  • cons
    • session keys expire independently of each other

store data in Redis hashes

  • pros
    • stored together, but queryable separately
    • high-concurrency-friendly
  • cons
    • you need more than a String => String mapping in session data

RedisJSON-based adapter

  • pros
    • lets you store arbitrary objects and query them even when nested (like session["a/b-test.experiments.foo"]? for A/B testing)
  • cons
    • RedisJSON is not supported by the baseline Redis server so not all providers will support it, but there are still some options
    • JSON traversal inside the Redis server puts additional load on it, which can be problematic on a single-threaded server
      • this reduces the throughput ceiling for session fetching on a single-threaded, non-clustered implementation
      • in my benchmarks, fetching a UUID user_id property in a 1M-query pipeline took ~3x as long with RedisJSON as it did when stored in a hash

Another thing to consider is that differences in capability between implementations may require differences in APIs between them. For most cases, it's probably fine because the Crystal type checker can automatically downcast variables from abstract types to concrete types if there's only one concrete subclass and there isn't really benefit to loading multiple session storage implementations. I saw this in my NATS KV-based session store experiment, which only mapped strings to strings, where session["user_id"]? returned a String? at compile time — I didn't have to unwrap a JSON::Any. However, for testing, it may be a good idea to have an in-memory implementation for performance and to avoid throwing a bunch of test data into a persistent data store, so the APIs there do need to match.

from armature.

Related Issues (3)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.