3e4bcc80c8
- 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
150 lines
4.5 KiB
Markdown
150 lines
4.5 KiB
Markdown
# Broadcasting: Faye → ActionCable Migration Guide
|
|
|
|
## Current state
|
|
|
|
```
|
|
Model (SimplyStored::Couch)
|
|
→ ApplicationController.new.broadcast_user # ⚠️ anti-pattern
|
|
→ Mozo.broadcast_user
|
|
→ Mozo::Broadcaster::Faye.new.broadcast # HTTP POST to Faye
|
|
→ Faye server (Thin, port 9296)
|
|
→ WebSocket → browser clients
|
|
```
|
|
|
|
## Target state
|
|
|
|
```
|
|
Model (Broadcastable concern)
|
|
→ Mozo.broadcast_user
|
|
→ Mozo::Broadcaster::ActionCable.new.broadcast # in-process async
|
|
→ ActionCable (Rails built-in)
|
|
→ WebSocket → browser clients
|
|
```
|
|
|
|
## What this branch adds
|
|
|
|
| File | Purpose |
|
|
|------|---------|
|
|
| `lib/mozo/broadcaster/action_cable.rb` | Drop-in ActionCable broadcaster adapter |
|
|
| `config/cable.yml` | ActionCable configuration (async for single-server) |
|
|
| `app/channels/application_cable/connection.rb` | WebSocket auth via auth_token |
|
|
| `app/channels/mozo_channel.rb` | Channel authorization for user/supplier/employee |
|
|
| `app/models/concerns/broadcastable.rb` | Clean module for models (replaces old monkey-patch) |
|
|
| `config/routes.rb` | Mounts `/cable` WebSocket endpoint |
|
|
| `config/initializers/model_broadcast.rb` | Fixed: delegates to Mozo directly (no more `ApplicationController.new`) |
|
|
|
|
## How to switch
|
|
|
|
### 1. Server (one-line change)
|
|
|
|
In `config/initializers/mozo_settings.rb`, change:
|
|
|
|
```ruby
|
|
# Mozo.broadcaster = Mozo::Broadcaster::Faye.new # old
|
|
Mozo.broadcaster = Mozo::Broadcaster::ActionCable.new # new
|
|
```
|
|
|
|
### 2. Client (mozo-user / mozo-supplier)
|
|
|
|
**Old Faye client (conceptual):**
|
|
```js
|
|
var client = new Faye.Client('https://events.mozo.bar/faye');
|
|
client.subscribe('/user/123', function(msg) { ... });
|
|
```
|
|
|
|
**New ActionCable client:**
|
|
```js
|
|
// Using @rails/actioncable npm package
|
|
import { createConsumer } from "@rails/actioncable";
|
|
|
|
const consumer = createConsumer(
|
|
`wss://mozo.bar/cable?auth_token=${authToken}`
|
|
);
|
|
|
|
consumer.subscriptions.create(
|
|
{ channel: "MozoChannel", id: "user_123" },
|
|
{
|
|
received(data) {
|
|
// data = { event: "list_closed", data: { id: 42 } }
|
|
handleEvent(data.event, data.data);
|
|
}
|
|
}
|
|
);
|
|
```
|
|
|
|
### 3. Remove Faye
|
|
|
|
Once stable:
|
|
- Remove `gem 'faye'` from Gemfile
|
|
- Remove `faye/` directory
|
|
- Remove nginx `events.mozo.bar` vhost
|
|
- Stop the Faye Thin process
|
|
|
|
## Benefits
|
|
|
|
- **No extra process** — ActionCable runs inside Puma
|
|
- **Async** — `broadcast` is non-blocking
|
|
- **Simpler deploys** — one less service to manage
|
|
- **WebSocket native** — no long-polling fallback complexity
|
|
- **Rails auth** — cookies/sessions work automatically
|
|
|
|
---
|
|
|
|
# Counter: DrbCounter → Redis Migration
|
|
|
|
## Current state
|
|
|
|
```
|
|
Supplier::Counters (app/models/supplier/counters.rb)
|
|
→ Mozo::Counter.incr/decr/get/set
|
|
→ Mozo::Counter.connection (Mozo::DrbCounter.object)
|
|
→ DRb → druby://localhost:9022
|
|
→ InMemoryQCounter (separate Ruby process)
|
|
→ on startup: reloads counts from CouchDB
|
|
→ in-memory only (lost on restart)
|
|
```
|
|
|
|
## Problems
|
|
|
|
1. **In-memory only** — restart the DRb process = lose all counts until CouchDB reload
|
|
2. **Single-process** — DRb runs one Ruby process, single point of failure
|
|
3. **Separate process** — another thing to monitor, deploy, and restart
|
|
4. **Race conditions** — between Puma workers, increment/decrement is not atomic across the DRb boundary
|
|
5. **Custom code** — `InMemoryQCounter` is 100 lines of hand-rolled counter logic
|
|
|
|
## Target state
|
|
|
|
```
|
|
Supplier::Counters
|
|
→ Mozo::Counter.incr/decr/get/set
|
|
→ Mozo::Counter.connection (Mozo::Counter::Redis.new)
|
|
→ Redis (localhost:6379)
|
|
→ persistent, atomic, multi-process safe
|
|
```
|
|
|
|
## How to switch
|
|
|
|
In `config/initializers/mozo_settings.rb`, change:
|
|
|
|
```ruby
|
|
# Mozo::Counter.connection = Mozo::DrbCounter.object # old
|
|
Mozo::Counter.connection = Mozo::Counter::Redis.new # new
|
|
```
|
|
|
|
That's it. All existing `Mozo::Counter.get/set/incr/decr` calls work unchanged.
|
|
|
|
## What Redis provides
|
|
|
|
- **Atomic INCR/DECR** — no race conditions
|
|
- **Persistence** — RDB snapshots + AOF, survives restarts
|
|
- **Multi-process** — all Puma workers share the same Redis
|
|
- **Already needed** — ActionCable uses Redis for pub/sub in production
|
|
- **Battle-tested** — millions of deployments
|
|
|
|
## Migration steps
|
|
|
|
1. `apt-get install redis-server` — already done on vmi3300327
|
|
2. `gem 'redis', '~> 5.0'` — added to Gemfile
|
|
3. Switch `Mozo::Counter.connection` — one-line change in mozo_settings.rb
|
|
4. Stop the DRb counter process (`drb_counter/drb_counter.rb`)
|