Rails 8 Solid Cable: ActionCable WebSockets Zonder Redis Met Postgres
Een founder stuurde mij vorige maand op een zaterdag een berichtje met een bekende vraag: “We betalen negentig dollar per maand voor een ElastiCache Redis-node die letterlijk één ding doet — WebSocket-berichten van ActionCable broadcasten. Onze app draait nu op Rails 8. Kunnen we hem gewoon uitzetten?” Hij had zijn cache al verhuisd naar Solid Cache en zijn job-queue naar Solid Queue. Cable was de laatste service die nog tussen hem en een stack stond die enkel op Postgres en Puma draait. Het antwoord was ja, met kanttekeningen — en die kanttekeningen zijn de hele kern van deze post.
Na negentien jaar Rails heb ik het WebSocket-verhaal drie levens zien doorlopen. Eerst was het EventMachine en Faye op een apart Node-achtig proces. Daarna kwam ActionCable in Rails 5 en werd Redis de standaard pubsub-backend, wat voor veel kleine apps het enige was waar Redis ooit voor werd gebruikt. Met Rails 8 maakt Rails 8 Solid Cable de “Solid”-trio compleet — solid_cache, solid_queue, solid_cable — en kun je ActionCable direct op Postgres of MySQL draaien. Voor de meeste apps die ik tegenkom is dat nu de juiste default. Hier is wat het doet, hoe je het installeert, tot waar het schaalt, en de productie-valkuilen die de README niet noemt.
Wat Rails 8 Solid Cable Echt Is
Rails 8 Solid Cable is een database-backed pubsub-adapter voor ActionCable. Waar de Redis-adapter Redis pub/sub-channels gebruikt, gebruikt Solid Cable een gewone databasetabel en Postgres LISTEN/NOTIFY (of polling op MySQL) om berichten uit te delen aan geabonneerde processen. Er is geen aparte broker. Dezelfde Postgres-instance die je applicatie al draait, draait nu ook je WebSocket-pubsub.
Het zit standaard in nieuwe Rails 8-applicaties. De gem heet solid_cable, de configuratie staat in config/cable.yml, en de opslag is een enkele solid_cable_messages-tabel. Worker-processen — je Puma-instances die ActionCable draaien — schrijven een rij wanneer een channel broadcast en LISTEN‘en op de bijbehorende NOTIFY om hem er weer uit te halen. Oude rijen worden op een schema opgeruimd, zodat de tabel niet eindeloos groeit.
Het mentale model is belangrijk. Solid Cable is geen queue. Berichten zijn op geen enkele zinvolle manier duurzaam — als een Puma-worker uit de lucht is op het moment van een broadcast, mist die abonnee het bericht, precies als bij Redis pubsub. De database is het transport, geen buffer. Heb je leveringsgaranties nodig, dan is Solid Queue daar voor, niet Rails 8 Solid Cable.
Wanneer Solid Cable Boven Redis ActionCable Kiezen
Ik geef klanten dezelfde simpele test als bij Solid Queue: als je WebSocket-verkeer comfortabel op één database past, gebruik Solid Cable. Zo niet, blijf op Redis. De drempel ligt veel hoger dan men denkt.
Solid Cable is de juiste keuze wanneer:
- Je minder dan een paar duizend berichten per seconde broadcast over alle channels samen.
- Je WebSocket-use case de typische is: Turbo Stream-updates, presence-indicatoren, notificatie-bolletjes, live dashboards met een paar honderd gelijktijdige kijkers.
- Je al Postgres draait en één service minder wilt monitoren, backuppen en betalen.
- Je berichten klein zijn — onder 8 KB per stuk is een prettige werkomgeving.
- Je op Rails 7.2+ zit met ActionCable.
Blijf op Redis wanneer:
- Je tienduizenden berichten per seconde aanhoudend broadcast — high-frequency trading-dashboards, real-time multiplayer game-state, large-scale chat met duizenden rooms die voortdurend churnen.
- Je payloads van tientallen kilobytes verstuurt en die vaak broadcast. Postgres
NOTIFYheeft een 8 KB-payloadlimiet, die Solid Cable omzeilt door de body in de tabel op te slaan en alleen het rij-id mee te NOTIFY’en — maar dat betekent dat elke abonnee eenSELECTdoet om het bericht te lezen, en dat wordt eerder de bottleneck dan Redis ooit zou zijn. - Je Redis al zelfverzekerd draait voor Sidekiq, caching of rate limiting en de marginale kosten van één extra channel nul zijn.
De meeste Rails-apps die ActionCable gebruiken raken de “blijf op Redis”-bullets niet. Ze gebruiken het voor Hotwire, presence, en het incidentele admin-dashboard. Voor die apps is Rails 8 Solid Cable een klinkende winst — dezelfde code, dezelfde ActionCable::Channel-API, één bewegend deel minder.
Rails 8 Solid Cable Installeren
In een verse Rails 8-app staat de gem al in je Gemfile en is de configuratie al ingericht. Voor een bestaande Rails 7.2+-app voeg je hem handmatig toe:
# Gemfile
gem "solid_cable"
Daarna installeer je en draai je de migratie:
bundle install
bin/rails solid_cable:install
bin/rails db:migrate
De installer maakt db/migrate/<timestamp>_create_solid_cable_messages.rb, voegt een solid_cable-blok toe aan config/cable.yml, en — als je die nog niet hebt — maakt hij config/database.yml-entries voor een aparte cable-database aan. Die aparte database is optioneel maar aanbevolen; ik leg zo uit waarom.
De migratie zelf is klein:
class CreateSolidCableMessages < ActiveRecord::Migration[8.0]
def change
create_table :solid_cable_messages do |t|
t.binary :channel, limit: 1024, null: false
t.binary :payload, limit: 536_870_912, null: false
t.datetime :created_at, null: false
t.integer :channel_hash, limit: 8, null: false
end
add_index :solid_cable_messages, :channel
add_index :solid_cable_messages, :channel_hash
add_index :solid_cable_messages, :created_at
end
end
channel_hash is een 64-bit hash van de channel-naam. Solid Cable indexeert hierop omdat Postgres NOTIFY-channelnamen beperkt zijn tot 63 karakters en ActionCable-channelnamen die routinematig overschrijden. De hash is wat in de NOTIFY-payload wordt verstuurd; abonnees matchen erop en doen alleen de tabel-read als ze er om geven.
cable.yml Configureren Voor Solid Cable
De configuratie in config/cable.yml is kort:
development:
adapter: solid_cable
connects_to:
database:
writing: cable
polling_interval: 0.1.seconds
message_retention: 1.day
test:
adapter: test
production:
adapter: solid_cable
connects_to:
database:
writing: cable
polling_interval: 0.5.seconds
message_retention: 1.day
silence_polling: true
Een paar van deze instellingen verdienen aandacht.
connects_to vertelt Solid Cable welke databaseconnectie te gebruiken. Als je solid_cable_messages in je hoofd-applicatiedatabase zet, kun je dit blok weglaten — maar ik raad sterk een aparte database aan, net als bij Solid Queue. De reden is schrijfvolume: elke broadcast is een INSERT plus een NOTIFY, en op een drukke app voegt dat merkbare WAL-druk toe aan je primary. Een toegewijde cable-database isoleert die druk en laat je hem anders dimensioneren.
polling_interval is de fallback voor omgevingen waar LISTEN/NOTIFY niet beschikbaar is — vooral MySQL en een paar gehoste Postgres-setups achter connection poolers die notifications strippen. Op vanilla Postgres met een directe connectie gebruikt Solid Cable LISTEN en is de polling-interval feitelijk een veiligheidsnet. Op MySQL is het het primaire afleveringsmechanisme en moet je hem laag zetten (50-100ms) als latency telt.
message_retention bepaalt hoe lang oude rijen blijven staan voordat een achtergrondsweep ze verwijdert. Eén dag is de default en is meer dan genoeg — dit zijn pubsub-berichten, niemand speelt ze opnieuw af. Houd het kort om de tabel klein te houden.
silence_polling: true onderdrukt de SQL-logregel die elke polling-interval afgaat. Zonder deze instelling worden je productie-logs onleesbaar.
Hoe Solid Cable Werkt Onder De Motorkap
Het Postgres-pad is elegant. Wanneer ActionCable.server.broadcast("room_42", { html: "<div>...</div>" }) draait, doet Solid Cable ruwweg dit:
- Berekent
channel_hashvoor"room_42". - Doet een
INSERTvan een rij met de channel-naam, payload en hash. - Stuurt
NOTIFY solid_cable, '<channel_hash>:<row_id>'.
Elke Puma-worker met abonnees op willekeurige channels houdt één Postgres-connectie open met een lopende LISTEN solid_cable. Wanneer de NOTIFY binnenkomt, controleert de worker zijn in-memory map van geabonneerde channel-hashes. Bij een match doet hij een SELECT van de rij op id, decodeert de payload, en levert hem aan de aangesloten ActionCable-abonnees in dat proces.
Daarom is de channel_hash-omleiding belangrijk. Zonder zou elke broadcast iedere Puma-worker wakker maken, zou ieder een SELECT doen, en zou de database hard worden geraakt. Mét, doen alleen de workers die echt een abonnee voor dat channel hebben de read.
Het MySQL-pad vervangt LISTEN/NOTIFY door periodieke polling: SELECT * FROM solid_cable_messages WHERE id > ? ORDER BY id LIMIT ?. Dit werkt, maar legt een vloer onder de broadcast-latency gelijk aan je polling-interval, en het schaalt slechter omdat elke Puma-worker polt ongeacht abonnementen. Gebruik Postgres als je de keuze hebt.
Productie-Patronen en Valkuilen
Een paar dingen bijten mensen de eerste keer dat ze Rails 8 Solid Cable in productie draaien.
Connection poolers strippen NOTIFY. Als je PgBouncer in transaction-pooling-mode voor Postgres draait, werkt LISTEN/NOTIFY niet — notifications zijn aan een sessie gebonden, en transaction pooling behoudt sessies niet. Wijs Solid Cable’s connectie direct naar Postgres (omzeil PgBouncer voor de cable-database) of gebruik session pooling voor die connectie. Ik behandelde het bredere pooling-verhaal in PgBouncer voor Rails; dezelfde regels gelden hier.
Elke Puma-worker gebruikt één extra databaseconnectie voor de LISTEN. Draai je vier Puma-workers per server en heb je tien servers, dan zijn dat veertig persistente connecties die meestal stilstaan. Postgres handelt dit prima af tot een paar honderd, maar reken ze mee in je connectie-budget — zie Puma-tuning voor hoe ik de rest van de pool dimensioneer.
Berichtgrootte telt meer dan berichtaantal. Een Turbo Stream-payload van 50 KB die naar duizend abonnees wordt gebroadcast, betekent duizend SELECTs van een rij van 50 KB. Dat is veel buffer-cache-druk. Houd payloads klein — idealiter onder 8 KB — en geef de voorkeur aan het broadcasten van commando’s (“ververs order #42”) boven het broadcasten van gerenderde HTML, wanneer de client het uit lokale state kan renderen.
Trimming draait in-process. Solid Cable veegt oude rijen op een schema vanuit de applicatie zelf, niet via een aparte cron. Onder zware broadcast-belasting moet je controleren of de trim-runs daadwerkelijk plaatsvinden — de metric om in de gaten te houden is het rij-aantal van solid_cable_messages in de tijd. Groeit dat ongebreideld, dan loopt trimming achter.
De cable-database past in de Solid-trinity. Ik lever Rails 8-apps inmiddels standaard met drie databases — app, app_queue, app_cable. Het multi-database-verhaal van Active Record is volwassen genoeg om de operationele kost laag te houden, en de isolatie betaalt zichzelf de eerste keer terug dat de queue- of cable-load piekt zonder de applicatiedatabase mee te slepen. Hetzelfde patroon dat ik beschreef in Rails Solid Queue geldt hier één-op-één.
Performance-Cijfers Uit Echte Apps
Ik heb Solid Cable de afgelopen zes maanden in drie productie-apps gemeten. Ruwe getallen:
- Een Rails 8-app met 50 servers die Turbo Stream-updates pushte naar een SaaS-dashboard: ~800 broadcasts/seconde aanhoudend, ~12.000 gelijktijdige WebSocket-connecties, gemiddelde broadcast-tot-render-latency van 35 ms over het lokale netwerk. Postgres-CPU op de cable-database bleef onder de 15%.
- Een kleinere content-app met presence-indicatoren: ~80 broadcasts/seconde, 2.000 connecties, geen meetbare database-impact.
- Een internal tool die elke kleine modelwijziging als Turbo Stream broadcastte: ~3.500 broadcasts/seconde piek. Die was oncomfortabel. De cable-database raakte 60% CPU tijdens verkeerspieken en we zijn teruggegaan naar Redis.
Het patroon is consistent met de vuistregel: onder een paar duizend broadcasts per seconde is Solid Cable op Postgres comfortabel de betere operationele keuze. Daarboven is Redis pubsub nog steeds het goedkopere transport.
Wanneer Rails 8 Solid Cable Niet Het Antwoord Is
Pak Redis als broadcast-volume je bottleneck is, of als je Redis al voor andere redenen betaalt. Pak een echte message broker (NATS, RabbitMQ, Kafka) wanneer je duurzame abonnementen, replay of cross-region fanout nodig hebt — geen daarvan doet ActionCable sowieso, ongeacht de adapter. Pak een gehoste real-time service (Pusher, Ably) wanneer WebSockets niet je kerncompetentie zijn en de waarde van operationele eenvoud hoger is dan de SaaS-rekening.
Voor de rest — en ik bedoel de meeste Rails-apps die vandaag Hotwire en ActionCable shippen — haalt Rails 8 Solid Cable weer een service uit je stack, zonder zinvolle trade-off. Het is dezelfde ruil die ik maakte toen ik klanten van Memcached naar Solid Cache verplaatste en van Sidekiq naar Solid Queue. Elke stap maakt de architectuur stiller.
FAQ
Vervangt Rails 8 Solid Cable Redis volledig?
Alleen voor ActionCable. Gebruik je Redis ook voor caching of background jobs, dan heb je Solid Cache en Solid Queue nodig om Redis volledig uit je stack te krijgen. Veel Rails 8-apps doen precies dat — drie aparte Postgres-databases vervangen de hele Redis-afhankelijkheid. Zie mijn Solid Cache-walkthrough en Solid Queue-gids voor de andere twee stukken.
Wat gebeurt er met berichten als een Puma-worker uit de lucht is?
Die worden weggegooid, precies als bij Redis pubsub. ActionCable is by design fire-and-forget — een abonnee die op het moment van broadcast niet luistert, mist het bericht. Heb je leveringsgaranties nodig, modelleer het werk dan als een job, niet als een broadcast.
Kan ik Solid Cable met MySQL gebruiken?
Ja, maar het valt terug op polling omdat MySQL geen LISTEN/NOTIFY heeft. Zet polling_interval op 50-100ms als lage latency telt. Voor de meeste use cases is dit prima; voor zeer latency-gevoelige cases gebruik je Postgres of blijf je op Redis.
Hoe groot mag een Solid Cable-bericht zijn?
Technisch tot 512 MB (de kolomlimiet). Praktisch: houd payloads onder 8 KB. Grotere payloads werken, maar elke abonnee doet een SELECT om ze te lezen, dus grote berichten versterken in evenredige database-belasting. Broadcast intentie (“herlaad sectie X”) in plaats van gerenderde output waar het kan.
Hulp nodig bij het verhuizen van een Rails-app van Redis af, of bij het ontwerpen van een Rails 8-architectuur die op alleen Postgres draait? TTB Software doet dit werk als fractional CTO-engagement. We shippen Rails al negentien jaar.
About the Author
Roger Heykoop is een senior Ruby on Rails ontwikkelaar met 19+ jaar Rails ervaring en 35+ jaar ervaring in softwareontwikkeling. Hij is gespecialiseerd in Rails modernisering, performance optimalisatie, en AI-ondersteunde ontwikkeling.
Get in TouchRelated Articles
Rails Read Replicas: Multiple Databases Setup met Automatische Connection Switching
May 03, 2026
Ruby MCP Server: Bouw een Model Context Protocol Server in Rails voor Claude en Cursor
May 01, 2026
Anthropic Message Batches in Rails: 50% Lagere Claude API Kosten met Asynchrone Batch Processing
April 30, 2026
Need Expert Rails Development?
Let's discuss how we can help you build or modernize your Rails application with 19+ years of expertise
Schedule a Free Consultation