惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

宝玉的分享
宝玉的分享
Recent Commits to openclaw:main
Recent Commits to openclaw:main
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
Tailwind CSS Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
罗磊的独立博客
V
Visual Studio Blog
爱范儿
爱范儿
H
Help Net Security
J
Java Code Geeks
I
InfoQ
Recent Announcements
Recent Announcements
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Recorded Future
Recorded Future
Jina AI
Jina AI
Microsoft Security Blog
Microsoft Security Blog
WordPress大学
WordPress大学
GbyAI
GbyAI
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Y
Y Combinator Blog
Google DeepMind News
Google DeepMind News
Scott Helme
Scott Helme
S
SegmentFault 最新的问题
S
Securelist
L
LINUX DO - 热门话题
Cyberwarzone
Cyberwarzone
C
Cisco Blogs
Simon Willison's Weblog
Simon Willison's Weblog
G
Google Developers Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 叶小钗
T
The Blog of Author Tim Ferriss
博客园_首页
B
Blog
F
Fortinet All Blogs
AWS News Blog
AWS News Blog
V
Vulnerabilities – Threatpost
S
Secure Thoughts
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Forbes - Security
Forbes - Security
S
Security @ Cisco Blogs
T
Threat Research - Cisco Blogs
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
S
Schneier on Security
Project Zero
Project Zero
Martin Fowler
Martin Fowler
C
Cybersecurity and Infrastructure Security Agency CISA
N
Netflix TechBlog - Medium
N
News and Events Feed by Topic

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Three Durability Modes, One WAL: Configurable Guarantees for Different Workloads
Loïc Baumann · 2026-05-20 · via DEV Community

Loïc Baumann

💡Typhon is an embedded, persistent, ACID database engine written in .NET that speaks the native language of game servers and real-time simulations: entities, components, and systems.

It delivers full transactional safety with MVCC snapshot isolation at sub-microsecond latency, powered by cache-line-aware storage, zero-copy access, and configurable durability.

Series: A Database That Thinks Like a Game Engine

  1. Why I'm Building a Database Engine in C#
  2. What Game Engines Know About Data That Databases Forgot
  3. Microsecond Latency in a Managed Language
  4. Deadlock-Free by Construction
  5. Three Durability Modes, One WAL (this post)
  6. MVCC at Microsecond Scale (coming soon)

Octocat GitHub repo  •  📬 Subscribe via RSS

Most databases pick one durability strategy at boot time. Typhon picks one per commit — and the surprising part isn't the user-facing API, it's that all three modes share the same WAL (Write-Ahead Log — every commit appends a record here before being durable) writer thread, the same ring buffer, the same I/O path. The only thing that differs is whether the producer waits.

Ten thousand NPC position updates per simulation tick at ~1-2µs each, all Deferred. One legendary item drop on the same transaction code path, escalated to Immediate, paying ~15-85µs for a guaranteed FUA (Force Unit Access — don't ack the write until it's on stable media) write to disk. Same engine, same WAL, one extra argument at Commit(). This post is about why that's possible and what it cost to keep it that way.

The other classical knob

Post #4 covered Typhon's first big architectural bet — eliminating deadlocks at the design level instead of detecting them at runtime. This one is about the other classical database knob: durability. And like deadlocks, the interesting decision is upstream of the implementation.

Three workloads sit on the same engine in a real game server:

  • Simulation tick: physics, AI, position updates. Hundreds to thousands of writes per frame. Losing the last tick on crash is fine; the simulation will recompute it.
  • Player actions: combat events, item pickups, dialogue state. Sub-5ms data loss window is acceptable. Throughput matters more than per-commit durability.
  • Money and rare events: currency transfers, legendary drops, account creation. Zero tolerance for loss. Players will dispute every missing transaction.

A single global durability setting forces all three to the most conservative option — and ~15-85µs per FUA write turns a 60Hz tick budget (16ms) into a fight you've already lost.

The three modes

The decision lives on the Unit of Work (UoW) at creation time, with a per-transaction override for escalation only. A UoW sits one level above a transaction: it groups one or more transactions and owns the durability contract they share. Transactions still commit atomic state changes; the UoW decides when — and whether — those commits reach disk. The user-facing enum is exactly what it looks like:

public enum DurabilityMode : byte
{
    /// <summary>WAL records buffered. Durable only after explicit Flush()/FlushAsync().
    /// Commit latency: ~1-2µs. Data-at-risk: until Flush().</summary>
    Deferred = 0,

    /// <summary>WAL writer auto-flushes every N ms (default 5ms).
    /// Commit latency: ~1-2µs. Data-at-risk: ≤ GroupCommitInterval.</summary>
    GroupCommit = 1,

    /// <summary>FUA on every tx.Commit(). Blocks until WAL record is on stable media.
    /// Commit latency: ~15-85µs. Data-at-risk: zero.</summary>
    Immediate = 2,
}

public enum DurabilityOverride : byte
{
    Default   = 0,  // Use the UoW's DurabilityMode
    Immediate = 1,  // Force FUA for this specific commit (escalation only)
}

Enter fullscreen mode Exit fullscreen mode

The override can only escalate. A Deferred UoW can promote one transaction to Immediate; an Immediate UoW cannot weaken anything. This is a deliberate constraint, not an oversight — it makes data-loss bugs impossible by API shape. You can never accidentally make a transaction less durable than the UoW's contract.

Mode Commit latency Data-at-risk window Use case
Deferred ~1-2µs Until explicit Flush() Game ticks, batch imports, simulation steps
GroupCommit ~1-2µs amortized ≤ 5ms (configurable) General server load, request handlers
Immediate ~15-85µs Zero Trades, account writes, legendary drops

One writer thread, three signaling patterns

Here's the part that surprised me when I came back to the design six months in: I did not need three I/O paths. Or three threads. Or three buffers. The shared infrastructure looks like this:


Three durability modes converging on one WAL Writer — Deferred, GroupCommit, and Immediate producers all publish to the same MPSC ring buffer; only Immediate also signals the writer and waits for DurableLsn to advance past its LSN; the GroupCommit timer wakes the writer on a 5ms ceiling; one writer thread, one segment file, one FUA write per drained batch

The same picture broken into phases:

# Producer thread (tx.Commit()) What this means for you WAL Writer thread (single, dedicated) What this means for you
1 TryClaim() — CAS (Compare-And-Swap) slot allocation Your commit atomically reserves a slot in the WAL ring buffer. No lock contention with other transactions claiming slots in parallel. (idle, or finishing a previous drain) The writer thread runs independently. Your producer never waits on it to claim a slot.
2 Publish() — release-store the frame header The record is now visible to the writer. Your commit has an LSN (Log Sequence Number) — its position in the durability timeline. TryDrain() — contiguous batch of published frames The writer harvests every published slot in one pass. This is the structural reason GroupCommit is amortized: N producers, one drain, one FUA cost.
3 Mode-specific: return now, or WaitForDurable(lsn) Deferred / GroupCommit: Commit() returns in ~1-2µs and durability lands asynchronously. Immediate: Commit() returns only once your LSN is on disk (~15-85µs). WriteAligned() → FUA write → Interlocked.Exchange(DurableLsn)_durabilityEvent.Set() One physical FUA write per batch (~15-85µs, paid once). DurableLsn advances; any Immediate waiter whose LSN ≤ DurableLsn wakes and returns from tx.Commit().

What changes between the modes is only the producer side:

  • Deferred — Publish and return. No signal, no wait. The writer may be asleep; it will wake on the next group-commit timer or explicit Flush().
  • GroupCommit — Publish and return. The writer is already in a WaitForData(GroupCommitIntervalMs) loop; the next tick of that timer (≤5ms) drains the batch.
  • Immediate — Publish, signal the writer, then WaitForDurable(lsn) until DurableLsn advances past the producer's LSN.

That last one is where the elegance lives. It's not a separate code path — it's the same path, with one extra fast-path check:

public void WaitForDurable(long lsn, ref WaitContext ctx)
{
    // Fast path: already durable, returns inline.
    if (Interlocked.Read(ref _durableLsn) >= lsn)
    {
        return;
    }
    WaitForDurableSlow(lsn, ref ctx);
}

Enter fullscreen mode Exit fullscreen mode

If the WAL writer has already drained past your LSN by the time you call this — say, because someone else's Immediate commit just batched yours along with it — you pay one atomic read and a return. No event wait, no syscall, no context switch. Immediate mode is GroupCommit's batching benefit, available to the one transaction that needs it now.

That's the teaching moment I want this post to leave you with: per-transaction durability is not three implementations. It's one implementation with three producer-side policies, and the FUA cost is a property of the I/O path, not the API surface.

Per-UoW, not per-engine — why

The API-shape decision is recorded in its own ADR (Architecture Decision Record). I considered four alternatives before landing on per-UoW:

Alternative Why I rejected it
Per-database (boot-time) Too coarse. Game ticks and trades on the same DB need different modes within the same process.
Per-transaction Can't batch — GroupCommit is inherently multi-transaction. The UoW is the natural batching boundary.
Two modes (Sync / Async) Misses GroupCommit's sweet spot. The whole point is amortized FUA, which a binary doesn't capture.
Caller-managed flush only Error-prone. Developers forget to flush. GroupCommit automates the common case correctly.

The honest version: I tried "per-database" first because it was easiest to wire up, and immediately hit the simulation-vs-trade problem on the first benchmark. Two production game-server workloads on the same engine, one wanting ~1µs commits and one wanting zero data loss. The mode has to follow the workload, not the storage.

Numbers that matter

The latency table above is the headline. The throughput table is where it gets interesting:

Mode Single-thread durable tx/s Multi-thread durable tx/s
Deferred N/A (batch-durable) N/A
GroupCommit (5ms interval) ~200K+ amortized Millions (shared flush)
Immediate ~12K-65K ~12K-65K (FUA-limited)

Single-thread Immediate is capped by NVMe FUA round-trip — there's no software trick to escape that. But multi-thread Immediate does not scale linearly past one thread, because every commit is racing the same writer through the same I/O. GroupCommit, on the other hand, scales nearly with thread count because the FUA cost is paid once per drain cycle no matter how many producers contributed to the batch.

That's not a flaw in Immediate. It's the physics of the storage device. The point of having three modes is that you only pay that cost where you actually need it.

What I got wrong

The first group-commit timer was 1ms. Under low write load the WAL was doing constant small FUA writes — worst-case for SSD wear and tail latency. Tuning to 5ms with a "wake on N records OR T milliseconds" trigger fixed it: the writer sleeps on WaitForData(intervalMs) and gets pulled out early when a producer signals (Immediate commits, explicit Flush(), or back-pressure). Idle periods cost nothing; busy periods batch naturally.

The first override design allowed downgrade. tx.Commit(DurabilityOverride.Deferred) from an Immediate UoW. The use case was "this single read-mostly transaction doesn't really need FUA." The use case was wrong: the UoW's contract is the durability floor, not the default. Downgrading a single commit means the application has accidentally created a hole in a contract it thinks it has. Now overrides can only escalate, and the type system enforces it.

Deferred mode is a contract, not a latency number. Early users assumed Commit() always meant "on disk." It doesn't. Deferred mode says: your data is not durable until you call Flush() or close cleanly. For game servers that's fine; their tick loop already has clear boundaries. But the documentation now leads with the contract, not the µs number. The latency is a consequence; the contract is what you signed up for.

What's next

Post #6 in the series: Building a Page Cache That Doesn't Count: Epoch-Based Memory Management. The durability story above assumes the commit path is fast, but that's only half the story — the read path has to be just as cheap, and the trick there is replacing per-page reference counting with epoch-based protection: two atomic operations per transaction instead of two per page. The mechanism is elegant enough that it deserves its own post.

Follow the GitHub repo for source and benchmarks, or subscribe via RSS.