{"title":"Redis to Adapters Migration","description":"Move Amber 1.x session and pub/sub behavior behind Amber V2 adapter interfaces","section":"migration-guide","version":"v2","path":"migration-guide/redis-to-adapters","canonical_url":"https://amberframework.org/docs/v2/migration-guide/redis-to-adapters","markdown_url":"https://amberframework.org/docs/v2/migration-guide/redis-to-adapters.md","inherited":false,"content_markdown":"# Migrating from Redis to Adapters\n\nAmber V2 removes Redis as a mandatory framework dependency. The framework ships\nin-memory session and pub/sub adapters; it does **not** ship a first-party Redis\nimplementation. Applications that still need Redis must implement and register\nadapters against the Amber interfaces.\n\nThis migration changes how Amber reaches the storage or message broker. It does\nnot require you to stop using Redis.\n\n## Where the examples go\n\n- built-in adapter names go in `config/environments/development.yml`,\n  `test.yml`, or the environment being changed;\n- application adapter classes go under `src/adapters/`, for example\n  `src/adapters/redis_session_adapter.cr`;\n- adapter registration belongs in `config/application.cr` before Amber builds\n  its session or pub/sub services; and\n- commands and specs run from the application root beside `shard.yml`.\n\n## Choose the target behavior\n\n| Requirement | Suitable direction |\n|---|---|\n| Local development and tests | Built-in memory adapters |\n| One application process where losing process-local state is acceptable | Built-in memory adapters after explicit verification |\n| Sessions shared across processes or hosts | Registered external session adapter |\n| WebSocket broadcasts shared across processes or hosts | Registered external pub/sub adapter |\n| Existing Redis-backed production behavior | Custom Redis adapters or another verified shared backend |\n\nThe memory adapters are process-local. Do not use them as a silent replacement\nfor shared Redis state in a horizontally scaled deployment.\n\n## Inventory the Amber 1.x contract\n\nBefore changing configuration, record:\n\n- the session cookie name, signing or encryption behavior, expiration, and\n  rotation rules;\n- the Redis key and channel namespaces;\n- the serialized session and pub/sub payload formats;\n- whether users or broadcasts must survive a process restart;\n- every application process that reads sessions or subscribes to broadcasts;\n- cleanup jobs, Redis ACLs, TLS settings, and monitoring tied to the old keys.\n\nKeep a deployable copy of the current configuration while the replacement is\ntested.\n\n## Built-in memory configuration\n\nThe clean V2 application selects the built-in adapters by name:\n\n```yaml\n# config/environments/development.yml\nsession:\n  key: \"my_app.session\"\n  store: \"signed_cookie\"\n  adapter: \"memory\"\n  expires: 3600\n\npubsub:\n  adapter: \"memory\"\n```\n\nUse this path for development, tests, or a deployment whose process-local state\nis an intentional constraint. Restart the application during testing to prove\nthat the resulting state loss is acceptable.\n\n## Keep Redis through a custom session adapter\n\nA shared session backend implements `Amber::Adapters::SessionAdapter`:\n\n```crystal\nabstract class Amber::Adapters::SessionAdapter\n  abstract def get(session_id : String, key : String) : String?\n  abstract def set(session_id : String, key : String, value : String) : Nil\n  abstract def delete(session_id : String, key : String) : Nil\n  abstract def destroy(session_id : String) : Nil\n  abstract def exists?(session_id : String, key : String) : Bool\n  abstract def keys(session_id : String) : Array(String)\n  abstract def values(session_id : String) : Array(String)\n  abstract def to_hash(session_id : String) : Hash(String, String)\n  abstract def empty?(session_id : String) : Bool\n  abstract def expire(session_id : String, seconds : Int32) : Nil\n  abstract def batch_set(session_id : String, values : Hash(String, String)) : Nil\n  abstract def batch(session_id : String, &block : Amber::Adapters::SessionBatchOperations ->) : Nil\nend\n```\n\nRegister the application implementation before Amber builds the session store:\n\n```crystal\n# config/application.cr\nrequire \"amber\"\nrequire \"../src/adapters/redis_session_adapter\"\n\nAmber::Adapters::AdapterFactory.register_session_adapter(\"redis\") do\n  RedisSessionAdapter.new(redis_client)\nend\n```\n\nThen select the registered name in the environment configuration:\n\n```yaml\nsession:\n  key: \"my_app.session\"\n  store: \"signed_cookie\"\n  adapter: \"redis\"\n  expires: 86400\n```\n\nThe [Session Adapters guide](../../guides/adapters/sessions/) documents the complete\ninterface and registration contract. Compile and contract-test the application\nadapter against the exact Redis shard version it uses.\n\n## Keep cross-process broadcasts through a custom pub/sub adapter\n\nA shared message backend implements `Amber::Adapters::PubSubAdapter`:\n\n```crystal\nabstract class Amber::Adapters::PubSubAdapter\n  abstract def publish(topic : String, sender_id : String, message : JSON::Any) : Nil\n  abstract def subscribe(topic : String, &block : (String, JSON::Any) -> Nil) : Nil\n  abstract def unsubscribe(topic : String) : Nil\n  abstract def unsubscribe_all : Nil\n  abstract def close : Nil\nend\n```\n\nRegister and select the application implementation:\n\n```crystal\n# config/application.cr\nrequire \"amber\"\nrequire \"../src/adapters/redis_pubsub_adapter\"\n\nAmber::Adapters::AdapterFactory.register_pubsub_adapter(\"redis\") do\n  RedisPubSubAdapter.new(redis_client)\nend\n```\n\n```yaml\npubsub:\n  adapter: \"redis\"\n```\n\nThe [PubSub Adapters guide](../../guides/adapters/pubsub/) covers registration and\nmulti-process behavior. Test with at least two application processes; a\nsingle-process browser test cannot prove cross-process delivery.\n\n## Preserve or retire existing sessions deliberately\n\nChanging a session backend can invalidate every active session. Choose one of\nthese policies before deployment:\n\n- preserve the existing Redis key namespace and serialization in the new\n  adapter;\n- deploy a temporary dual-read migration that moves a session after a\n  successful old-format read;\n- schedule a coordinated logout and communicate it as an intentional product\n  change.\n\nDo not assume that forcing every user to sign in again is harmless. Account\nrecovery, long-running work, carts, CSRF state, and administrative sessions may\nmake session loss operationally significant.\n\n## Cutover sequence\n\n1. Add the adapter implementation and its dependency without removing the old\n   Redis configuration.\n2. Contract-test every adapter method, expiration behavior, malformed payload,\n   connection failure, and reconnect path.\n3. Exercise login, logout, session rotation, and WebSocket broadcasts in a\n   staging deployment that matches the production process count.\n4. Apply the chosen active-session migration policy.\n5. Switch the Amber configuration to the registered adapter name.\n6. Monitor adapter errors, Redis connections, session failures, and broadcast\n   delivery through the rollback window.\n\n## Removing Redis after the cutover\n\nRemove Redis only after confirming that no application process, job worker,\ncache, rate limiter, session store, or pub/sub subscriber still uses it. Inspect\nthe shard dependencies, environment variables, deployment manifests, secrets,\nmonitoring, and infrastructure configuration before retiring the service.\n\nKeep the previous configuration and deployment artifact available until the\nreplacement has passed its production verification window.\n\n## Verification checklist\n\n- [ ] Session create, read, update, delete, destroy, and expiration behavior pass.\n- [ ] Login, logout, rotation, and invalid-cookie behavior pass.\n- [ ] Restart behavior matches the chosen state policy.\n- [ ] Broadcasts reach subscribers in a second application process.\n- [ ] Redis authentication, TLS, ACLs, timeouts, and reconnect behavior are tested when Redis remains.\n- [ ] The active-session migration or coordinated logout is documented.\n- [ ] The previous configuration can be restored without a code rewrite."}