The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.
DuckLake’s time travel capabilities provide a powerful audit trail for your data, enabling you to:
This functionality is especially valuable in domains where data provenance and reproducibility are critical, such as clinical trials, financial reporting, and scientific research.
We’ll start by creating a new DuckLake and loading the mtcars dataset. We’ll then make several modifications to demonstrate time travel functionality.
# Install the ducklake extension (required once per system)
# The ducklake extension only needs installing once per machine:
# install_ducklake()
# Create or attach to a data lake
attach_ducklake(
ducklake_name = "time_travel_demo",
lake_path = vignette_temp_dir
)
# Create initial table with the mtcars dataset
with_transaction(
create_table(mtcars, "cars"),
author = "Data Engineer",
commit_message = "Initial load of mtcars dataset"
)
#> Transaction started.
#> Transaction committed.
# Verify the table was created
get_ducklake_table("cars") |>
select(mpg, cyl, hp, wt) |>
head()
#> # A query: ?? x 4
#> # Database: DuckDB 1.5.1 [tgerke@Darwin 25.5.0:R 4.5.2//private/var/folders/b7/664jmq55319dcb7y4jdb39zr0000gq/T/RtmpBfCpem/ducklake/ducklake8fa654044b50.duckdb]
#> mpg cyl hp wt
#> <dbl> <dbl> <dbl> <dbl>
#> 1 21 6 110 2.62
#> 2 21 6 110 2.88
#> 3 22.8 4 93 2.32
#> 4 21.4 6 110 3.22
#> 5 18.7 8 175 3.44
#> 6 18.1 6 105 3.46Let’s make several changes to our data to create a version history we can explore.
We already have our initial dataset. Let’s check the current state:
get_ducklake_table("cars") |>
summarise(
n_cars = n(),
avg_mpg = mean(mpg, na.rm = TRUE),
avg_hp = mean(hp, na.rm = TRUE)
)
#> # A query: ?? x 3
#> # Database: DuckDB 1.5.1 [tgerke@Darwin 25.5.0:R 4.5.2//private/var/folders/b7/664jmq55319dcb7y4jdb39zr0000gq/T/RtmpBfCpem/ducklake/ducklake8fa654044b50.duckdb]
#> n_cars avg_mpg avg_hp
#> <dbl> <dbl> <dbl>
#> 1 32 20.1 147.Suppose we discover that fuel efficiency measurements need to be adjusted for some vehicles:
# Update mpg for high-performance cars (5% reduction)
with_transaction(
get_ducklake_table("cars") |>
mutate(mpg = if_else(hp > 200, mpg * 0.95, mpg)) |>
replace_table("cars"),
author = "Data Analyst",
commit_message = "Adjust MPG for high-performance vehicles"
)
#> Transaction started.
#> Transaction committed.
# Check the updated averages
get_ducklake_table("cars") |>
summarise(
n_cars = n(),
avg_mpg = mean(mpg, na.rm = TRUE),
avg_hp = mean(hp, na.rm = TRUE)
)
#> # A query: ?? x 3
#> # Database: DuckDB 1.5.1 [tgerke@Darwin 25.5.0:R 4.5.2//private/var/folders/b7/664jmq55319dcb7y4jdb39zr0000gq/T/RtmpBfCpem/ducklake/ducklake8fa654044b50.duckdb]
#> n_cars avg_mpg avg_hp
#> <dbl> <dbl> <dbl>
#> 1 32 19.9 147.Let’s add a new categorical variable to classify cars by fuel efficiency:
with_transaction(
get_ducklake_table("cars") |>
mutate(
efficiency_class = case_when(
mpg >= 25 ~ "High",
mpg >= 20 ~ "Medium",
TRUE ~ "Low"
)
) |>
replace_table("cars"),
author = "Data Analyst",
commit_message = "Add efficiency classification"
)
#> Transaction started.
#> Transaction committed.
# View the new classification
get_ducklake_table("cars") |>
count(efficiency_class) |>
arrange(desc(n))
#> # A query: ?? x 2
#> # Database: DuckDB 1.5.1 [tgerke@Darwin 25.5.0:R 4.5.2//private/var/folders/b7/664jmq55319dcb7y4jdb39zr0000gq/T/RtmpBfCpem/ducklake/ducklake8fa654044b50.duckdb]
#> # Ordered by: desc(n)
#> efficiency_class n
#> <chr> <dbl>
#> 1 Low 18
#> 2 Medium 8
#> 3 High 6Suppose we realize the efficiency classification thresholds were wrong and need to be corrected:
with_transaction(
get_ducklake_table("cars") |>
mutate(
efficiency_class = case_when(
mpg >= 30 ~ "High",
mpg >= 20 ~ "Medium",
TRUE ~ "Low"
)
) |>
replace_table("cars"),
author = "Senior Analyst",
commit_message = "Correct efficiency classification thresholds"
)
#> Transaction started.
#> Transaction committed.
# View the corrected classification
get_ducklake_table("cars") |>
count(efficiency_class) |>
arrange(desc(n))
#> # A query: ?? x 2
#> # Database: DuckDB 1.5.1 [tgerke@Darwin 25.5.0:R 4.5.2//private/var/folders/b7/664jmq55319dcb7y4jdb39zr0000gq/T/RtmpBfCpem/ducklake/ducklake8fa654044b50.duckdb]
#> # Ordered by: desc(n)
#> efficiency_class n
#> <chr> <dbl>
#> 1 Low 18
#> 2 Medium 10
#> 3 High 4Now that we have a history of changes, let’s explore the time travel functionality.
# View all available versions of the table
snapshots <- list_table_snapshots("cars")
snapshots
#> snapshot_id snapshot_time schema_version
#> 1 1 2026-08-27 14:25:58 1
#> 2 2 2026-08-27 14:25:58 2
#> 3 3 2026-08-27 14:25:58 3
#> 4 4 2026-08-27 14:25:59 4
#> changes
#> 1 tables_created, tables_inserted_into, main.cars, 1
#> 2 tables_created, tables_dropped, tables_inserted_into, main.cars, 1, 2
#> 3 tables_created, tables_dropped, tables_inserted_into, main.cars, 2, 3
#> 4 tables_created, tables_dropped, tables_inserted_into, main.cars, 3, 4
#> author commit_message commit_extra_info
#> 1 Data Engineer Initial load of mtcars dataset <NA>
#> 2 Data Analyst Adjust MPG for high-performance vehicles <NA>
#> 3 Data Analyst Add efficiency classification <NA>
#> 4 Senior Analyst Correct efficiency classification thresholds <NA>Let’s look at version 2, before we added the efficiency classification:
# Get version 2 (after MPG adjustment, before classification)
get_ducklake_table_version("cars", version = 2) |>
select(mpg, cyl, hp, wt) |>
head()
#> # A query: ?? x 4
#> # Database: DuckDB 1.5.1 [tgerke@Darwin 25.5.0:R 4.5.2//private/var/folders/b7/664jmq55319dcb7y4jdb39zr0000gq/T/RtmpBfCpem/ducklake/ducklake8fa654044b50.duckdb]
#> mpg cyl hp wt
#> <dbl> <dbl> <dbl> <dbl>
#> 1 21 6 110 2.62
#> 2 21 6 110 2.88
#> 3 22.8 4 93 2.32
#> 4 21.4 6 110 3.22
#> 5 18.7 8 175 3.44
#> 6 18.1 6 105 3.46
# Notice: no efficiency_class column yetCompare this with version 3, which has the classification:
# Get version 3 (with initial classification)
get_ducklake_table_version("cars", version = 3) |>
select(mpg, efficiency_class) |>
count(efficiency_class)
#> # A query: ?? x 2
#> # Database: DuckDB 1.5.1 [tgerke@Darwin 25.5.0:R 4.5.2//private/var/folders/b7/664jmq55319dcb7y4jdb39zr0000gq/T/RtmpBfCpem/ducklake/ducklake8fa654044b50.duckdb]
#> efficiency_class n
#> <chr> <dbl>
#> 1 High 6
#> 2 Medium 8
#> 3 Low 18We can also query data as it existed at any point in time:
# Get the timestamp from version 2
version2_timestamp <- snapshots |>
filter(schema_version == 2) |>
pull(snapshot_time)
# Query data as it existed at that time
# Note: Add 1 second to ensure we query AFTER the snapshot was created
get_ducklake_table_asof("cars", version2_timestamp + 1) |>
summarise(
avg_mpg = mean(mpg, na.rm = TRUE)
)
#> # A query: ?? x 1
#> # Database: DuckDB 1.5.1 [tgerke@Darwin 25.5.0:R 4.5.2//private/var/folders/b7/664jmq55319dcb7y4jdb39zr0000gq/T/RtmpBfCpem/ducklake/ducklake8fa654044b50.duckdb]
#> avg_mpg
#> <dbl>
#> 1 19.9One powerful use case is comparing different versions to understand what changed:
# Get MPG values from version 1 (original) and version 2 (after adjustment)
original <- get_ducklake_table_version("cars", version = 1) |>
select(mpg) |>
collect() |>
mutate(version = "Original")
adjusted <- get_ducklake_table_version("cars", version = 2) |>
select(mpg) |>
collect() |>
mutate(version = "Adjusted")
# Combine and compare
bind_rows(original, adjusted) |>
group_by(version) |>
summarise(
avg_mpg = mean(mpg, na.rm = TRUE),
min_mpg = min(mpg),
max_mpg = max(mpg)
)
#> # A tibble: 2 × 4
#> version avg_mpg min_mpg max_mpg
#> <chr> <dbl> <dbl> <dbl>
#> 1 Adjusted 19.9 9.88 33.9
#> 2 Original 20.1 10.4 33.9If we need to undo changes, restore_table_version()
rolls a table back to an earlier snapshot in one call:
# Go back to version 2 (before adding classifications)
restore_table_version("cars", version = 2, author = "Senior Analyst")
#> Transaction started.
#> Transaction committed.
#> Table "cars" restored to snapshot 2 (recorded as a new snapshot).
# Verify the restoration - efficiency_class column should be gone
get_ducklake_table("cars") |> colnames()
#> [1] "mpg" "cyl" "disp" "hp" "drat" "wt" "qsec" "vs" "am" "gear"
#> [11] "carb"You can also restore to a point in time with
restore_table_version("cars", timestamp = "2026-07-01 09:00:00"),
and pass a custom commit_message if the default (“Restored
cars to snapshot 2”) isn’t descriptive enough for your audit trail.
Nothing is lost in a restore: the rollback happens forward,
as a new snapshot with its own author and commit message, so the full
history — including the states after the restore point — remains
available for time travel. That also means a restore is itself
reversible with another restore_table_version() call:
list_table_snapshots("cars")
#> snapshot_id snapshot_time schema_version
#> 1 1 2026-08-27 14:25:58 1
#> 2 2 2026-08-27 14:25:58 2
#> 3 3 2026-08-27 14:25:58 3
#> 4 4 2026-08-27 14:25:59 4
#> 5 5 2026-08-27 14:25:59 5
#> changes
#> 1 tables_created, tables_inserted_into, main.cars, 1
#> 2 tables_created, tables_dropped, tables_inserted_into, main.cars, 1, 2
#> 3 tables_created, tables_dropped, tables_inserted_into, main.cars, 2, 3
#> 4 tables_created, tables_dropped, tables_inserted_into, main.cars, 3, 4
#> 5 tables_created, tables_dropped, tables_inserted_into, main.cars, 4, 5
#> author commit_message commit_extra_info
#> 1 Data Engineer Initial load of mtcars dataset <NA>
#> 2 Data Analyst Adjust MPG for high-performance vehicles <NA>
#> 3 Data Analyst Add efficiency classification <NA>
#> 4 Senior Analyst Correct efficiency classification thresholds <NA>
#> 5 Senior Analyst Restored cars to snapshot 2 <NA>The queries above travel one table at a time. To freeze everything — say, to re-run a report exactly as it stood at a submission milestone — attach the lake pinned to a snapshot:
Every table then reads as of snapshot 2 with no AT (...)
clauses needed, and writes are rejected, so the milestone view can’t
drift. A snapshot_time argument does the same for a point
in time.
Time travel functionality is particularly valuable for:
Each snapshot includes metadata about when it was created and what
changes were made. The list_table_snapshots() function
provides a complete audit trail:
# Get detailed snapshot history with all metadata
snapshot_history <- list_table_snapshots("cars")
snapshot_history |>
select(snapshot_id, snapshot_time, author, commit_message)
#> snapshot_id snapshot_time author
#> 1 1 2026-08-27 14:25:58 Data Engineer
#> 2 2 2026-08-27 14:25:58 Data Analyst
#> 3 3 2026-08-27 14:25:58 Data Analyst
#> 4 4 2026-08-27 14:25:59 Senior Analyst
#> 5 5 2026-08-27 14:25:59 Senior Analyst
#> commit_message
#> 1 Initial load of mtcars dataset
#> 2 Adjust MPG for high-performance vehicles
#> 3 Add efficiency classification
#> 4 Correct efficiency classification thresholds
#> 5 Restored cars to snapshot 2This complete audit trail ensures that you can always answer questions like:
You can also access metadata about all tables in the DuckLake:
# View metadata for all tables
all_snapshots <- list_table_snapshots()
all_snapshots |>
select(snapshot_id, snapshot_time, changes) |>
head(10)
#> snapshot_id snapshot_time
#> 1 0 2026-08-27 14:25:58
#> 2 1 2026-08-27 14:25:58
#> 3 2 2026-08-27 14:25:58
#> 4 3 2026-08-27 14:25:58
#> 5 4 2026-08-27 14:25:59
#> 6 5 2026-08-27 14:25:59
#> changes
#> 1 schemas_created, main
#> 2 tables_created, tables_inserted_into, main.cars, 1
#> 3 tables_created, tables_dropped, tables_inserted_into, main.cars, 1, 2
#> 4 tables_created, tables_dropped, tables_inserted_into, main.cars, 2, 3
#> 5 tables_created, tables_dropped, tables_inserted_into, main.cars, 3, 4
#> 6 tables_created, tables_dropped, tables_inserted_into, main.cars, 4, 5These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.