> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lancedb.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Versioning and Reproducibility

> Learn how to implement versioning and ensure reproducibility in LanceDB. Includes version control, data snapshots, and audit trails.

This page shows the core table-versioning APIs used in the code snippets for Python, TypeScript, and Rust.
Each operation below maps directly to methods shown in the examples.

## Basic Versioning Example

Let's create a table with sample data to demonstrate LanceDB's versioning capabilities:

### Set Up the Table

First, let's create a table with some sample data:

import pyarrow as pa

db = tmp\_db

table\_name = "quotes\_versioning\_example"
data = \[
&#x20;   \{"id": 1, "author": "Richard", "quote": "Wubba Lubba Dub Dub!"},
&#x20;   \{"id": 2, "author": "Morty", "quote": "Rick, what's going on?"},
&#x20;   \{
&#x20;       "id": 3,
&#x20;       "author": "Richard",
&#x20;       "quote": "I turned myself into a pickle, Morty!",
&#x20;   },
]

\# Define schema
schema = pa.schema(
&#x20;   \[
&#x20;       pa.field("id", pa.int64()),
&#x20;       pa.field("author", pa.string()),
&#x20;       pa.field("quote", pa.string()),
&#x20;   ]
)

table = db.create\_table(table\_name, data, schema=schema, mode="overwrite")


const tableName = "quotes\_versioning\_example";
const data = \[
&#x20; \{ id: 1, author: "Richard", quote: "Wubba Lubba Dub Dub!" },
&#x20; \{ id: 2, author: "Morty", quote: "Rick, what's going on?" },
&#x20; \{
&#x20;   id: 3,
&#x20;   author: "Richard",
&#x20;   quote: "I turned myself into a pickle, Morty!",
&#x20; },
];
const table = await db.createTable(tableName, data, \{ mode: "overwrite" });


let table\_name = "quotes\_versioning\_example";
let data = vec!\[
&#x20;   (1, "Richard", "Wubba Lubba Dub Dub!"),
&#x20;   (2, "Morty", "Rick, what's going on?"),
&#x20;   (3, "Richard", "I turned myself into a pickle, Morty!"),
];

let table = db
&#x20;   .create\_table(table\_name, make\_quotes\_reader(data))
&#x20;   .mode(CreateTableMode::Overwrite)
&#x20;   .execute()
&#x20;   .await
&#x20;   .unwrap();


**Rust helper used in this page**

fn make\_quotes\_reader(rows: Vec\<(i64, \&str, \&str)>) -> Box\<dyn RecordBatchReader + Send> \{
&#x20;   let ids: Vec\<i64> = rows.iter().map(|(id, \_, \_)| \*id).collect();
&#x20;   let authors: Vec\<\&str> = rows.iter().map(|(\_, author, \_)| \*author).collect();
&#x20;   let quotes: Vec\<\&str> = rows.iter().map(|(\_, \_, quote)| \*quote).collect();

&#x20;   let schema = Arc::new(Schema::new(vec!\[
&#x20;       Field::new("id", DataType::Int64, false),
&#x20;       Field::new("author", DataType::Utf8, false),
&#x20;       Field::new("quote", DataType::Utf8, false),
&#x20;   ]));

&#x20;   let batch = RecordBatch::try\_new(
&#x20;       schema.clone(),
&#x20;       vec!\[
&#x20;           Arc::new(Int64Array::from(ids)),
&#x20;           Arc::new(StringArray::from(authors)),
&#x20;           Arc::new(StringArray::from(quotes)),
&#x20;       ],
&#x20;   )
&#x20;   .unwrap();
&#x20;   let reader = RecordBatchIterator::new(vec!\[Ok(batch)].into\_iter(), schema);
&#x20;   Box::new(reader)
}


### Check Initial Version

After creating the table, let's check the initial version information:

versions = table.list\_versions()
current\_version = table.version
print(f"Number of versions after creation: \{len(versions)}")
print(f"Current version: \{current\_version}")


const versions = await table.listVersions();
const currentVersion = await table.version();
console.log(\`Number of versions after creation: \$\{versions.length}\`);
console.log(\`Current version: \$\{currentVersion}\`);


let versions = table.list\_versions().await.unwrap();
let current\_version = table.version().await.unwrap();
println!("Number of versions after creation: \{}", versions.len());
println!("Current version: \{}", current\_version);


## Modify Data

When you modify data through operations like update or delete, LanceDB automatically creates new versions.

### Update Existing Data

Let's update some existing records to see versioning in action:

table.update(where="author='Richard'", values=\{"author": "Richard Daniel Sanchez"})
rows\_after\_update = table.count\_rows("author = 'Richard Daniel Sanchez'")
print(f"Rows updated to Richard Daniel Sanchez: \{rows\_after\_update}")


await table.update(\{
&#x20; where: "author = 'Richard'",
&#x20; values: \{ author: "Richard Daniel Sanchez" },
});
const rowsAfterUpdate = await table.countRows(
&#x20; "author = 'Richard Daniel Sanchez'",
);
console.log(\`Rows updated to Richard Daniel Sanchez: \$\{rowsAfterUpdate}\`);


table
&#x20;   .update()
&#x20;   .only\_if("author = 'Richard'")
&#x20;   .column("author", "'Richard Daniel Sanchez'")
&#x20;   .execute()
&#x20;   .await
&#x20;   .unwrap();
let rows\_after\_update = table
&#x20;   .count\_rows(Some("author = 'Richard Daniel Sanchez'".to\_string()))
&#x20;   .await
&#x20;   .unwrap();
println!(
&#x20;   "Rows updated to Richard Daniel Sanchez: \{}",
&#x20;   rows\_after\_update
);


### Add New Data

Now let's add more records to the table:

more\_data = \[
&#x20;   \{
&#x20;       "id": 4,
&#x20;       "author": "Richard Daniel Sanchez",
&#x20;       "quote": "That's the way the news goes!",
&#x20;   },
&#x20;   \{"id": 5, "author": "Morty", "quote": "Aww geez, Rick!"},
]
table.add(more\_data)


const moreData = \[
&#x20; \{
&#x20;   id: 4,
&#x20;   author: "Richard Daniel Sanchez",
&#x20;   quote: "That's the way the news goes!",
&#x20; },
&#x20; \{ id: 5, author: "Morty", quote: "Aww geez, Rick!" },
];
await table.add(moreData);


let more\_data = vec!\[
&#x20;   (4, "Richard Daniel Sanchez", "That's the way the news goes!"),
&#x20;   (5, "Morty", "Aww geez, Rick!"),
];
table
&#x20;   .add(make\_quotes\_reader(more\_data))
&#x20;   .execute()
&#x20;   .await
&#x20;   .unwrap();


### Check Version Changes

Let's see how the versions have changed after our modifications:

versions = table.list\_versions()
version\_count\_after\_mod = len(versions)
version\_after\_mod = table.version
print(f"Number of versions after modifications: \{version\_count\_after\_mod}")
print(f"Current version: \{version\_after\_mod}")


const versionsAfterMod = await table.listVersions();
const versionCountAfterMod = versionsAfterMod.length;
const versionAfterMod = await table.version();
console.log(
&#x20; \`Number of versions after modifications: \$\{versionCountAfterMod}\`,
);
console.log(\`Current version: \$\{versionAfterMod}\`);


let versions\_after\_mod = table.list\_versions().await.unwrap();
let version\_count\_after\_mod = versions\_after\_mod.len();
let version\_after\_mod = table.version().await.unwrap();
println!(
&#x20;   "Number of versions after modifications: \{}",
&#x20;   version\_count\_after\_mod
);
println!("Current version: \{}", version\_after\_mod);


## Rollback to Previous Versions

LanceDB supports fast rollbacks to any previous version without data duplication.

### View All Versions

First, let's see all the versions we've created:

versions = table.list\_versions()
for v in versions:
&#x20;   print(f"Version \{v\['version']}, created at \{v\['timestamp']}")


const allVersions = await table.listVersions();
for (const v of allVersions) \{
&#x20; console.log(\`Version \$\{v.version}, created at \$\{v.timestamp}\`);
}


let all\_versions = table.list\_versions().await.unwrap();
for v in \&all\_versions \{
&#x20;   println!("Version \{}, created at \{}", v.version, v.timestamp);
}


### Restore a Version Snapshot

Now let's restore a captured version snapshot:

table.restore(version\_after\_mod)
versions = table.list\_versions()
version\_count\_after\_rollback = len(versions)
print(f"Total number of versions after rollback: \{version\_count\_after\_rollback}")


await table.checkout(versionAfterMod);
await table.restore();
const versionsAfterRollback = await table.listVersions();
const versionCountAfterRollback = versionsAfterRollback.length;
console.log(
&#x20; \`Total number of versions after rollback: \$\{versionCountAfterRollback}\`,
);


table.checkout(version\_after\_mod).await.unwrap();
table.restore().await.unwrap();
let versions\_after\_rollback = table.list\_versions().await.unwrap();
let version\_count\_after\_rollback = versions\_after\_rollback.len();
println!(
&#x20;   "Total number of versions after rollback: \{}",
&#x20;   version\_count\_after\_rollback
);


## Tag-Based Versioning

Numeric table versions like `v3` or `v17` are precise but hard to remember. Tags
let you attach human-readable labels (e.g., `"prod"`, `"baseline"`,
`"q3-evaluation"`) to specific versions and check those out by name. They are
conceptually similar to git tags, and unlike numeric versions, **tagged versions
are preserved when old versions are pruned** (see the cleanup note at the bottom
of this page).

The tags API supports the standard CRUD operations — create, list, update, delete —
plus checking out by tag name.

\# Create a tag pointing at a specific version
table.tags.create("baseline", 1)
table.tags.create("with-edits", table.version)

\# List all tags on this table
print(table.tags.list())

\# Look up the version a tag points at
print(table.tags.get\_version("baseline"))

\# Move an existing tag to a different version
table.tags.update("baseline", 2)

\# Check out a version by tag name
table.checkout("baseline")
print(table.version)

\# Delete a tag (does not delete the underlying version)
table.tags.delete("with-edits")

\# Return to the latest version
table.checkout\_latest()


const tags = await tagsTable.tags();

// Create a tag pointing at a specific version
await tags.create("baseline", 1);
await tags.create("with-edits", await tagsTable.version());

// List all tags on this table
console.log(await tags.list());

// Look up the version a tag points at
console.log(await tags.getVersion("baseline"));

// Move an existing tag to a different version
await tags.update("baseline", 2);

// Check out a version by tag name
await tagsTable.checkout("baseline");
console.log(await tagsTable.version());

// Delete a tag (does not delete the underlying version)
await tags.delete("with-edits");

// Return to the latest version
await tagsTable.checkoutLatest();


let mut tags = tags\_table.tags().await.unwrap();

// Create a tag pointing at a specific version
tags.create("baseline", 1).await.unwrap();
let current\_version = tags\_table.version().await.unwrap();
tags.create("with-edits", current\_version).await.unwrap();

// List all tags on this table
let all\_tags = tags.list().await.unwrap();
println!("Tags: \{:?}", all\_tags);

// Look up the version a tag points at
let baseline\_version = tags.get\_version("baseline").await.unwrap();
println!("baseline -> v\{}", baseline\_version);

// Move an existing tag to a different version
tags.update("baseline", 2).await.unwrap();

// Check out a version by tag name (separate method in Rust)
tags\_table.checkout\_tag("baseline").await.unwrap();
println!("Current version: \{}", tags\_table.version().await.unwrap());

// Delete a tag (does not delete the underlying version)
tags.delete("with-edits").await.unwrap();

// Return to the latest version
tags\_table.checkout\_latest().await.unwrap();


> [!NOTE]
>
> Deleting a tag only removes the label, not the version it points to. After
> deletion, the underlying table version becomes eligible for cleanup again.

## Branches

Beyond linear history, LanceDB also supports **branches** — isolated, writable
lines of history forked from `main` (or a specific version). Whereas tags and
`checkout` give you read-only views of existing versions, a branch has its own
writable history, making it ideal for experiments, backfills, and migrations
that you want to keep separate from production reads on `main`.

Branches are covered in their own guide: see [Branches](https://docs.lancedb.com/tables/branching.md).

## Delete Data From the Table

Let's demonstrate how deletions also create new versions:

### Go Back to Latest Version

First, let's return to the latest version:

table.checkout\_latest()


await table.checkoutLatest();


table.checkout\_latest().await.unwrap();


### Delete Data

Now let's delete some data to see how it affects versioning:

table.delete("author = 'Morty'")
rows\_after\_deletion = table.count\_rows()
print(f"Number of rows after deletion: \{rows\_after\_deletion}")


await table.delete("author = 'Morty'");
const rowsAfterDeletion = await table.countRows();
console.log(\`Number of rows after deletion: \$\{rowsAfterDeletion}\`);


table.delete("author = 'Morty'").await.unwrap();
let rows\_after\_deletion = table.count\_rows(None).await.unwrap();
println!("Number of rows after deletion: \{}", rows\_after\_deletion);


### Version History and Operations

On a fresh table, the snippets in this guide produce this version sequence:

1. `v1`: create table (`create_table` / `createTable` / `create_table`)
2. `v2`: update rows (`update`)
3. `v3`: add rows (`add`)
4. `v4`: restore snapshot (`restore`) from `version_after_mod`/`versionAfterMod`
5. `v5`: delete rows (`delete`)

Read-only and checkout operations shown here (`list_versions`/`listVersions`, `version`, `checkout`, `checkout_latest`/`checkoutLatest`) do not create new versions.

The version metadata fields can differ by backend. Direct table-backed version listing exposes a
timestamp, while namespace-backed listing may expose fields such as `manifest_path`,
`manifest_size`, `e_tag`, and `timestamp_millis`. In deployments that use managed versioning,
prefer the table version APIs exposed by LanceDB Enterprise or the namespace service instead of
mixing in lower-level Lance file operations.

> [!NOTE]
>
> **System Operations**
>
> System operations like `optimize()`, index updates, and table compaction also increment table version numbers.
> In LanceDB OSS and Enterprise, `optimize()` can prune older versions based on its retention setting (`cleanup_older_than`, 7 days by default),
> which is when old-version files are removed and disk space is reclaimed.
>
> **Tagged versions are exempt from cleanup.** A version with a tag pointing at it is
> retained regardless of age, and its files are not removed by `optimize()`. To make
> a tagged version eligible for pruning, [delete the tag](#tag-based-versioning) first.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.