What happens?
Hey,
I would want to efficiently track only the delta of public datasets in ducklake. I asked about it in #392 where @guillesd recommended to use the MERGE INTO statement.
I didn't include the geometry column into this example because of #461 and I wanted this example simpler to understand.
Again it might have been just my poor communication and this got misunderstood.
To Reproduce
ATTACH 'ducklake:osm.ducklake'
AS osm;
INSTALL spatial; LOAD spatial;
LOAD httpfs;
CREATE OR REPLACE MACRO load_osm(area_with_country_and_date) AS TABLE
SELECT
kind::varchar as kind,
id,
tags
FROM ST_ReadOSM('https://download.geofabrik.de/' || area_with_country_and_date || '.osm.pbf');
-- Initiate the monaco data from January 2025
CREATE TABLE osm.monaco AS
FROM load_osm('europe/monaco-250101');
-- Update the monaco data from September 2025
BEGIN;
-- FIXME: This step is very very inefficient for the storage
-- Step 1: UPDATE old rows
MERGE INTO osm.monaco
USING load_osm('europe/monaco-250901')
USING (kind, id)
WHEN MATCHED THEN UPDATE;
-- Step 2: Add completely new rows
MERGE INTO osm.monaco
USING load_osm('europe/monaco-250901')
USING (kind, id)
WHEN NOT MATCHED THEN INSERT;
-- Step 3: Delete rows which were not in the new set
WITH missing_rows AS (
SELECT kind, id FROM osm.monaco
ANTI JOIN load_osm('europe/monaco-250901') USING(kind, id)
)
DELETE FROM osm.monaco current_rows USING missing_rows
WHERE current_rows.id = missing_rows.id AND current_rows.kind = missing_rows.kind;
COMMIT;
After we have run the SQL above let's look at the results:
$ tree -h osm.ducklake.files/
[ 96] osm.ducklake.files/
└── [ 96] main
└── [ 192] monaco
├── [403K] ducklake-019971a3-b894-7c2c-8e19-21cfcca25b1a.parquet <-- Initial data set here
├── [584K] ducklake-019971a3-e18c-72f2-bf16-b04dd18365a8.parquet <-- Step 1: Updated rows
├── [177K] ducklake-019971a3-e19c-73db-a9e2-74e783ceb4bf-delete.parquet <-- Step 3: Deleted rows
└── [ 24K] ducklake-019971a3-f12f-7f69-949d-84e5944cd1fe.parquet <-- Step 2: New rows
3 directories, 4 files
When you look at the parquet file which contains updated rows I assume it should be smaller?
Using the load_osm MACRO we can see that almost all rows were exactly same between both datasets and only 10% of the data changed between:
WITH changed_rows AS (
SELECT *, COUNT(*) as count
FROM (
FROM load_osm('europe/monaco-250101')
UNION ALL
FROM load_osm('europe/monaco-250901')
)
GROUP BY kind, id, tags
)
SELECT
CASE count
WHEN 1 THEN 'only-in-other-dataset'
WHEN 2 THEN 'in-both-datasets'
END as row_exists,
COUNT(*) as count
FROM changed_rows
GROUP BY row_exists;
┌───────────────────────┬───────┐
│ row_exists │ count │
│ varchar │ int64 │
├───────────────────────┼───────┤
│ only-in-other-dataset │ 4186 │
│ in-both-datasets │ 44038 │
└───────────────────────┴───────┘
I would have assumed that the parquet file which contains only the updated rows would have been ~10% of the size from the initial import.
I might be holding it in a wrong way? What is the recommended way to track data like this in ducklake where only small portion of it changes? Ideally I would only want to store the changed rows or even better would be just the columns which changed.
My goal would be to save money for the storage by just storing the delta and still be able to go back in history to certain date. Thanks in advance 🙇 . If this is not a bug but my misunderstading I hope the conversation will at least teach others.
OS:
MacOS 15.6.1 aarch64
DuckDB Version:
v1.4.0 (Andium) b8a06e4a22
DuckLake Version:
45788f0
DuckDB Client:
CLI
Hardware:
Macbook Pro M1 Pro
Full Name:
Onni Hakala
Affiliation:
Freelancer
What is the latest build you tested with? If possible, we recommend testing with the latest nightly build.
I have tested with a stable release
Did you include all relevant data sets for reproducing the issue?
Yes
Did you include all code required to reproduce the issue?
Did you include all relevant configuration (e.g., CPU architecture, Python version, Linux distribution) to reproduce the issue?
What happens?
Hey,
I would want to efficiently track only the delta of public datasets in ducklake. I asked about it in #392 where @guillesd recommended to use the
MERGE INTOstatement.I didn't include the
geometrycolumn into this example because of #461 and I wanted this example simpler to understand.Again it might have been just my poor communication and this got misunderstood.
To Reproduce
After we have run the SQL above let's look at the results:
$ tree -h osm.ducklake.files/ [ 96] osm.ducklake.files/ └── [ 96] main └── [ 192] monaco ├── [403K] ducklake-019971a3-b894-7c2c-8e19-21cfcca25b1a.parquet <-- Initial data set here ├── [584K] ducklake-019971a3-e18c-72f2-bf16-b04dd18365a8.parquet <-- Step 1: Updated rows ├── [177K] ducklake-019971a3-e19c-73db-a9e2-74e783ceb4bf-delete.parquet <-- Step 3: Deleted rows └── [ 24K] ducklake-019971a3-f12f-7f69-949d-84e5944cd1fe.parquet <-- Step 2: New rows 3 directories, 4 filesWhen you look at the parquet file which contains updated rows I assume it should be smaller?
Using the
load_osmMACRO we can see that almost all rows were exactly same between both datasets and only 10% of the data changed between:I would have assumed that the parquet file which contains only the updated rows would have been ~10% of the size from the initial import.
I might be holding it in a wrong way? What is the recommended way to track data like this in ducklake where only small portion of it changes? Ideally I would only want to store the changed rows or even better would be just the columns which changed.
My goal would be to save money for the storage by just storing the delta and still be able to go back in history to certain date. Thanks in advance 🙇 . If this is not a bug but my misunderstading I hope the conversation will at least teach others.
OS:
MacOS 15.6.1 aarch64
DuckDB Version:
v1.4.0 (Andium) b8a06e4a22
DuckLake Version:
45788f0
DuckDB Client:
CLI
Hardware:
Macbook Pro M1 Pro
Full Name:
Onni Hakala
Affiliation:
Freelancer
What is the latest build you tested with? If possible, we recommend testing with the latest nightly build.
I have tested with a stable release
Did you include all relevant data sets for reproducing the issue?
Yes
Did you include all code required to reproduce the issue?
Did you include all relevant configuration (e.g., CPU architecture, Python version, Linux distribution) to reproduce the issue?