Built an inverted index using DuckLake table
▲ 21 r/DuckDB

Built an inverted index using DuckLake table

Recently, I had a discussion with a database kernel engineer about implementing inverted on Iceberg tables. Coming from OLTP background, Index is typically a secondary data structure (like B-Tree) and i never thought of using tables itself as an index.

So, I implemented a simple version of an inverted index in my project Streambed (Postgres for analytics using DuckDB) using DuckLake.

Here is the implementation https://github.com/viggy28/streambed/issues/101

I had to fork DuckLake since it doesn't support external hooks for bringing custom predicates. Here is the upstream issue https://github.com/duckdb/ducklake/issues/1498

I thought folks in this subreddit might be interested, and I also would love to hear if there is any feedback.

u/vira28 — 10 days ago
▲ 37 r/DuckDB

DuckLake (DuckDB as Catalog) is 41x faster than Iceberg for our Postgres CDC workload

I've been building an open-source (Apache 2.0) "analytics for Postgres" project called Streambed, and the piece this subreddit might find interesting is the DuckDB/DuckLake layer.

Streambed streams Postgres WAL changes into lake storage and serves queries through DuckDB as the query engine.

I started Iceberg as the default target but one of the community members recently mentioned about DuckLake. It caught my attention immediately (since it uses Catalog as the DB). So I added the DuckLake support and did the benchmark.

Ducklake is 41 times faster than Iceberg in my benchmark.

Benchmark slice: 1M rows, 100k updates, flush=1,000

  Write path:
  Iceberg COW:        269s
  Iceberg MoR:        4.7s
  DuckLake + DuckDB:  6.6s
  Full aggregate median:
  Iceberg MoR: 87s
  DuckLake:    16ms

  TopN median:
  Iceberg MoR: 85s
  DuckLake:    21ms

Takeaway: for a DuckDB-centered CDC path, DuckLake looks like the cleaner default. Iceberg is still great when broad multi-engine interoperability matters.

DuckDB/DuckLake is a magical primitive. So much to build on top of it.

Blog: https://streambed.dev/blog/ducklake-target-support/

Benchmark: https://github.com/viggy28/streambed/blob/main/docs/benchmarks/ducklake-vs-iceberg.md

u/vira28 — 2 months ago

DuckLake was 41x faster than Iceberg for our Postgres CDC workload

Adding some context. This came from a problem I ran into while managing the Postgres team at Cloudflare: BI teams wanted long-running queries, so we often spun up dedicated read replicas. But read replicas still have tradeoffs around hot_standby_feedback and max_standby_streaming_delay.

Streambed started as:

Postgres WAL → S3 → query from psql

Iceberg was the first target, but real-world CDC looks more like:

small batch → commit → small batch → commit

Small commits keep data fresh, but they also create files, manifests, metadata, and copy-on-write work. So I tested DuckLake as a target format.

One benchmark slice: 1M rows, 100k updates, flush=1,000.

Iceberg COW: 269s. DuckLake + DuckDB catalog: 6.6s. Roughly 41x faster for this specific Streambed CDC-style workload.

Caveat: this bypasses Postgres logical replication and psql-wire; it measures the lakehouse writer/catalog path over local MinIO.

Blog: https://streambed.dev/blog/ducklake-target-support/

reddit.com
u/vira28 — 2 months ago

Linux style reverse search in Pi

This is my first post.

I learnt about Pi from a co-worker last month and I can safely say this is the first harness tool that I absolutely love it. There are many reasons but the main one is the ability to extend the harness. No need to dig into docs or create issues or wait for the provider to build it. Just create an extension.

Anyway, I missed Linux style reverse search, so I was wondering can I bring it in?

Pi itself built it - https://www.npmjs.com/package/pi-reverse-search

what's your most favorite feature or reason for using Pi?

reddit.com
u/vira28 — 2 months ago

Why did the same SQLite query take 9 minutes in CI and 0.8 seconds on my Mac?

For context, I run benchmark on release builds with 100k session data. It took 44 minutes on GitHub Actions but finished in under a minute locally.

CI’s Python 3.12 used SQLite 3.45.1. My local Python used SQLite 3.53.3. EXPLAIN QUERY PLAN showed that 3.45.1 scanned the eligible-session set and repeated the FTS scan for every session. SQLite 3.53.3 chose the efficient join order.

The query joined two sets approaching 100k rows: eligible sessions and FTS-matching messages. The eligible set was materialized as a CTE, so we couldn’t index it explicitly, and 3.45.1 didn’t create a useful automatic index.

That produced an effective 100k × 100k operation: about 9 minutes per search. The benchmark ran it five times.

I replaced the CTE with a temp table whose primary key covered the join columns, then materialized the FTS matches once. On SQLite 3.45.1, the query dropped to about 1.2 seconds and the full benchmark to 45 seconds. Honestly, I didn't realize CTE's can't have indexes (which makes sense)

BTW, the benchmark deliberately uses a worst-case query that matches almost every synthetic message.

Curious if you seen this large a plan difference between SQLite versions?

reddit.com
u/vira28 — 2 months ago

Mom made this as a return gift for our Puja visitors

https://preview.redd.it/xnxd65dwx10h1.jpg?width=1600&format=pjpg&auto=webp&s=edb0d49d25c8ca1eb4ad7a9e83a863c9f49809a8

I have been pushing her to start selling but, I am not sure if folks will like this. Of course, she can make other kinda dolls too. Just naturally gifted.

https://preview.redd.it/ecx0zhdky10h1.jpg?width=1600&format=pjpg&auto=webp&s=04c3c03f1f515b2961bdae59524674f7a5a7ab58

reddit.com
u/vira28 — 5 months ago
▲ 70 r/postgres+1 crossposts

Anyone who works on a production Postgres knows the feeling. Every command you run, you're walking a tightrope. One typo, one wrong terminal tab, one bug in the app that turned a filter into a full-table query, and now you're doing PITR or restoring from backup at 3am.

I've spent years as a DBA in charge of critical production workloads. Most of the time the rope holds. Sometimes it doesn't.

pg_savior is a Postgres extension that hooks the planner and refuses the obvious dangerous shapes:

  • DELETE / UPDATE without a WHERE
  • CREATE INDEX without CONCURRENTLY
  • DROP DATABASE
  • ALTER COLUMN TYPE that triggers a full rewrite
  • DELETE WHERE id > 0 (planner row estimate gives intent away)

When you really mean it: SET LOCAL pg_savior.bypass = on for the transaction, and the guard steps aside.

It's an extension, not a proxy — psql against a local socket, ORMs, migration tools, cron jobs, AI agents with DB credentials all hit the same hook. Nothing routes around it.

Three hooks do the work: post_parse_analyze_hook, ExecutorStart_hook, ProcessUtility_hook.

What other dangerous queries should pg_savior catch? Also, curious if you have best practices to catch these mistakes.

u/vira28 — 2 months ago