<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: maisonw</title>
    <description>The latest articles on DEV Community by maisonw (@shunhewang).</description>
    <link>https://dev.to/shunhewang</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3984210%2F94124239-dfd3-48a0-be08-7e6030b472af.png</url>
      <title>DEV Community: maisonw</title>
      <link>https://dev.to/shunhewang</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shunhewang"/>
    <language>en</language>
    <item>
      <title>Show HN: I replaced database deadlock victim selection with an LLM — here's the data</title>
      <dc:creator>maisonw</dc:creator>
      <pubDate>Sun, 14 Jun 2026 17:35:03 +0000</pubDate>
      <link>https://dev.to/shunhewang/show-hn-i-replaced-database-deadlock-victim-selection-with-an-llm-heres-the-data-3hch</link>
      <guid>https://dev.to/shunhewang/show-hn-i-replaced-database-deadlock-victim-selection-with-an-llm-heres-the-data-3hch</guid>
      <description>&lt;p&gt;Every mainstream database uses fixed rules for deadlock victim selection.&lt;/p&gt;

&lt;p&gt;MySQL kills the one with the fewest locks. CockroachDB kills the youngest. PostgreSQL kills the one that closed the cycle. Not one of them can explain "why you" — the killed transaction just knows it got rolled back, no reason given.&lt;/p&gt;

&lt;p&gt;I wanted to see what happens if an LLM makes that decision instead.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;DDA is a Sidecar process. It sits outside the database, polls lock state, builds a wait-for graph, runs DFS cycle detection, and — &lt;em&gt;only&lt;/em&gt; at the "choose a victim" step — calls an LLM (deepseek-v4-pro via Anthropic SDK). The other four steps (SQL execution, lock parsing, graph construction, rollback) are pure deterministic Python code.&lt;/p&gt;

&lt;p&gt;The database is rookieDB, Berkeley's CS186 teaching database. It has full multi-granularity locking (IS/IX/S/SIX/X) but exactly zero deadlock detection — the minimal experimental environment DDA needs. I chose it not because I couldn't touch a real database, but because its codebase (~tens of thousands of lines of Java) was readable enough to fully understand the locking system in my spare time. The Sidecar architecture doesn't bind to any database — adapting to PostgreSQL means swapping one data source. No database code changes needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Honest limitations upfront:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LLM latency is 2-4 seconds per call. Fixed rules are microseconds. This is a real tradeoff.&lt;/li&gt;
&lt;li&gt;rookieDB is a teaching system, not a production database.&lt;/li&gt;
&lt;li&gt;Only 3 deadlock scenarios. Not comprehensive.&lt;/li&gt;
&lt;li&gt;In production, deadlock &lt;em&gt;prevention&lt;/em&gt; (wound-wait) matters more than detection + rollback. Victim selection is a relatively niche problem.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But it's a real, finished project with some findings I think are interesting.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Experiment
&lt;/h2&gt;

&lt;p&gt;Three fixed-rule baselines first:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Strategy&lt;/th&gt;
&lt;th&gt;Logic&lt;/th&gt;
&lt;th&gt;Database Analogy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MinLocks&lt;/td&gt;
&lt;td&gt;Fewest locks → kill&lt;/td&gt;
&lt;td&gt;MySQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;YoungestFirst&lt;/td&gt;
&lt;td&gt;Most recently started → kill&lt;/td&gt;
&lt;td&gt;CockroachDB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CycleTrigger&lt;/td&gt;
&lt;td&gt;Deepest in wait queue → kill&lt;/td&gt;
&lt;td&gt;PostgreSQL/Oracle&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Then the LLM. 3 scenarios × 4 strategies = 12 runs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;12/12 deadlocks detected and resolved. Zero residual locks. Zero hallucinated victims. Zero fallbacks triggered.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The most telling data — &lt;code&gt;cascading_deadlock&lt;/code&gt; scenario. T1 holds 12 locks (2 exclusive X write locks), T2 holds 8 locks (1 exclusive X write lock):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;MinLocks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;      &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;T10&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;holds&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;8&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;locks&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;—&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;fewest"&lt;/span&gt;
&lt;span class="na"&gt;YoungestFirst&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;T22&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;started&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;at&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;—&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;youngest"&lt;/span&gt;  
&lt;span class="na"&gt;CycleTrigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;T33&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;last&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;in&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;request&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;queue"&lt;/span&gt;
&lt;span class="na"&gt;LLM&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;           &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;T124&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;holds&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;fewer&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;locks&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;(8&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;vs&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;12)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;only&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;one&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;X&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;write&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;lock&lt;/span&gt;
                &lt;span class="s"&gt;vs&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;T123's&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;two&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;—&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;lower&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;rollback&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;cost"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The LLM compared not just lock counts, but &lt;em&gt;lock type severity&lt;/em&gt;. It learned the hierarchy (X &amp;gt; SIX &amp;gt; S &amp;gt; IX &amp;gt; IS) from the system prompt and applied it to this specific scenario. Fixed rules can't do multi-dimensional reasoning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The symmetric case&lt;/strong&gt; was even more revealing. Two transactions: 8 locks each, identical start times, identical lock types. All three fixed rules degraded to tiebreaking — effectively random — but each still claimed it "followed the rule." The LLM was more honest:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Both transactions are symmetric: 8 locks each, including X and SIX, identical start times. Choosing T32 arbitrarily to break the deadlock."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Fixed rules offer false certainty. The LLM admits when nothing differentiates the candidates. I prefer the latter.&lt;/p&gt;




&lt;h2&gt;
  
  
  Hallucination Defense
&lt;/h2&gt;

&lt;p&gt;LLMs hallucinate, so I built three layers of defense:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Hard reject&lt;/strong&gt;: victim must be in the deadlock cycle — code validates this. If not → fallback to MinLocks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Soft warn&lt;/strong&gt;: &lt;code&gt;_verify_claims()&lt;/code&gt; regex-extracts specific numbers from the LLM's reason (lock counts, timestamps), cross-checks against actual snapshot data. Mismatches get a &lt;code&gt;[⚠]&lt;/code&gt; marker but don't reject the choice&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prevention&lt;/strong&gt;: the prompt includes a code-computed structured summary as ground truth, plus a direct instruction not to fabricate resource names or business meanings&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the LLM completely fails → 0ms fallback to MinLocks. System availability is unaffected.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bugs Worth Mentioning
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DeepSeek thinking mode&lt;/strong&gt;: v4-pro enables thinking by default. Thinking tokens count toward &lt;code&gt;max_tokens&lt;/code&gt;. My initial &lt;code&gt;max_tokens=1024&lt;/code&gt; was entirely consumed by thinking (~3500 tokens), leaving an empty text response. Set to &lt;code&gt;8192&lt;/code&gt; to fix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Content block structure&lt;/strong&gt;: thinking returns &lt;code&gt;type='thinking', text=None&lt;/code&gt; as the first content block. Calling &lt;code&gt;response.content[0].text&lt;/code&gt; — the standard SDK approach — returns None. The actual JSON is in a later &lt;code&gt;type='text'&lt;/code&gt; block. Wrote &lt;code&gt;_extract_text()&lt;/code&gt; to iterate all blocks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;httpx compatibility&lt;/strong&gt;: anthropic 0.39 is incompatible with httpx 0.28+ (removed &lt;code&gt;proxies&lt;/code&gt; parameter). Pinned &lt;code&gt;httpx&amp;lt;0.28&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;p&gt;Python 3 + asyncio + Anthropic SDK. rookieDB (Java) as the database backend.&lt;/p&gt;

&lt;p&gt;No LangChain, CrewAI, or any agent framework — the LLM appears at exactly one step.&lt;/p&gt;

&lt;p&gt;37 unit tests + 7 integration tests (live LLM calls). Full design docs and experiment reports in both English and Chinese. 6 architecture decision records (why I cut Multi-Agent, why Sidecar, why Rich-Context prompting, etc.).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub: &lt;a href="https://github.com/ShunheWang/dda" rel="noopener noreferrer"&gt;https://github.com/ShunheWang/dda&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;PostgreSQL adaptation — swap &lt;code&gt;\alllocks&lt;/code&gt; for &lt;code&gt;pg_locks + pg_stat_activity&lt;/code&gt;. Every other component (WFG, cycle detection, victim selection, rollback) fully reuses. Then a direct comparison: "PG's built-in detection chose T42, DDA's LLM chose T43 because..." — that's the data I actually want.&lt;/p&gt;

&lt;p&gt;If I can get transaction SQL text and affected row counts from the LockManager, the LLM could do even deeper analysis — "T1 updated the payments table but only 1 row affected vs T2 updating 5000 rows in orders."&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm currently looking for backend/infrastructure/database roles. Location flexible — based in Wuhan, open to Shanghai, Beijing, remote. 5 years backend experience, details in my GitHub profile and resume. If your team has something relevant, I'd love to talk.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>database</category>
      <category>llm</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
