Skip to content

DecentDB vs DuckDB: When to Choose Which

This document helps developers decide between DecentDB and DuckDB for embedded database workloads. Both are embeddable, but they target fundamentally different use cases.

Versions compared: DecentDB 2.0.0 vs DuckDB 1.x (as of 2024).

See also: SQL Feature Matrix for a per-feature support grid, and SQL Reference for DecentDB's full SQL surface.

They Solve Different Problems

DecentDB and DuckDB are not direct competitors. They share the "embedded database" label but optimize for opposite ends of the workload spectrum:

  • DecentDB is an OLTP engine: durability-first, one writer, many concurrent reader threads, optimized for point lookups, small transactions, and application state.
  • DuckDB is an OLAP engine: throughput-first, parallel columnar execution, optimized for scans, aggregations, joins over large datasets, and analytics.

Choosing between them is less about feature checklists and more about what your application does.

At a Glance

Dimension DecentDB DuckDB
Design priority Durability, then read performance Analytical throughput, then flexibility
Query engine Row-oriented, B-tree index seeks Columnar, vectorized, parallel
Concurrency model One writer, many concurrent reader threads (single process) Single-connection writes; parallel within one query
Default durability WAL + fsync-on-commit, always Configurable; optimized for bulk analytics workflows
Crash safety testing Built-in FaultyVFS + WAL failpoint hooks Not a first-class testing surface
Extension ecosystem None (extend via core contribution) Rich (install extensions, UDFs)
External data access Single .ddb file Parquet, CSV, JSON, Iceberg, S3/GCS/Azure, HTTP
SQL breadth Deliberate Postgres-like subset Very broad, Postgres-compatible dialect
Bindings C ABI, Rust, Python, .NET, Go, Java, Node.js, Dart C/C++, Python, Java, Node.js, R, Go, Rust, Julia
License MIT or Apache-2.0 MIT
Binary size ~2-3 MB ~15-20 MB
Platform support Tier 1 Rust platforms Windows, macOS, Linux, WASM
File format stability Stable from 2.0.0 Stable, backward compatible

When DecentDB Is the Better Fit

1. Your workload is transactional, not analytical

DecentDB is built for workloads that look like: "insert a row, update a row, look up a row by key, repeat." If your application is an API backend, a session store, a queue, or anything that issues many small read/write operations, DecentDB's row-oriented B-tree engine is designed for this pattern. DuckDB's columnar engine adds overhead per transaction because it's optimized for scanning millions of rows, not touching one.

-- Typical OLTP workload: point lookups and small writes
SELECT * FROM sessions WHERE token = $1;
UPDATE carts SET item_count = item_count + 1 WHERE user_id = $1;
INSERT INTO orders (user_id, total) VALUES ($1, $2) RETURNING id;

2. You need guaranteed durability by default

DecentDB fsyncs on every commit. There is no configuration knob to disable it. If your application stores data that must survive power loss, kernel panics, or process crashes, DecentDB makes the safe thing the default.

-- DecentDB: every COMMIT is durable. No settings to tune.
BEGIN;
UPDATE account SET balance = balance - 100 WHERE id = 1;
UPDATE account SET balance = balance + 100 WHERE id = 2;
COMMIT;  -- fsync'd before returning to caller

DuckDB's durability model depends on the storage mode and configuration. It is designed for analytics workflows where re-running a pipeline on failure is acceptable.

3. You need full foreign key support including CASCADE

Both databases enforce foreign key constraints on INSERT and UPDATE. However, DecentDB supports ON DELETE CASCADE, ON DELETE SET NULL, and ON DELETE SET DEFAULT. DuckDB currently does not support cascading operations.

-- DecentDB: full FK support including CASCADE
CREATE TABLE users (id INT PRIMARY KEY, name TEXT);
CREATE TABLE orders (
  id INT PRIMARY KEY,
  user_id INT REFERENCES users(id) ON DELETE CASCADE
);
INSERT INTO orders (user_id) VALUES (999);  -- ERROR: FK violation
DELETE FROM users WHERE id = 1;             -- Cascades to orders

-- DuckDB: FK enforced, but CASCADE not supported
CREATE TABLE users (id INT PRIMARY KEY, name TEXT);
CREATE TABLE orders (
  id INT PRIMARY KEY,
  user_id INT REFERENCES users(id)  -- CASCADE syntax parsed but not enforced
);
INSERT INTO orders (user_id) VALUES (999);  -- ERROR: FK violation (enforced!)

If you need cascading deletes or set-null behavior, DecentDB provides it. With DuckDB, you must implement cascade logic in application code.

4. You need triggers

DecentDB supports AFTER row triggers on tables and INSTEAD OF row triggers on views. DuckDB does not support triggers at all.

-- DecentDB: audit trail via trigger
CREATE TABLE audit_log (id INT PRIMARY KEY, msg TEXT, ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP);

CREATE TRIGGER log_user_insert AFTER INSERT ON users
FOR EACH ROW
EXECUTE FUNCTION decentdb_exec_sql('INSERT INTO audit_log (msg) VALUES (''user created'')');

If you need automatic side-effects on writes (auditing, cascading logic, materialized view refresh), triggers are the natural mechanism, and DuckDB lacks them entirely.

5. You need savepoints for partial rollback

DecentDB supports SAVEPOINT, RELEASE SAVEPOINT, and ROLLBACK TO SAVEPOINT within transactions. DuckDB does not.

-- DecentDB: partial rollback within a transaction
BEGIN;
INSERT INTO orders (user_id, total) VALUES (1, 29.99);
SAVEPOINT sp1;
INSERT INTO order_items (order_id, sku) VALUES (1, 'INVALID-SKU');
ROLLBACK TO SAVEPOINT sp1;  -- undo the bad insert only
INSERT INTO order_items (order_id, sku) VALUES (1, 'SKU-123');
RELEASE SAVEPOINT sp1;
COMMIT;  -- order + valid item committed

6. You need recursive JSON traversal

DecentDB supports json_tree() for recursive traversal of nested JSON. DuckDB does not.

-- DecentDB: recursively walk nested JSON
SELECT key, value, type, path
FROM json_tree('{"config":{"db":{"host":"localhost","port":5432},"cache":{"ttl":300}}}');

-- Returns:
-- config | {"db":{"host":"localhost","port":5432},"cache":{"ttl":300}} | object | $.config
-- db     | {"host":"localhost","port":5432}                          | object | $.config.db
-- host   | localhost                                                  | text   | $.config.db.host
-- port   | 5432                                                       | number | $.config.db.port
-- cache  | {"ttl":300}                                                | object | $.config.cache
-- ttl    | 300                                                        | number | $.config.cache.ttl

DecentDB offers a built-in trigram index type for fast LIKE '%pattern%' queries. DuckDB does not have an equivalent native index type.

-- DecentDB: trigram index for substring search
CREATE INDEX idx_users_name_trgm ON users USING trigram(name);

-- Uses the trigram index instead of a full scan
SELECT * FROM users WHERE name LIKE '%alice%';

Without a trigram index, LIKE '%pattern%' requires a full table scan in both databases.

8. You need TOTAL() (NULL-safe sum)

DecentDB provides TOTAL(), which returns 0.0 for empty sets instead of NULL. DuckDB does not have this function.

-- DecentDB
SELECT TOTAL(amount) FROM orders WHERE category = 'nonexistent';  -- returns 0.0
SELECT SUM(amount) FROM orders WHERE category = 'nonexistent';    -- returns NULL

-- DuckDB: no TOTAL(), must use COALESCE
SELECT COALESCE(SUM(amount), 0.0) FROM orders WHERE category = 'nonexistent';

Note: Both databases support GROUP_CONCAT (DuckDB has it as an alias for STRING_AGG).

9. You need a single-process, multi-threaded reader architecture

DecentDB is designed for one process with multiple concurrent reader threads that get lock-free snapshot isolation. DuckDB executes queries in parallel within a single connection but is not designed for many concurrent connections issuing queries simultaneously.

DecentDB model:
  Process
    ├─ Writer thread (holds commit lock)
    ├─ Reader thread 1 (snapshot, no blocking)
    ├─ Reader thread 2 (snapshot, no blocking)
    └─ Reader thread N (snapshot, no blocking)

DuckDB model:
  Connection
    └─ Query runs with internal parallelism (multi-core within one query)

If your application is a web server or API that must handle many concurrent read requests on separate threads, DecentDB's model is the natural fit.

When DuckDB Is the Better Fit

1. Your workload is analytical (aggregations, scans, joins over large data)

DuckDB's columnar, vectorized, parallel engine is purpose-built for analytical queries. It will dramatically outperform DecentDB on queries that scan, filter, aggregate, or join millions of rows.

-- Analytical query: DuckDB excels here
SELECT
  DATE_TRUNC('month', order_date) AS month,
  product_category,
  COUNT(*) AS orders,
  SUM(amount) AS revenue,
  AVG(amount) AS avg_order_value
FROM orders
WHERE order_date >= '2024-01-01'
GROUP BY 1, 2
ORDER BY 1, 3 DESC;

DecentDB can execute this query, but DuckDB's columnar execution and parallelism make it orders of magnitude faster on large datasets.

2. You need to query external data directly

DuckDB can read Parquet, CSV, JSON, Arrow, Iceberg, and remote files (S3, GCS, Azure, HTTP) directly in SQL without loading them into a database first. DecentDB operates on a single .ddb file.

-- DuckDB: query Parquet files directly
SELECT * FROM 'data/orders.parquet' WHERE amount > 100;

-- DuckDB: query S3 directly
SELECT * FROM 's3://my-bucket/logs/*.parquet' WHERE status = 'error';

-- DuckDB: query CSV with automatic schema inference
SELECT * FROM read_csv('data/users.csv', auto_detect = true) WHERE age > 30;

-- DuckDB: join a local table with a remote Parquet file
SELECT l.name, r.total
FROM local_users l
JOIN 's3://analytics/revenue.parquet' r ON l.id = r.user_id;

DecentDB requires you to load data into the .ddb file first.

3. You need to read multiple formats and export to others

DuckDB is a Swiss-army knife for data format conversion. It reads Parquet, CSV, JSON, Arrow, and writes to all of them.

-- DuckDB: convert CSV to Parquet
COPY (SELECT * FROM read_csv('raw.csv')) TO 'output.parquet' (FORMAT PARQUET);

-- DuckDB: export query results to JSON
COPY (SELECT * FROM users WHERE active) TO 'active_users.json' (FORMAT JSON);

-- DuckDB: write to Parquet with compression and partitioning
COPY orders TO 'orders/' (FORMAT PARQUET, PARTITION_BY (year, month), COMPRESSION zstd);

4. You need parallel query execution

DuckDB parallelizes individual queries across multiple cores. A single SELECT with a large scan or aggregation will use all available CPU cores automatically.

-- DuckDB: this query runs in parallel across cores automatically
SELECT category, COUNT(*), AVG(price), SUM(quantity)
FROM line_items
GROUP BY category;

DecentDB executes queries on a single core per query. You get concurrency through multiple reader threads running different queries, not parallelism within one query.

5. You need a rich type system for analytics

DuckDB supports types that DecentDB does not: LIST, STRUCT, MAP, ARRAY, INTERVAL, HUGEINT (128-bit integer), BIT, ENUM, UNION, TIME, TIMETZ, and more.

-- DuckDB: nested and complex types
CREATE TABLE events (
  id INT,
  tags LIST<TEXT>,
  metadata STRUCT(host TEXT, port INT),
  properties MAP(TEXT, TEXT)
);

SELECT tags FROM events WHERE 'error' IN tags;
SELECT metadata.host FROM events WHERE metadata.port = 8080;

6. You need built-in statistical and machine-learning functions

DuckDB has an extensive library of statistical, mathematical, and analytical functions that go far beyond what DecentDB offers.

-- DuckDB: statistical functions
SELECT
  CORR(x, y) AS correlation,
  COVAR_POP(x, y) AS covariance,
  REGR_SLOPE(y, x) AS slope,
  REGR_INTERCEPT(y, x) AS intercept
FROM measurements;

-- DuckDB: quantile with multiple methods
SELECT QUANTILE_CONT(amount, 0.95) FROM orders;
SELECT QUANTILE_DISC(amount, 0.5) FROM orders;
SELECT MEDIAN(amount) FROM orders;

7. You need window frame clauses

DuckDB supports ROWS BETWEEN and RANGE BETWEEN frame specifications. DecentDB does not currently support frame clauses.

-- DuckDB: rolling 7-day average
SELECT date, amount,
  AVG(amount) OVER (
    ORDER BY date
    ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
  ) AS rolling_7d_avg
FROM daily_sales;

-- DuckDB: cumulative sum with frame
SELECT date, amount,
  SUM(amount) OVER (
    ORDER BY date
    ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
  ) AS running_total
FROM daily_sales;

8. You need extensions

DuckDB has a rich extension ecosystem: spatial (GIS), httpfs (S3/HTTP), parquet, json, autocomplete, delta, iceberg, and community extensions. Extensions are installed with a single command.

-- DuckDB: install and load extensions
INSTALL spatial;
LOAD spatial;

-- Query GeoJSON with the spatial extension
SELECT name, ST_Area(geom) FROM countries;

-- DuckDB: httpfs for remote data
INSTALL httpfs;
LOAD httpfs;
SELECT * FROM 'https://example.com/data.parquet';

DecentDB has no extension mechanism. All features are built into the core.

9. You need to work with Pandas, Arrow, or Polars

DuckDB has first-class integration with Python data frameworks. You can query Pandas DataFrames, Arrow tables, and Polars DataFrames directly in SQL.

import duckdb
import pandas as pd

df = pd.read_csv("sales.csv")
# Query the DataFrame directly with SQL
result = duckdb.sql("SELECT category, SUM(amount) FROM df GROUP BY category").df()

10. You need LISTAGG or ordered string aggregation with rich syntax

DuckDB supports LISTAGG with full ordering and distinct control, which is more flexible than DecentDB's STRING_AGG/GROUP_CONCAT.

-- DuckDB: LISTAGG with ordering and distinct
SELECT department,
  LISTAGG(name, ', ') WITHIN GROUP (ORDER BY name) AS team
FROM employees
GROUP BY department;

-- DuckDB: LISTAGG with DISTINCT
SELECT department,
  LISTAGG(DISTINCT role, ', ') WITHIN GROUP (ORDER BY role) AS roles
FROM employees
GROUP BY department;

Side-by-Side SQL Examples

Aggregation: statistical summary

-- Both support standard aggregates
SELECT COUNT(*), SUM(amount), AVG(amount), MIN(amount), MAX(amount) FROM orders;

-- Both support statistical aggregates
SELECT STDDEV(amount), VARIANCE(amount) FROM orders;

-- DuckDB: additional analytics functions
SELECT
  QUANTILE_CONT(amount, 0.5) AS median,
  QUANTILE_CONT(amount, 0.95) AS p95,
  SKEWNESS(amount) AS skew,
  KURTOSIS(amount) AS kurtosis
FROM orders;

-- DecentDB: equivalent using built-in functions
SELECT
  MEDIAN(amount) AS median,
  PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY amount) AS p95
FROM orders;

Transaction safety

-- DecentDB: durable by default, savepoints supported
BEGIN;
UPDATE inventory SET qty = qty - 1 WHERE sku = 'WIDGET';
SAVEPOINT sp1;
INSERT INTO shipments (order_id) VALUES (42);
ROLLBACK TO SAVEPOINT sp1;  -- undo shipment, keep inventory update
COMMIT;

-- DuckDB: savepoints not supported, durability is configurable
BEGIN;
UPDATE inventory SET qty = qty - 1 WHERE sku = 'WIDGET';
COMMIT;
-- No partial rollback available

Querying external data

-- DuckDB: query Parquet directly, no import step
SELECT user_id, SUM(amount) AS total
FROM 's3://analytics/2024/orders/*.parquet'
WHERE region = 'us-east'
GROUP BY user_id
HAVING total > 1000;

-- DecentDB: must import first, then query
-- Step 1: Load data into the .ddb file
-- Step 2: Run the query against the local table
SELECT user_id, SUM(amount) AS total
FROM orders
WHERE region = 'us-east'
GROUP BY user_id
HAVING total > 1000;

JSON traversal

-- DecentDB: recursive JSON traversal
SELECT key, value, path FROM json_tree('{"a":{"b":1},"c":[2,3]}');

-- DuckDB: no json_tree(), use repeated json_extract() for known paths
SELECT
  json_extract(config, '$.a.b') AS a_b,
  json_extract(config, '$.c[0]') AS c_0
FROM settings;

INSERT with RETURNING

-- DecentDB: RETURNING is supported
INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com') RETURNING id;

-- DuckDB: RETURNING is also supported (both have it)
INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com') RETURNING id;

Summary Decision Matrix

Your situation Recommendation
OLTP workload: point lookups, small transactions DecentDB
Non-negotiable durability, fsync on every commit DecentDB
Full FK support including CASCADE DecentDB
Need triggers (audit, cascading logic) DecentDB
Need savepoints for partial rollback DecentDB
Single-process, multi-threaded concurrent readers DecentDB
Recursive JSON traversal (json_tree) DecentDB
Built-in trigram substring search DecentDB
TOTAL() for NULL-safe sums DecentDB
OLAP workload: aggregations, scans over large data DuckDB
Query Parquet/CSV/JSON/S3 directly in SQL DuckDB
Parallel query execution across cores DuckDB
Rich analytics: quantiles, regression, skewness DuckDB
Window frame clauses (ROWS BETWEEN) DuckDB
Extensions (spatial, httpfs, Iceberg, etc.) DuckDB
Integration with Pandas/Arrow/Polars DuckDB
Complex types: LIST, STRUCT, MAP, ENUM DuckDB
Data format conversion (CSV to Parquet, etc.) DuckDB

Many applications use both: DuckDB for analytics and reporting pipelines, DecentDB for the transactional application store. They are complementary, not competing.