跳到正文
cccskills
免费在 GitHub 公开

rfdb-write-lock-edge-flush-gotcha

RFDB server becomes unresponsive (60s+ timeouts) during enricher addEdges write storms. Use when: (1) RFDB CPU spikes to 60-90% after addEdges calls; (2) subsequent reads time out at 60s; (3) server stays catatonic for 5-15 min after a write storm; (4) Tokio main thread parked (write-lock contention). Root cause: add_edges() calls maybe_auto_flush() which has a memory-pressure path that fires on NODE count — not edge count — and holds the exclusive write lock during disk I/O. Fix: separate maybe_auto_flush_edges() uses byte-limit only.

中文简介正在准备中。当前显示原文说明。

查看安装方法

包含的文件(1)

  • SKILL.md4.7 KB

SKILL.md(原文)

安装前可以查看交给 AI 代理的指令内容。

RFDB Write Lock Edge-Flush Gotcha (RFD-67)

Problem

RFDB server saturates during enricher write storms. A single addEdges call can hold the exclusive write lock (db.engine.write()) for seconds while flushing data to disk, blocking ALL reads and other writes during that time.

Context / Trigger Conditions

  • Enrichers (behaviorEnricher, packageApiEnricher) call client.addEdges() repeatedly
  • Server was previously used for grafema analyze (analysis nodes still in write buffer)
  • System memory > 70% used (very common on dev machines)
  • First addEdges call spikes CPU; subsequent calls time out at 60s

Root Cause (code trace)

client.addEdges(10 edges)
  → with_engine_write()               ← acquires EXCLUSIVE write lock
    → engine.add_edges()
      → store.upsert_edges()          ← fast, in-memory (~0.01ms)
      → self.maybe_auto_flush()       ← ← ← THE BUG
        Path 2: memory_pressure > 0.7 && total_write_buffer_nodes() >= 500
          → store.flush_all()         ← DISK I/O (seconds) under write lock!
  → lock released

maybe_auto_flush has two paths:

  1. Buffer overflow: any_shard_needs_flush(node_limit, byte_limit) — fine
  2. Memory pressure: memory_pressure > 0.7 && total_write_buffer_nodes() >= 500 — BUG

Path 2 fires based on nodes, not edges. After grafema analyze, the write buffer holds analysis nodes. The enricher's first addEdges call triggers a full flush of all those analysis nodes — the edge write causes a massive node flush.

Files involved:

  • packages/rfdb-server/src/graph/engine_v2.rs — add_edges(), maybe_auto_flush()
  • packages/rfdb-server/src/bin/rfdb_server.rs — with_engine_write() (line ~2289)
  • packages/rfdb-server/src/database_manager.rs — Database.engine: RwLock<Box<dyn GraphStore>>

Fix (applied in RFD-67)

Replace maybe_auto_flush() call in add_edges with maybe_auto_flush_edges():

fn maybe_auto_flush_edges(&mut self) {
    // Byte-limit only — never fires on memory pressure / node count.
    // Prevents edge writes from triggering node flushes under write lock.
    if self.store.any_shard_needs_flush(usize::MAX, self.cached_profile.write_buffer_byte_limit) {
        if let Err(e) = self.store.flush_all(&mut self.manifest) {
            tracing::warn!("auto-flush (edges) failed: {}", e);
        }
    }
}

Key invariant: add_nodes keeps maybe_auto_flush() (nodes are 120 bytes, OOM risk is real). add_edges uses maybe_auto_flush_edges() (edges are 98 bytes, byte limit = 100MB default).

Verification

Test: test_edge_writes_do_not_block_reads in engine_v2.rs — wraps engine in RwLock, runs concurrent writer + reader threads, asserts reads complete in < 1s during edge storm.

Enricher Contract After Fix

Enrichers must call Flush explicitly after their write phase:

await enrichBehaviors(client, lookup, { flushBatchSize: 50 });
await client.flush();  // required — edges won't auto-flush during normal write storms

Recommended flushBatchSize: 50–200 edges per batch. Under 50: excessive socket round-trips. Over 200: diminishing returns, larger per-request latency.

Diagnosis Checklist

If RFDB saturates again:

  1. RFDB_VERBOSE=1 — log every request; look for AddEdges/Flush taking > 1s
  2. GetStats → shardDiagnostics.writeBufferNodes — high = unflushed analysis nodes
  3. samply record on rfdb-server PID → look for flush_with_ids holding write lock
  4. Check if enricher calls client.flush() after writes

Notes

The fundamental fix (moving disk I/O off the write-lock path entirely via background flush thread) is deferred. The current fix eliminates the specific bug: edge writes triggering node flushes. If enrichers begin emitting both nodes AND edges, the add_nodes memory-pressure path could trigger — at that point a background flush thread becomes necessary.

Safe write throughput: up to ~100K addEdges batches (at 10 edges each) before auto-flush. That's 1M edges before the byte limit triggers. No enricher should hit this in practice.

评价

还没有评价。欢迎分享你的使用感受。

评论

无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。

还没有评论。来写第一条吧。

提交后会立即公开。请勿填写邮箱、电话等个人信息。URL 最多 2 个。

0 / 1000 字

此表单受 reCAPTCHA 保护,适用 Google 的隐私权政策和服务条款。

同一仓库的技能

Fix Elixir/Erlang AST processing bugs in Grafema beam-analyzer. Use when: (1) Elixir parser returns MODULE node but 0 functions/calls — body nesting issue, (2) Erlang parser crashes with "cannot convert list to string" on OTP 26+ — location format changed from integer to keyword list, (3) pipe operator |> creates spurious CALL nodes instead of desugared function calls — clause ordering bug, (4) multi-module .ex files return only the first module — missing __block__ handler, (5) installing Erlang/Elixir on macOS with outdated Xcode/Clang.

中文简介正在准备中。当前显示原文说明。

Disentinel/grafema352026年8月24日 更新

Fires after fixing any non-trivial bug or regression. Asks: could the graph have caught this as a guarantee? Pairs with reflection-in-and-on-action — picks up after "earliest catchable signal" and asks the next question: was that signal expressible in graph? Triggers: (1) after any non-trivial bug fix is verified working, (2) after a regression report (something used to work, broke), (3) during step 6 (knowledge extraction) of the workflow, (4) when reflection-on-action surfaces a "would have been catchable" signal. Outcome is a triage decision (graph-reachable? rule expressible? rule sound?) and either a draft Linear issue + guarantee proposal, or a recorded "graph capability gap" note. Never auto-creates guarantees.

中文简介正在准备中。当前显示原文说明。

Disentinel/grafema352026年8月24日 更新

Fix Playwright automation failures against code-server (VS Code in browser). Use when: (1) trust dialog blocks all clicks — "monaco-dialog-modal-block intercepts pointer events", (2) button:has-text() finds wrong buttons behind modal dialog, (3) keyboard shortcuts don't work — Meta vs Control inconsistency, (4) VS Code extension activity bar icon not found by aria-label, (5) panels show placeholder text despite extension being "Connected". Covers trust dialog dismissal, keyboard shortcut hybrid mode, extension panel selectors, and database connection verification for Grafema extension.

中文简介正在准备中。当前显示原文说明。

Disentinel/grafema352026年8月24日 更新

Systematic methodology for achieving 100% backward dataflow reachability in a new language. Create gauntlet fixture, write trace, diagnose gaps, fix analyzer/algorithm, iterate to 100%. Language-agnostic process. Use when: (1) adding a new language to Grafema, (2) auditing dataflow coverage for existing language, (3) user says "/dataflow-gauntlet".

中文简介正在准备中。当前显示原文说明。

Disentinel/grafema352026年8月24日 更新

Fix docker exec hanging when starting background processes (servers, daemons) inside containers. Use when: (1) docker exec never returns despite using & or nohup, (2) background server started in container causes docker exec to hang indefinitely, (3) env_startup_command in SWE-bench or similar frameworks times out, (4) setsid/disown needed for proper process detachment in Docker. Root cause: docker exec tracks ALL processes in the exec session, not just the top-level PID.

中文简介正在准备中。当前显示原文说明。

Disentinel/grafema352026年8月24日 更新

Fix Node.js CLI tools crashing inside Docker containers when host-installed node_modules require a newer Node version than the container provides. Use when: (1) "SyntaxError: Invalid regular expression flags" with /v flag in string-width or similar packages, (2) node_modules installed on host with Node 20+ but container has Node 18, (3) `npm install` with file: protocol creates symlinks that break inside Docker, (4) pnpm workspace packages become broken symlinks in containers. Covers version detection, Node binary mounting, and npm install strategies for cross-version compatibility.

中文简介正在准备中。当前显示原文说明。

Disentinel/grafema352026年8月24日 更新

查看 Disentinel 的全部技能

报告此技能的问题