If you work with DuckDB locally, you’ll hit this eventually:
IO Error: Could not set lock on file "tuva.duckdb": Resource temporarily unavailable
This happens when two processes try to write to the same DuckDB database file simultaneously. In a typical dbt development workflow, DBeaver is the silent culprit.
DuckDB’s concurrency model
DuckDB is designed for single-process use. It implements MVCC (multi-version concurrency control) for reads, so multiple read connections can coexist, but it enforces a single-writer constraint at the file level using OS file locks.
When you open a .duckdb file in DBeaver, the client opens a read-write connection by default. That connection holds a write lock on the file. The lock persists as long as the DBeaver connection is active — which is usually as long as DBeaver is running.
When dbt tries to run against the same file, it also needs a write connection. The OS lock is already held by DBeaver, so dbt gets EWOULDBLOCK and surfaces it as the IO error above.
Why this is specific to DuckDB
Most databases separate the server process from client connections. PostgreSQL, MySQL, BigQuery — the client connects to a process that manages concurrent access. DuckDB in file mode has no server process. Every connection goes directly to the file, and the file lock is the only coordination mechanism.
DuckDB does offer a client-server mode (duckdb --listen or MotherDuck), but for local development most setups use the file directly.
The fix: set DBeaver to read-only mode
DBeaver supports DuckDB connection properties. To prevent DBeaver from acquiring a write lock, set read_only = true in the connection configuration.
In DBeaver:
- Open the connection editor for your DuckDB connection
- Go to the “Driver Properties” or “Connection Properties” tab
- Add the property
read_onlywith valuetrue - Reconnect
DBeaver will reconnect in read-only mode. You can still run SELECT queries, browse tables, and inspect schemas. INSERT, UPDATE, CREATE, and DROP will fail — but for a development analysis tool, that’s almost always fine.
What read-only mode actually does
Under the hood, DuckDB opens the file with O_RDONLY and skips the file lock acquisition. Multiple read-only connections can coexist with each other indefinitely. A single write connection can coexist with any number of read-only connections, with MVCC snapshot isolation guarantees for the readers.
The safe state for development:
- dbt: write connection (acquires lock during
dbt run, releases when the run finishes) - DBeaver: read-only connection (no lock, always available, sees a consistent snapshot)
The same conflict with Streamlit
DBeaver isn’t the only tool that causes this. The same lock conflict appears with:
- Two terminal sessions running separate dbt commands
- A Streamlit app reading from the file while dbt is writing
- Any Python script that opens DuckDB in default (write) mode concurrently with dbt
For a Streamlit app that reads from the same database dbt writes to, open DuckDB explicitly in read-only mode:
import duckdb
con = duckdb.connect("tuva.duckdb", read_only=True)
This is the right pattern for any analytics application that reads from a DuckDB file that another process might be writing to.
What about in-memory DuckDB?
In-memory DuckDB (duckdb.connect() with no file path) doesn’t have this problem — each connection gets its own isolated in-memory database. But in-memory databases don’t persist between process restarts, which makes them unsuitable for the dbt-to-Streamlit workflow where you build once with dbt and read many times with Streamlit.
The future
DuckDB 1.10 improved error messaging for lock conflicts but didn’t change the underlying single-writer model. The constraint is fundamental to how DuckDB achieves its performance characteristics — maintaining a separate server process would add overhead that defeats the point of an embedded analytics database. For production multi-writer scenarios, MotherDuck handles this differently. For local development, read-only mode for your analysis tools is the practical answer.