Hosted ongabo.esvia theHypermedia Protocol

How Erlang Beam fixes this

BEAM avoids this class of problem mostly by not letting shared mutable state be touched directly by many concurrent execution contexts.

The core idea is:

many processes
  ↓ send messages
one owner process owns the resource/state
  ↓ serializes operations
no lock stampede

In BEAM terms

Erlang processes are lightweight and isolated:

  • each process has its own heap

  • no shared mutable memory by default

  • processes communicate by message passing

  • if a resource needs serialization, you usually model it as one process owning that resource

So for a database writer, BEAM-style architecture would be:

sqlite_writer process owns SQLite connection
other processes send {write, Op} messages
writer executes writes one by one
writer replies when done

That means callers do not all race into BEGIN IMMEDIATE. They queue in the mailbox of the writer process.

Compared to current Go/SQLite situation

Current shape:

goroutine A -> BEGIN IMMEDIATE
goroutine B -> BEGIN IMMEDIATE
goroutine C -> BEGIN IMMEDIATE
goroutine D -> BEGIN IMMEDIATE
SQLite busy handler arbitrates

BEAM-style shape:

process A -> send message
process B -> send message
process C -> send message
process D -> send message

sqlite_writer:
  handle A
  handle B
  handle C
  handle D

The second shape gives you explicit control over:

  • fairness

  • batching

  • priority

  • backpressure

  • dropping/coalescing low-value writes

  • avoiding 10s lock timeout storms

What BEAM gets right here

BEAM schedulers multiplex many processes fairly, but the important part is architectural:

shared resources become processes, not mutexes.

A GenServer is the classic pattern:

GenServer.call(SQLiteWriter, {:write, query})
GenServer.cast(SQLiteWriter, {:best_effort_peer_update, peer})

Then SQLiteWriter can decide:

  • synchronous important writes: execute and reply

  • best-effort writes: batch

  • duplicate peer updates: coalesce

  • huge blob indexing: process in chunks and yield between chunks

  • low-priority domain updates: delay/drop under pressure

Equivalent fix in this Go codebase

A BEAM-inspired design would be a single write-owner goroutine:

WriteQueue goroutine owns writer admission
all SQLite writes submit jobs
queue executes one write tx at a time

For this repo specifically:

  • peerWriter is already a small BEAM-like actor for peer writes.

  • Problem: not all peer writes use it; connect() still bypasses it.

  • Bigger problem: PutMany, domain updates, connect updates, etc. still compete directly at SQLite.

So the BEAM answer would be:

Don’t let every goroutine call BEGIN IMMEDIATE.
Make SQLite writing an owned service/actor with a mailbox.

That is the global fix pattern.

Do you like what you are reading? Subscribe to receive updates.

Unsubscribe anytime