Files
mozo-backend/lib/mozo/counter/redis.rb
T
BenClaw 3e4bcc80c8 feat(counter): add Redis counter adapter, replace DrbCounter
- Add Mozo::Counter::Redis with same get/set/incr/decr interface
- Add redis gem (~> 5.0) to Gemfile
- Update cable.yml to use Redis adapter in production (shared with counters)
- Document DrbCounter → Redis migration in broadcasting-migration.md
- Redis installed and running on vmi3300327
- Leave Faye as current broadcaster; both switches are one-line changes

DrbCounter problems solved:
  - In-memory → persistent (RDB + AOF)
  - Single-process DRb → multi-process safe Redis
  - Atomic INCR/DECR across Puma workers
  - One less custom process to manage
2026-05-17 16:42:09 +02:00

81 lines
2.4 KiB
Ruby

# frozen_string_literal: true
module Mozo
module Counter
# Redis-backed counter adapter. Replaces the DrbCounter (in-memory,
# single-process, DRb-based) with a persistent, multi-process-safe,
# battle-tested key-value store.
#
# Benefits over DrbCounter:
# - Persistent (survives restarts, no CouchDB reload dance)
# - Atomic INCR/DECR (no race conditions between Puma workers)
# - No separate process to manage (Redis is already needed for ActionCable)
# - Production-ready, widely deployed
#
# Usage:
# Mozo::Counter.connection = Mozo::Counter::Redis.new
# # or with custom config:
# Mozo::Counter.connection = Mozo::Counter::Redis.new(url: "redis://localhost:6379/2")
#
class Redis
def initialize(url: nil)
require 'redis'
@redis = ::Redis.new(url: url || ENV.fetch('REDIS_URL', 'redis://localhost:6379/0'))
end
def get(key, options = {})
value = @redis.get(key)
quiet = options[:quiet]
unless quiet
Rails.logger.debug("[REDIS_COUNTER] GET #{key} = #{value.inspect}")
end
value&.to_i || 0
rescue => e
Rails.logger.error("[REDIS_COUNTER] GET #{key} failed: #{e.message}")
0
end
def set(key, value)
Rails.logger.debug("[REDIS_COUNTER] SET #{key} = #{value}")
@redis.set(key, value)
rescue => e
Rails.logger.error("[REDIS_COUNTER] SET #{key} failed: #{e.message}")
value
end
def incr(key, options = {})
initial = options[:initial] || 1
Rails.logger.debug("[REDIS_COUNTER] INCR #{key}")
if @redis.exists?(key)
@redis.incr(key)
else
@redis.set(key, initial)
initial
end
rescue => e
Rails.logger.error("[REDIS_COUNTER] INCR #{key} failed: #{e.message}")
initial
end
def decr(key, options = {})
initial = options[:initial] || 0
Rails.logger.debug("[REDIS_COUNTER] DECR #{key}")
if @redis.exists?(key)
@redis.decr(key)
else
@redis.set(key, initial)
initial
end
rescue => e
Rails.logger.error("[REDIS_COUNTER] DECR #{key} failed: #{e.message}")
initial
end
def flush
Rails.logger.debug("[REDIS_COUNTER] FLUSHDB")
@redis.flushdb
end
end
end
end