Created
May 7, 2026 10:49
-
-
Save milushov/46554e490102c4fdb419129277c8a2e4 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <title>CA-5591/CA-5592 Architecture</title> | |
| <style> | |
| * { margin: 0; padding: 0; box-sizing: border-box; } | |
| body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #0f172a; color: #e2e8f0; padding: 40px; } | |
| h1 { text-align: center; margin-bottom: 8px; font-size: 24px; color: #f8fafc; } | |
| .subtitle { text-align: center; color: #94a3b8; margin-bottom: 40px; font-size: 14px; } | |
| .container { display: flex; gap: 40px; max-width: 1200px; margin: 0 auto; } | |
| .project { flex: 1; border: 2px solid #334155; border-radius: 12px; padding: 24px; position: relative; } | |
| .project-title { position: absolute; top: -14px; left: 20px; background: #0f172a; padding: 0 12px; font-weight: 700; font-size: 14px; letter-spacing: 1px; } | |
| .rgs .project-title { color: #60a5fa; } | |
| .games .project-title { color: #34d399; } | |
| .box { border-radius: 8px; padding: 16px; margin-bottom: 16px; } | |
| .job { background: #1e293b; border-left: 4px solid #60a5fa; } | |
| .service { background: #1e293b; border-left: 4px solid #f59e0b; } | |
| .controller { background: #1e293b; border-left: 4px solid #34d399; } | |
| .worker { background: #1e293b; border-left: 4px solid #a78bfa; } | |
| .model { background: #1e293b; border-left: 4px solid #f472b6; } | |
| .box-title { font-weight: 700; font-size: 13px; margin-bottom: 8px; display: flex; align-items: center; gap: 8px; } | |
| .box-title .tag { font-size: 10px; padding: 2px 8px; border-radius: 4px; font-weight: 600; } | |
| .tag-job { background: #1e3a5f; color: #60a5fa; } | |
| .tag-service { background: #422006; color: #f59e0b; } | |
| .tag-controller { background: #064e3b; color: #34d399; } | |
| .tag-worker { background: #2e1065; color: #a78bfa; } | |
| .tag-model { background: #500724; color: #f472b6; } | |
| .box-body { font-size: 12px; color: #94a3b8; line-height: 1.6; } | |
| .box-body code { background: #334155; padding: 1px 6px; border-radius: 4px; font-size: 11px; color: #e2e8f0; } | |
| .arrow-section { display: flex; align-items: center; justify-content: center; margin: 12px 0; } | |
| .arrow { color: #475569; font-size: 20px; } | |
| .http-bridge { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; min-width: 80px; } | |
| .http-arrow { font-size: 32px; color: #f59e0b; } | |
| .http-label { font-size: 10px; color: #f59e0b; font-weight: 700; letter-spacing: 1px; } | |
| .http-detail { font-size: 10px; color: #94a3b8; text-align: center; max-width: 120px; } | |
| .flow-num { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; border-radius: 50%; font-size: 11px; font-weight: 700; margin-right: 6px; flex-shrink: 0; } | |
| .flow-num-blue { background: #1e3a5f; color: #60a5fa; } | |
| .flow-num-green { background: #064e3b; color: #34d399; } | |
| .legend { max-width: 1200px; margin: 32px auto 0; display: flex; gap: 24px; justify-content: center; flex-wrap: wrap; } | |
| .legend-item { display: flex; align-items: center; gap: 8px; font-size: 12px; color: #94a3b8; } | |
| .legend-dot { width: 12px; height: 12px; border-radius: 3px; } | |
| .note { max-width: 1200px; margin: 24px auto 0; padding: 16px; background: #1e293b; border: 1px solid #334155; border-radius: 8px; } | |
| .note-title { font-size: 13px; font-weight: 700; color: #f59e0b; margin-bottom: 8px; } | |
| .note-body { font-size: 12px; color: #94a3b8; line-height: 1.8; } | |
| .note-body li { margin-left: 16px; } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>CA-5591 / CA-5592 — Game Tables Cleanup</h1> | |
| <p class="subtitle">Delete unused game tables (no traffic) • Archive game tables with traffic outside required currencies</p> | |
| <div class="container"> | |
| <!-- RGS --> | |
| <div class="project rgs"> | |
| <div class="project-title">RGS BACKEND (client-area)</div> | |
| <div class="box job"> | |
| <div class="box-title"> | |
| <span class="flow-num flow-num-blue">1</span> | |
| <span>GameTables::CleanupKickoffJob</span> | |
| <span class="tag tag-job">JOB</span> | |
| </div> | |
| <div class="box-body"> | |
| Cron or manual trigger.<br> | |
| Iterates <code>Environment.find_each</code><br> | |
| Skips envs without qualifying servers.<br> | |
| Enqueues <code>CleanupJob</code> per environment. | |
| </div> | |
| </div> | |
| <div class="arrow-section"><span class="arrow">↓ perform_async(env.id)</span></div> | |
| <div class="box job"> | |
| <div class="box-title"> | |
| <span class="flow-num flow-num-blue">2</span> | |
| <span>GameTables::CleanupJob</span> | |
| <span class="tag tag-job">JOB</span> | |
| </div> | |
| <div class="box-body"> | |
| Finds next active server (chains via <code>perform_async</code>).<br> | |
| Delegates to <code>GameTables::Cleanup</code> service.<br> | |
| Logs failures, chains to next server. | |
| </div> | |
| </div> | |
| <div class="arrow-section"><span class="arrow">↓ Cleanup.call(server:, environment:)</span></div> | |
| <div class="box service"> | |
| <div class="box-title"> | |
| <span class="flow-num flow-num-blue">3</span> | |
| <span>GameTables::Cleanup</span> | |
| <span class="tag tag-service">SERVICE</span> | |
| </div> | |
| <div class="box-body"> | |
| Queries <code>GameTable</code> outside <code>required_currencies</code>.<br> | |
| Loads traffic from <code>AggregatedGGR</code>.<br> | |
| Splits into <b>to_delete</b> (no traffic) / <b>to_archive</b> (with traffic).<br> | |
| Sends <code>bo_id</code>s to Games API in slices of 500.<br> | |
| <code>async: true</code> in every request. | |
| </div> | |
| </div> | |
| </div> | |
| <!-- HTTP Bridge --> | |
| <div class="http-bridge" style="padding-top: 200px;"> | |
| <div class="http-label">HTTP POST</div> | |
| <div class="http-arrow">⟶</div> | |
| <div class="http-detail"> | |
| /api/v1/client_area/<br>game_tables/cleanup<br><br> | |
| <code>{ game_table_ids,<br>action_type,<br>async: true }</code> | |
| </div> | |
| <div style="margin-top: 8px; font-size: 10px; color: #64748b;">max 500 IDs/req</div> | |
| </div> | |
| <!-- Games --> | |
| <div class="project games"> | |
| <div class="project-title">GAMES PROJECT</div> | |
| <div class="box controller"> | |
| <div class="box-title"> | |
| <span class="flow-num flow-num-green">4</span> | |
| <span>GameTablesController#cleanup</span> | |
| <span class="tag tag-controller">CONTROLLER</span> | |
| </div> | |
| <div class="box-body"> | |
| Validates IDs limit (500).<br> | |
| Creates <code>CaIntegrationMessage</code>.<br> | |
| <b>Async:</b> creates <code>CaAsyncOperation</code>, enqueues worker.<br> | |
| <b>Sync:</b> calls service directly, returns result. | |
| </div> | |
| </div> | |
| <div class="arrow-section"><span class="arrow">↓ perform_async(operation.id)</span></div> | |
| <div class="box worker"> | |
| <div class="box-title"> | |
| <span class="flow-num flow-num-green">5</span> | |
| <span>GameTables::CleanupWorker</span> | |
| <span class="tag tag-worker">WORKER</span> | |
| </div> | |
| <div class="box-body"> | |
| Locked: <code>lock: :while_executing</code> (SidekiqUniqueJobs).<br> | |
| Reads params from <code>CaAsyncOperation</code> → <code>CaIntegrationMessage.body</code>.<br> | |
| Updates operation status: <code>received → in_progress → processed/failed</code>. | |
| </div> | |
| </div> | |
| <div class="arrow-section"><span class="arrow">↓ Cleanup.call(...)</span></div> | |
| <div class="box service"> | |
| <div class="box-title"> | |
| <span class="flow-num flow-num-green">6</span> | |
| <span>GameTables::Cleanup</span> | |
| <span class="tag tag-service">SERVICE</span> | |
| </div> | |
| <div class="box-body"> | |
| <b>Delete:</b> <code>destroy!</code> per record → PaperTrail + callbacks.<br> | |
| Error-tolerant: continues on failure, collects errors.<br> | |
| <b>Archive:</b> <code>update!</code> per record in <b>transaction</b>.<br> | |
| Sets <code>status: 'archived', archived_at:, active: false</code>.<br> | |
| Rolls back ALL on any failure.<br> | |
| Batches of 100, <code>sleep(0.5)</code> between batches. | |
| </div> | |
| </div> | |
| <div class="arrow-section"><span class="arrow">↓</span></div> | |
| <div class="box model"> | |
| <div class="box-title"> | |
| <span>GameTable</span> | |
| <span class="tag tag-model">MODEL</span> | |
| </div> | |
| <div class="box-body"> | |
| <code>has_paper_trail</code> — versions tracked.<br> | |
| Callbacks fire on <code>destroy!</code> / <code>update!</code>.<br> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="legend"> | |
| <div class="legend-item"><div class="legend-dot" style="background:#60a5fa"></div> Sidekiq Job</div> | |
| <div class="legend-item"><div class="legend-dot" style="background:#f59e0b"></div> Service</div> | |
| <div class="legend-item"><div class="legend-dot" style="background:#34d399"></div> Controller</div> | |
| <div class="legend-item"><div class="legend-dot" style="background:#a78bfa"></div> Worker (throttled)</div> | |
| <div class="legend-item"><div class="legend-dot" style="background:#f472b6"></div> Model</div> | |
| </div> | |
| <div class="note"> | |
| <div class="note-title">Key Design Decisions</div> | |
| <div class="note-body"> | |
| <ul> | |
| <li><b>Delete</b> uses <code>destroy!</code> (not <code>delete_all</code>) — triggers PaperTrail + callbacks per reviewer feedback</li> | |
| <li><b>Archive</b> wraps in a <b>transaction</b> — if one record fails, ALL roll back (data consistency)</li> | |
| <li><b>Delete</b> is error-tolerant — continues on individual failures, collects errors</li> | |
| <li>RGS chains servers one-by-one via <code>perform_async</code> (CA-5072 pattern) — no DB overload</li> | |
| <li>Games worker uses <b>lock: :while_executing</b> (SidekiqUniqueJobs) — one job at a time</li> | |
| <li><code>CaIntegrationMessage</code> + <code>CaAsyncOperation</code> — full audit trail, status tracking</li> | |
| <li>Max <b>500 IDs per request</b> — enforced on both sides</li> | |
| </ul> | |
| </div> | |
| </div> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment