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:
peerWriteris 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