analyze-read-replica-risk

Installation
SKILL.md

Analyze Read Replica Risk

Surge.ReadRepo is a read replica with ~20ms average lag behind the primary Surge.Repo. Moving a query to it (Repo.all/one/get/preloadReadRepo.*) is safe ONLY if every caller can tolerate reading data that may be up to ~20ms stale. Your job: given a query function, trace its full usage context and check each call path against the red flags below, then report which paths (if any) trip one.

This is a procedural scan, not a data-imagination exercise. Don't enumerate every way a stale field or a missing row could ripple through the system. Instead, walk each path and ask one question: does it match one of the red-flag patterns below? The red flags are the structural signatures of a lag problem - if a path matches none of them and fits a green-light signal, it's safe.

Workflow

  1. Locate the query. Find the function definition the user named. Confirm it actually runs DB reads and isn't already on ReadRepo.

  2. Trace the full call graph - not just direct callers. This is the core of the task. Lag risk usually lives several layers up, not at the query itself. Be exhaustive: under-counting paths is the most common way this analysis goes wrong.

    • Find direct callers (Grep the function name across lib/; test/ is for context only).
    • For each caller, find ITS callers. Repeat up the chain - parents, grandparents, and beyond - until you reach a stable entry point (controller action, LiveView event, Oban worker perform/1, channel handler, webhook handler, GenServer callback, public context function with many external callers).
    • Don't stop at an Oban worker perform/1 - climb to who enqueues the job. A worker is a fan-in point: Grep for the worker module (.new(, .insert(, Oban.insert) to find every enqueue site. Different entry points (e.g. different provider webhooks) often funnel into one worker, and each is a distinct path. The enqueue-then-job hop is itself a timing gap where the job can start within ms of the enqueuing write.
    • Expand dispatcher/multiplexer functions. A single function (e.g. a controller create/2, a catch-all webhook handler, a handle_event/3) often serves many event types or routes. Treat each meaningfully different trigger as its own path rather than collapsing them.
    • Follow shared helpers and defdelegates. If the query is reached through a wrapper, plug, or delegated function, trace callers of the wrapper too.
    • Stop climbing a branch only when you understand what triggers it. If a branch fans out very widely into clearly equivalent leaves, note the entry-point category once - but only after confirming the leaves really are equivalent.
  3. Check each call path against the red flags below. For each path, scan for the red-flag patterns. Track each distinct path separately - one path may trip a flag while another is clean. A single path that trips a "do NOT move" flag means the query stays on primary (unless you split it).

Installs
4
First Seen
Jun 21, 2026
analyze-read-replica-risk — jasonharmongit/jh-skills