Rails Kredis: Redis-Backed Model Attributen Zonder Redis-Code te Schrijven
Rails Kredis voegt Redis-backed counters, flags, lists en hashes direct toe aan je models — geen handmatig key-beheer, geen boilerplate.
Elke keer als ik een Rails-applicatie overneem die al een paar jaar in productie draait, vind ik dezelfde constructie ergens in de codebase: een $redis global, of een Redis.new-aanroep begraven in een service object, en een handvol methoden die eruitzien als INCR user:#{user.id}:daily_requests verspreid over controllers, achtergrondtaken en model callbacks. Het key-formaat is elke keer opnieuw bedacht door degene die het nodig had. Niemand heeft het gedocumenteerd. De enige documentatie is een Slack-bericht van achttien maanden geleden, waarin iemand uitlegde waarom de teller middernacht UTC reset in plaats van in de tijdzone van de gebruiker.
Dit is het probleem dat Kredis oplost. Niet het tijdzone-probleem — dat moet je nog steeds zelf doordenken — maar het versnipperde Redis-key-beheer, het gebrek aan encapsulatie en de wrijving die ontwikkelaars naar een databasekolom doet grijpen in plaats van Redis, omdat een databasekolom “gewoon werkt” met ActiveRecord.
Kredis is een first-party Rails gem — geschreven door DHH en het Rails core team, geëxtraheerd uit Basecamp en Hey, en meegeleverd met Rails 7+. Het geeft je Redis-backed datatypes — counters, flags, lists, sets, enums, hashes — als model-attributen die werken als elk ander ActiveRecord-attribuut. Declareer kredis_counter :page_views op een model en je krijgt direct user.page_views.increment en user.page_views.value, met automatisch beheerde Redis-key-naamgeving. Geen handmatige key-constructie. Geen global Redis-client die in de verkeerde laag terechtkomt.
Na het gebruiken van Kredis in diverse productie-Rails-applicaties heb ik gemerkt dat het verandert hoe ik denk over wat in Postgres versus Redis thuishoort. Bepaalde patronen die ik vroeger in de database wrong — recente zoekgeschiedenis, zachte rate limits, tijdelijke feature-states, per-sessie-metadata — passen veel natuurlijker in Redis, en Kredis neemt de wrijving weg die me vroeger naar de makkelijkere databasekolom dreef.
Wat Rails Kredis Is (en Niet Is)
Kredis is een dunne wrapper over Redis, geen ORM voor Redis. Het probeert niet over keys heen te queryen of cross-record indexes te bouwen. Wat het wel doet, zijn drie dingen:
- Redis-key-naamgeving automatisch beheren, per model-instantie
- Een nette Ruby-interface bieden voor een handvol Redis-datatypes
- Die types direct integreren in ActiveRecord-models via class-macro’s
Een kredis_counter op een User-model slaat zijn data op in een key die eruitziet als user:42:page_views. Die key wordt aangemaakt bij de eerste schrijfoperatie, bijgewerkt bij elke increment en verloopt wanneer je .reset aanroept of expires_in: instelt op de declaratie. Niets wordt naar Postgres geschreven. Niets in een databasetransactie.
Wat Kredis niet is: het is geen vervanging voor Solid Cache of de fragmentcache. Het is geen vervanging voor Rack::Attack of Sidekiq’s rate limiting middleware. Het is geen pub/sub-systeem of event stream. Het is bedoeld voor ephemeral state per resource — state die gebonden is aan één model-instantie, vaak wordt gelezen en geschreven, en niet over records heen gequeryd hoeft te worden.
Setup
Voeg de gem toe aan je Gemfile:
gem "kredis"
bundle install
rails kredis:install
De installer maakt config/redis/shared.yml aan:
development:
url: redis://localhost:6379/1
test:
url: redis://localhost:6379/2
production:
url: <%= ENV["REDIS_URL"] %>
Ik stel altijd aparte Redis-database-indexen in voor Kredis versus de cache. Als je ooit de cache moet flushen, wil je niet tegelijkertijd Kredis-state — gebruikerstellers, flags — wissen.
Voor meerdere benoemde Redis-verbindingen:
development:
url: redis://localhost:6379/1
analytics:
url: redis://localhost:6379/3
Verwijs ernaar met de config:-optie: kredis_counter :events, config: :analytics. Handig wanneer je frequente analytics-tellers wilt isoleren van gebruikersgerelateerde state.
Kredis Counter: Atomische Increments Zonder Locking
De counter is waar Kredis het snelst zijn waarde bewijst. Redis INCR is atomisch — geen dirty reads, geen with_lock nodig, geen row-level contention op een databasekolom. Voor alles wat je regelmatig optelt vanuit meerdere threads of processen is een Kredis-counter schoner en sneller dan een databasekolom.
class User < ApplicationRecord
kredis_counter :api_calls
kredis_counter :api_calls_today, expires_in: -> { Time.current.end_of_day - Time.current }
end
Gebruik:
user = User.find(1)
user.api_calls.increment # +1
user.api_calls.increment(by: 10) # +10
user.api_calls.decrement # -1
user.api_calls.value # => 47
user.api_calls.reset # terug naar 0
De api_calls_today-counter gebruikt een lambda voor expires_in:. De resterende tijd tot het einde van de dag wordt berekend op het moment van de eerste schrijfoperatie. Elke dag verloopt de Redis-key vanzelf en begint de teller opnieuw bij de volgende increment — geen cron-job, geen opruimcode.
Een praktische toepassing: zachte API rate limiting op model-niveau, als aanvulling op de hardere aanvraag-level limiting die je al hebt in Rack::Attack (behandeld in de Rack::Attack productiegids):
class User < ApplicationRecord
API_DAILY_LIMIT = 1_000
kredis_counter :api_calls_today, expires_in: -> { Time.current.end_of_day - Time.current }
def api_rate_limited?
api_calls_today.value >= API_DAILY_LIMIT
end
def track_api_call!
api_calls_today.increment
end
end
Een controller kan current_user.api_rate_limited? controleren vóór verwerking en gebruik bijhouden met current_user.track_api_call! erna — geen van beide paden raakt Postgres.
Kredis Flag: Eenmalige States en Zachte Feature Toggles
Een Kredis-flag is een Redis-key die ofwel bestaat of niet bestaat. mark zet hem, marked? controleert hem, remove verwijdert hem.
class User < ApplicationRecord
kredis_flag :welcome_email_sent
kredis_flag :profile_prompt_dismissed
kredis_flag :beta_features_enabled
end
user.welcome_email_sent.mark
user.welcome_email_sent.marked? # => true
user.welcome_email_sent.remove
user.welcome_email_sent.marked? # => false
Een flag verslaat een boolean databasekolom wanneer de state ephemeral is (veilig verliesbaar bij een Redis-herstart), niet nodig voor queries of rapportage en beperkt tot een korte tijdvenster. Voeg vervaldatum toe voor tijdgebonden states:
class User < ApplicationRecord
kredis_flag :password_reset_initiated, expires_in: 10.minutes
end
Dit is een lichtgewicht alternatief voor een password_reset_tokens-tabel bij eenvoudige flows die geen cryptografisch gesignde tokens vereisen. De key verloopt automatisch na tien minuten — geen achtergrondopruiming, geen accumulatie van verlopen tokenrecords.
Flags werken ook goed voor per-gebruiker feature toggles die de volledige machinerie van Flipper niet nodig hebben (behandeld in de feature flags post). Als je een bètafunctie wilt inschakelen voor een specifieke gebruiker zonder een volledig feature flag-systeem, zijn user.beta_features_enabled.mark en een controle op user.beta_features_enabled.marked? twee regels code zonder nieuwe tabellen.
List en Unique List: Recente Geschiedenis Zonder Koppeltabel
kredis_list geeft je een geordende Redis-lijst met duplicaten toegestaan. kredis_unique_list dedupliceert — ondersteund door een Redis sorted set met invoertijdstempel als score.
class User < ApplicationRecord
kredis_unique_list :recent_searches, limit: 10
kredis_list :activity_log, limit: 50
end
user.recent_searches.prepend("rails credentials")
user.recent_searches.prepend("kamal deploy")
user.recent_searches.prepend("rails credentials") # dedupliceert en verplaatst naar voren
user.recent_searches.elements # => ["rails credentials", "kamal deploy"]
user.activity_log.append("logged_in")
user.activity_log.append("viewed_dashboard")
user.activity_log.elements # => ["logged_in", "viewed_dashboard"]
user.activity_log.remove("logged_in")
De limit:-optie beperkt de lijstlengte automatisch. Oudere invoer valt eraf zodra de limiet wordt overschreden. Geen opruimtaak. Geen groeiende tabel.
Voor een zoekautocomplete-functie vervangt dit wat anders een recent_searches-koppeltabel zou zijn met een user_id, query, searched_at, een uniekheidsindex en een opruimtaak voor items ouder dan 30 dagen. De koppeltabelbenadering werkt, maar voegt een migratie, een index en doorlopend onderhoud toe. De Kredis unique list is één declaratie en één .prepend-aanroep.
De tradeoff is reëel: Kredis-lijsten zijn niet querybaar over records heen. Je kunt niet vragen “welke gebruikers hebben gezocht naar ‘rails credentials’.” Als je cross-user-analytics nodig hebt, is de koppeltabel het juiste gereedschap. Kredis is voor per-resource-state die alleen van één kant wordt benaderd.
Enum: Tijdelijke State Machines
kredis_enum slaat een string op beperkt tot een toegestane set waarden, met optionele vervaldatum.
class VideoExportJob < ApplicationRecord
kredis_enum :processing_status,
values: %w[queued processing completed failed],
default: "queued"
end
job.processing_status.value # => "queued"
job.processing_status.value = "processing"
job.processing_status.is?(:queued) # => false
job.processing_status.is?(:processing) # => true
Ik gebruik dit patroon voor operaties die een zichtbare status hebben die niet langer hoeft te bestaan dan de operatie zelf:
class Account < ApplicationRecord
kredis_enum :maintenance_mode,
values: %w[none enabled scheduled],
default: "none",
expires_in: 4.hours
end
Onderhoudsmodus die automatisch na vier uur verloopt elimineert het incident waarbij iemand de onderhoudsmodus inschakelde en vergat hem uit te zetten. De expires_in:-klok begint bij de eerste schrijfoperatie, dus als de onderhoudsmodus om 23:00 wordt ingeschakeld, verloopt hij automatisch om 03:00.
Hash en JSON: Gestructureerde Metadata Zonder Schemawijzigingen
kredis_hash slaat sleutel-waardeparen op in een Redis-hash. kredis_json slaat elk JSON-serialiseerbaar Ruby-object op.
class User < ApplicationRecord
kredis_hash :preferences
kredis_json :last_session_context, expires_in: 30.days
end
user.preferences.update(theme: "dark", locale: "nl")
user.preferences[:theme] # => "dark"
user.preferences.to_h # => {"theme" => "dark", "locale" => "nl"}
user.last_session_context.value = { referrer: "google", utm_campaign: "q4" }
user.last_session_context.value # => {"referrer" => "google", "utm_campaign" => "q4"}
kredis_json is het meest flexibele type en ook het type dat het meest misbruikt wordt. Ik gebruik het voor sessie-metadata, analysecontext of functie-specifieke configuratie per gebruiker die niet querybaar hoeft te zijn. Ik gebruik het niet als documentopslag. Als ik de opgeslagen data later moet queryen, gaat het in een Postgres JSONB-kolom — de Postgres JSONB-post behandelt indexeringspatronen daarvoor.
Testen van Rails Kredis
Kredis werkt tegen echte Redis in tests. Configureer een dedicated test-database-index in config/redis/shared.yml (index 2 als conventie) en flush tussen tests door:
# spec/rails_helper.rb
RSpec.configure do |config|
config.before(:each) do
Kredis.redis.flushdb
end
end
Met Minitest:
# test/test_helper.rb
class ActiveSupport::TestCase
teardown do
Kredis.redis.flushdb
end
end
Een praktische test voor het rate limiting-voorbeeld:
class UserTest < ActiveSupport::TestCase
test "api rate limiting schakelt in bij limiet" do
user = users(:active)
assert_not user.api_rate_limited?
User::API_DAILY_LIMIT.times { user.api_calls_today.increment }
assert user.api_rate_limited?
end
test "rate limit reset na vervallen" do
user = users(:active)
User::API_DAILY_LIMIT.times { user.api_calls_today.increment }
user.api_calls_today.reset
assert_not user.api_rate_limited?
end
end
Testen tegen echte Redis is aanzienlijk eenvoudiger dan het stubben van individuele Kredis-attributen. De flushdb in teardown houdt tests geïsoleerd zonder mock-setup.
Productieoverwegingen voor Kredis
Key-naamgeving. Standaard prefixeert Kredis keys met de naam van de Rails-applicatie om conflicten in een gedeelde Redis-instantie te voorkomen. Controleer het prefix in config/redis/shared.yml. In een multi-app-omgeving is expliciete naamgeving het dubbel controleren waard vóór deployment.
Keyspace-grootte. Kredis maakt één Redis-key aan per gedeclareerd attribuut per model-instantie. Een User-model met drie Kredis-attributen en 500.000 gebruikers maakt tot 1,5 miljoen Redis-keys. Dat valt ruim binnen de capaciteiten van Redis — het verwerkt honderden miljoenen keys — maar het vereist geheugenplanning. Een Kredis-counter neemt ongeveer 50–100 bytes in beslag inclusief key-overhead. Beoordeel de populatiegrootte van je models en gedeclareerde attributen voordat je je Redis-instantie dimensioneert.
Vervalbeleid. Stel expires_in: in op alles wat niet onbegrensd mag groeien. Lists met limit: krimpen automatisch, maar een flag die wordt gezet en nooit wordt verwijderd, groeit onbeperkt. Ik beoordeel elk Kredis-declaratie in een codebase op vervalbeleid voordat ik naar productie ga.
Redis-persistentie. Redis RDB/AOF-persistentie dekt Kredis-data bij herstarts. Redis draaien zonder persistentie betekent dat Kredis-state bij herstart wordt gereset. Voor tellers die toch dagelijks resetten is dat prima. Voor flags die gebruikersgedrag sturen, configureer persistentie of voeg een database-fallback toe.
Redis Cluster. Kredis ondersteunt Redis Cluster standaard niet. Key-hashing in Cluster distribueert keys over verschillende nodes, en Kredis verwacht dat alle operaties voor een model-instantie op dezelfde node terechtkomen. Voor de meeste Rails-applicaties kan een enkele Redis-instantie of Redis Sentinel de Kredis-belasting moeiteloos verwerken zonder Cluster.
Kredis versus Alternatieven
Directe Redis-aanroepen. Flexibeler, uitgebreider, meer kans op fouten in key-naamgeving. Gebruik directe Redis-aanroepen voor eenmalige ad-hoc keys. Wanneer hetzelfde key-patroon in meerdere models voorkomt of op meer dan twee plekken wordt herhaald, extraheer het naar Kredis — dat dwingt consistente naamgeving af en elimineert de boilerplate.
Databasekolommen. Altijd querybaar, transactioneel en duurzaam bij storingen. Kredis vervangt geen database-state. Gebruik een databasekolom wanneer je over records heen moet queryen, wanneer de state een Redis-herstart moet overleven zonder herstel, of wanneer hij financiële of compliance-logica aanstuurt. Kredis en databasekolommen bestaan prima naast elkaar — de meeste models die ik bouw, gebruiken allebei.
Flipper voor feature flags. Flipper is het juiste gereedschap voor percentage-rollouts, groepstargeting en feature flag-analytics per omgeving. Een Kredis-flag is het juiste gereedschap voor een eenvoudige per-gebruiker-toggle die niets van dat alles nodig heeft. Ik gebruik allebei in dezelfde applicatie zonder conflicten.
Veelgestelde Vragen
Is Rails Kredis stabiel genoeg voor productie?
Ja. Het is geëxtraheerd uit Basecamp en Hey — beide hoogverkeer-productie-Rails-applicaties — voordat het open source werd gemaakt en werd meegeleverd met Rails 7+. Het Rails-team onderhoudt het naast het framework. Ik heb het in productie gedraaid op applicaties die miljoenen verzoeken per dag verwerken zonder problemen.
Wat gebeurt er met Kredis-data wanneer een model-record wordt verwijderd?
Niets automatisch. Kredis-keys zijn onafhankelijk van het model-record in Postgres. Het vernietigen van een User laat hun Kredis-keys in Redis achter totdat ze op natuurlijke wijze verlopen. Voor keys met expires_in: wordt dit automatisch opgeruimd. Voor permanente keys voeg je een before_destroy-callback toe:
before_destroy :cleanup_kredis_attributes
private
def cleanup_kredis_attributes
api_calls.reset
welcome_email_sent.remove
recent_searches.clear
end
Kan ik Kredis gebruiken zonder ActiveRecord?
Ja. De standalone API werkt overal:
counter = Kredis.counter("search:#{user_id}:queries")
counter.increment
counter.value
De model-macro’s zijn het grootste ergonomische voordeel, maar de onderliggende Kredis.*-klassemethoden werken in gewone Ruby-objecten, service classes of achtergrondtaken zonder enige ActiveRecord-afhankelijkheid.
Hoe gaat Kredis om met Redis-uitval?
Het gooit een Redis::CannotConnectError. Er is geen automatische fallback. Omsluit Kredis-aanroepen in rescue-blokken waar Redis-uitval een reëel risico is:
def api_rate_limited?
api_calls_today.value >= API_DAILY_LIMIT
rescue Redis::CannotConnectError
false # fail open: verzoeken doorlaten als Redis down is
end
De beslissing over fail-open versus fail-closed hangt af van de bedrijfsimpact. Rate limiting en recente geschiedenis kunnen doorgaans fail-open zijn. Elk Kredis-attribuut dat authenticatie of betalingen bewaakt, heeft een database-fallback nodig — of moet helemaal niet met Kredis worden geïmplementeerd.
Werkt jouw Rails-applicatie met ephemeral state die je Postgres-schema onnodig belast? TTB Software helpt engineeringteams bij het ontwerpen van schone data-architecturen op Rails. We doen dit al negentien jaar.
Related Articles
Rails Hybrid Search: pgvector Semantisch Zoeken Combineren met Postgres Full-Text voor Betere Recall
Rails hybrid search combineert pgvector semantische vectoren met Postgres full-text zoeken en Reciprocal Rank Fusion ...
Rails LLM Gespreksgeschiedenis: Context Opslaan, Samenvatten en het Context Window Beheren
LLM gespreksgeschiedenis in Rails: sla beurtenwissels op, beheer context windows, implementeer samenvatting en bouw m...
Rails API Serialisatie: Blueprinter, Alba en JSONAPI-Serializer Vergeleken voor Productie-API's
Rails API serialisatie goed aanpakken: vergelijk Blueprinter, Alba en jsonapi-serializer met echte code, N+1-valkuile...