mirror of
https://github.com/lancedb/lancedb.git
synced 2026-10-12 14:52:26 +00:00
## Summary
Materialize a source-preserving dedup result from an indexed, pinned
snapshot with a short declaration. The default greedy policy keeps the
smallest unassigned snapshot row ID and removes only its directly
matching neighbors: For A < B < C, an A–B–C chain without an A–C edge
keeps A and C.
```python
from lancedb import vector_dedup
view = db.create_materialized_view(
"images_clean",
vector_dedup("images", column="phash", distance_threshold=4),
)
cleaned = view.table
```
The Python source expression captures the source version once at
creation and
waits for the materialized result by default. Use `dataset_version=...`
for an
explicit snapshot, `with_no_data=True` for declaration only, or
`create_materialized_view_async(...)` for a job handle. AsyncConnection
supports
the same expression. It uses the existing MV REST endpoints and registry
jobs,
without opening a SQL query connection. Source names are exact
root-namespace
table names. Additional select/where/limit options are rejected for this
source.
The SQL declaration remains available:
```sql
CREATE MATERIALIZED VIEW images_clean AS
SELECT * FROM vector_dedup('images', 7, 'phash', 4);
REFRESH MATERIALIZED VIEW images_clean;
```
This is the host-facing SQL entry point. The MV builder now accepts a
typed native vector source; the Rust SDK also supports the same
definition through `MaterializedViewDefinition::from_sql` and
`prepare_definition`; `view.refresh().execute()` executes it locally.
Hosts distribute its units using their existing job infrastructure. The
raw `vector_duplicate_pairs` source remains available separately when
callers want the pair relation.
## Implementation and contract
- Use actual Rust Lance `13.0.0-beta.15` (`db53dcf367`); the native
partition primitive computes pairs from index representations without
source-vector aggregation or a Python pair loop.
- Extend partitioned MV refresh with two dependent waves. Physical
segment/partition tasks stream pairs through a bounded external sort,
select direct representatives and write per-source-fragment bitmap
artifacts. Source-fragment tasks start after every selection receipt
completes and stream retained original rows, including blob payloads,
into uncommitted output fragments.
- Reuse normal blob write coercion and row-mask scans, including stable
row-ID translation after compaction. Null vectors, isolated rows and
source deletions are preserved correctly. Conflicting representative
decisions across overlapping index scopes fail before publication.
- Expose `dependency_units()` and `write_refresh_partition_with_inputs`
alongside the existing plan/commit API. Receipts bind
snapshot/configuration/policy/destination generation. Only a complete
accepted set can publish; unfinished units restart, completed units can
be reused. There is no pair-position checkpoint.
- Use MV format 4 for dedup results and format 3 for native pairs;
Python and TypeScript metadata readers recognize both. No source
deletion or source row-ID migration occurs.
Only same-segment/same-partition edges are covered. Quantized scores use
symmetric represented-vector distances. Result materialization requires
V2 data storage and is initially a native MV source, not an ordinary
SELECT table function. Compressed vertex masks stay in memory; edge
sorting spills to local disk. Unreferenced artifacts use normal Lance
retention/cleanup and must be retained for the job's recovery window.
## Validation
AWS Linux validation:
- Native pair/result integration matrix passed: multiple
segments/partitions, exact thresholds, empty output, cross-batch data,
single-process pair parity, source preservation, lost receipts and
publication guards.
- Blob/null-vector/pinned-deletion tests passed with both row-ID modes,
including compaction that changes physical row addresses.
- Representative policy, forced external-sort spill and
conflicting/corrupt artifact tests passed.
- Full MV regression suite: 153 passed, 2 pre-existing ignored. Rust
formatting and Clippy passed.
- Python MV suite: 47 passed; TypeScript MV suite: 11 passed. Native
bindings were rebuilt in project-local environments; formatting, lint
and TypeScript docs passed.
- Rust declaration doctests: 3 passed, including the complete local
dedup example.
- Companion host validation: 41 MV job tests passed (1 pre-existing
ignored), full workspace Clippy passed, and the complete process-level
CLI E2E suite passed (3 tests). Recovery exercises both selection and
materialization waves.
- A real CIFAR-10 pHash run used five actual registry-worker owners,
completed 32 selection + 24 materialization tasks, and retained 49,846
of 50,000 rows with identical original values and image payloads.
Host workflow performance measurements are recorded in the companion
host PR. Cross-platform CI is still running. The previous remote-test
bootstrap failed before Cargo tests with `401 Bad credentials`; the
workflow fix described below is being validated by CI. No deployment or
merge is performed.
## Python source validation
AWS tests cover automatic and explicit snapshots, physical/stable row
IDs, local
and remote creation, sync/job/async-connection APIs, delayed refresh,
strict
options and identifier quoting. The native Python extension was rebuilt
through
the project's uv/maturin backend. Ruff, Pyright and the full MkDocs
API-reference
build passed; both new public symbols are rendered.
A real 50,000-image FEv2 run disabled both Python SQL entry points and
supplied an
unreachable SQL endpoint. The new API still materialized the same 49,846
retained
rows and original image payloads. Three recorded creation/refresh jobs
completed
56 tasks each on all five registry-worker owners, and remained on source
snapshot
3 after source latest advanced to 4. No worker or server wire-protocol
changes
were needed for this client entry point.
Previous CI authentication failure:
https://github.com/lancedb/lancedb/actions/runs/36334679276/job/108663151265.
## Review and CI fixes
- Validate all JSON merge source fields before projection, rejecting
unknown and duplicate names at every struct level. Missing/reordered
fields and ordinary JSON merges remain supported. A checked-in 12-case
integration matrix covers root fields, structs, lists, large lists,
fixed-size lists and map values; rejected writes leave the table version
and row count unchanged.
- Match namespace connection MV method signatures to
`MaterializedViewSource` in all four sync/async methods, fixing the
Python 3.10 import failure from runtime override checks.
- Restore the remote-test script's encrypted-CLI path: use the Actions
token for artifact download and pass `SOPHON_READ_TOKEN` under the
variable the decryption script consumes. The previous workflow omitted
that variable and attempted the private release path, failing with HTTP
401. On the final head, remote CI passes: 1,464 Rust unit tests, the
integration targets and doctests pass (3 unit tests ignored).
AWS validation for these fixes: both review reproducers failed before
their fixes and pass afterward; 55 merge tests, 29 cast tests and the
standalone 7-case admission regression pass. Python 3.10.21 imports
successfully; 90 MV/namespace tests, 14 JSON tests and four real
namespace dedup cases pass. After `794b97a0`, the native binding was
rebuilt again and the JSON suite rerun. Rust formatting/Clippy, Ruff and
Pyright pass. Sophon's locked test compilation passes against the final
SDK source for all seven affected crates. Extended runners, logs and
benchmark artifacts remain local.
Gatekeeper recommends approval on `794b97a0`, and the `breaking-change`
label is set. Final CI: **49 checks passed, 3 expected skips, no
failures or pending checks**, including remote tests, Python 3.10,
minimum dependencies and Windows ARM Rust tests. All wheel builds pass
after retrying the Linux matrix affected by transient AlmaLinux
repository-metadata 404s. All code-review threads are resolved. The PR
remains open for maintainer review and merge.
---------
Co-authored-by: Yang Cen <yangcen@Yangs-Mac-mini.local>
Co-authored-by: Lance Release <lance-dev@lancedb.com>
349 lines
13 KiB
YAML
349 lines
13 KiB
YAML
name: Rust
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- main
|
|
pull_request:
|
|
paths:
|
|
- Cargo.toml
|
|
- Cargo.lock
|
|
- rust-toolchain.toml
|
|
- deny.toml
|
|
- rust/**
|
|
- nodejs/Cargo.toml
|
|
- python/Cargo.toml
|
|
- .github/workflows/rust.yml
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
env:
|
|
# This env var is used by Swatinem/rust-cache@v2 for the cache
|
|
# key, so we set it to make sure it is always consistent.
|
|
CARGO_TERM_COLOR: always
|
|
RUST_BACKTRACE: "1"
|
|
|
|
jobs:
|
|
lint:
|
|
timeout-minutes: 30
|
|
runs-on: ubuntu-24.04
|
|
defaults:
|
|
run:
|
|
shell: bash
|
|
env:
|
|
# Need up-to-date compilers for kernels
|
|
CC: clang-18
|
|
CXX: clang++-18
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
fetch-depth: 0
|
|
lfs: true
|
|
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
|
with:
|
|
components: rustfmt, clippy
|
|
- uses: Swatinem/rust-cache@v2
|
|
with:
|
|
# Restore everywhere, but only save from main. Per-PR saves are
|
|
# unreadable outside their own branch anyway, since GitHub scopes
|
|
# caches to the creating ref.
|
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
|
- name: Install dependencies
|
|
run: |
|
|
sudo apt update
|
|
sudo apt install -y protobuf-compiler libssl-dev
|
|
- name: Run format
|
|
run: cargo fmt --all -- --check
|
|
- name: Run clippy
|
|
run: cargo clippy --profile ci --workspace --tests --all-features -- -D warnings
|
|
- name: Run clippy (without remote feature)
|
|
run: cargo clippy --profile ci --workspace --tests -- -D warnings
|
|
|
|
deny:
|
|
# Supply-chain checks: advisories, licenses, banned crates, and source
|
|
# restrictions. Configuration lives in `deny.toml` at the workspace root.
|
|
timeout-minutes: 10
|
|
runs-on: ubuntu-24.04
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- uses: EmbarkStudios/cargo-deny-action@v2
|
|
with:
|
|
command: check advisories bans licenses sources
|
|
|
|
build-no-lock:
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 30
|
|
env:
|
|
# Need up-to-date compilers for kernels
|
|
CC: clang
|
|
CXX: clang++
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
# Building without a lock file often requires the latest Rust version since downstream
|
|
# dependencies may have updated their minimum Rust version.
|
|
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
|
with:
|
|
toolchain: "stable"
|
|
# Remove cargo.lock to force a fresh build
|
|
- name: Remove Cargo.lock
|
|
run: rm -f Cargo.lock
|
|
- uses: rui314/setup-mold@v1
|
|
- uses: Swatinem/rust-cache@v2
|
|
with:
|
|
# Restore everywhere, but only save from main. Per-PR saves are
|
|
# unreadable outside their own branch anyway, since GitHub scopes
|
|
# caches to the creating ref.
|
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
|
- name: Install dependencies
|
|
run: |
|
|
sudo apt update
|
|
sudo apt install -y protobuf-compiler libssl-dev
|
|
- name: Build all
|
|
run: |
|
|
cargo build --profile ci --benches --all-features --tests
|
|
|
|
linux:
|
|
timeout-minutes: 60
|
|
# To build all features, we need more disk space than is available
|
|
# on the free OSS github runner. This is mostly due to the the
|
|
# sentence-transformers feature.
|
|
runs-on: ubuntu-2404-4x-x64
|
|
defaults:
|
|
run:
|
|
shell: bash
|
|
working-directory: rust
|
|
env:
|
|
# Need up-to-date compilers for kernels
|
|
CC: clang-18
|
|
CXX: clang++-18
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
fetch-depth: 0
|
|
lfs: true
|
|
- uses: Swatinem/rust-cache@v2
|
|
with:
|
|
# Restore everywhere, but only save from main. Per-PR saves are
|
|
# unreadable outside their own branch anyway, since GitHub scopes
|
|
# caches to the creating ref.
|
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
|
- name: Install dependencies
|
|
run: |
|
|
sudo apt update
|
|
sudo apt install -y protobuf-compiler libssl-dev
|
|
- uses: rui314/setup-mold@v1
|
|
- name: Make Swap
|
|
run: |
|
|
swapfile=/swapfile
|
|
min_swap_bytes=$((15 * 1024 * 1024 * 1024))
|
|
active_swap_bytes="$(sudo swapon --show=NAME,SIZE --bytes --noheadings | awk '$1 == "/swapfile" { print $2 }')"
|
|
if [ -n "$active_swap_bytes" ]; then
|
|
if [ "$active_swap_bytes" -ge "$min_swap_bytes" ]; then
|
|
echo "/swapfile is already active with enough space; skipping swap creation"
|
|
exit 0
|
|
fi
|
|
echo "/swapfile is already active but smaller than 16G; using /mnt/lancedb-swapfile"
|
|
swapfile=/mnt/lancedb-swapfile
|
|
fi
|
|
if sudo swapon --show=NAME --noheadings | grep -Fxq "$swapfile"; then
|
|
echo "$swapfile is already active; skipping swap creation"
|
|
exit 0
|
|
fi
|
|
sudo rm -f "$swapfile"
|
|
sudo fallocate -l 16G "$swapfile"
|
|
sudo chmod 600 "$swapfile"
|
|
sudo mkswap "$swapfile"
|
|
sudo swapon "$swapfile"
|
|
- name: Build
|
|
run: cargo build --profile ci --all-features --tests --locked --examples
|
|
- name: Run feature tests
|
|
run: CARGO_ARGS="--profile ci" make -C ./lancedb feature-tests
|
|
- name: Run examples
|
|
run: cargo run --profile ci --all-features --example simple --locked
|
|
|
|
remote:
|
|
timeout-minutes: 30
|
|
# Running this requires access to secrets, so skip if this is a PR from a
|
|
# fork. Keep it separate from the all-features build so Cargo does not
|
|
# retain both dependency graphs in one target directory.
|
|
if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork
|
|
runs-on: ubuntu-2404-4x-x64
|
|
defaults:
|
|
run:
|
|
shell: bash
|
|
working-directory: rust
|
|
env:
|
|
CC: clang-18
|
|
CXX: clang++-18
|
|
GH_TOKEN: ${{ github.token }}
|
|
SOPHON_READ_TOKEN: ${{ secrets.SOPHON_READ_TOKEN }}
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
fetch-depth: 0
|
|
lfs: true
|
|
- uses: Swatinem/rust-cache@v2
|
|
with:
|
|
# Remote tests use a different feature graph from the main Linux
|
|
# job. Cache downloads, but build into a fresh target directory.
|
|
cache-targets: false
|
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
|
- name: Install dependencies
|
|
run: |
|
|
sudo apt update
|
|
sudo apt install -y protobuf-compiler libssl-dev
|
|
- uses: rui314/setup-mold@v1
|
|
- name: Run remote tests
|
|
run: CARGO_ARGS="--profile ci" make -C ./lancedb remote-tests
|
|
|
|
macos:
|
|
timeout-minutes: 60
|
|
strategy:
|
|
matrix:
|
|
mac-runner: ["macos-14", "macos-15"]
|
|
runs-on: "${{ matrix.mac-runner }}"
|
|
defaults:
|
|
run:
|
|
shell: bash
|
|
working-directory: rust
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
fetch-depth: 0
|
|
lfs: true
|
|
- name: CPU features
|
|
run: sysctl -a | grep cpu
|
|
- uses: Swatinem/rust-cache@v2
|
|
with:
|
|
# Restore everywhere, but only save from main. Per-PR saves are
|
|
# unreadable outside their own branch anyway, since GitHub scopes
|
|
# caches to the creating ref.
|
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
|
- name: Install dependencies
|
|
run: brew install protobuf
|
|
- name: Run tests
|
|
run: |
|
|
# Don't run the s3 integration tests since docker isn't available
|
|
# on this image.
|
|
ALL_FEATURES=`cargo metadata --format-version=1 --no-deps \
|
|
| jq -r '.packages[] | .features | keys | .[]' \
|
|
| grep -v s3-test | sort | uniq | paste -s -d "," -`
|
|
# Run doctests before test binaries fill the runner disk. Examples are
|
|
# already built by the Linux job, so avoid retaining them here.
|
|
cargo test --profile ci --features $ALL_FEATURES --locked --doc
|
|
cargo test --profile ci --features $ALL_FEATURES --locked --lib --tests
|
|
|
|
windows:
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- target: x86_64-pc-windows-msvc
|
|
runner: windows-2022
|
|
# windows-11-arm is a standard runner, so it is free on public repos.
|
|
# Running natively lets the aarch64 tests actually execute -- this
|
|
# job used to cross-compile them and then skip the test step, paying
|
|
# full codegen and link cost for a compile check.
|
|
- target: aarch64-pc-windows-msvc
|
|
runner: windows-11-arm
|
|
runs-on: ${{ matrix.runner }}
|
|
defaults:
|
|
run:
|
|
working-directory: rust/lancedb
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- name: Set target
|
|
run: rustup target add ${{ matrix.target }}
|
|
- uses: Swatinem/rust-cache@v2
|
|
with:
|
|
# Restore everywhere, but only save from main. Per-PR saves are
|
|
# unreadable outside their own branch anyway, since GitHub scopes
|
|
# caches to the creating ref.
|
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
|
- name: Install Protoc v21.12
|
|
run: choco install --no-progress protoc
|
|
- name: Build
|
|
run: |
|
|
$env:VCPKG_ROOT = $env:VCPKG_INSTALLATION_ROOT
|
|
cargo build --profile ci --features aws,remote --tests --locked --target ${{ matrix.target }}
|
|
- name: Run tests
|
|
run: |
|
|
$env:VCPKG_ROOT = $env:VCPKG_INSTALLATION_ROOT
|
|
# `--target` has to match the build step above. Without it cargo uses
|
|
# target/ci/ rather than target/<triple>/ci/ and rebuilds the entire
|
|
# dependency graph a second time.
|
|
cargo test --profile ci --features aws,remote --locked --target ${{ matrix.target }}
|
|
|
|
msrv:
|
|
# Check the minimum supported Rust version
|
|
name: MSRV Check - Rust v${{ matrix.msrv }}
|
|
runs-on: ubuntu-24.04
|
|
strategy:
|
|
matrix:
|
|
msrv: ["1.91.0"] # This should match up with rust-version in Cargo.toml
|
|
env:
|
|
# Need up-to-date compilers for kernels
|
|
CC: clang-18
|
|
CXX: clang++-18
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
submodules: true
|
|
- name: Install dependencies
|
|
run: |
|
|
sudo apt update
|
|
sudo apt install -y protobuf-compiler libssl-dev
|
|
- name: Install ${{ matrix.msrv }}
|
|
uses: dtolnay/rust-toolchain@master
|
|
with:
|
|
toolchain: ${{ matrix.msrv }}
|
|
- uses: Swatinem/rust-cache@v2
|
|
with:
|
|
# Restore everywhere, but only save from main. Per-PR saves are
|
|
# unreadable outside their own branch anyway, since GitHub scopes
|
|
# caches to the creating ref.
|
|
save-if: ${{ github.ref == 'refs/heads/main' }}
|
|
- name: Downgrade dependencies
|
|
# These packages have newer requirements for MSRV
|
|
run: |
|
|
cargo update -p aws-sdk-bedrockruntime --precise 1.77.0
|
|
cargo update -p aws-sdk-dynamodb --precise 1.68.0
|
|
cargo update -p aws-config --precise 1.6.0
|
|
cargo update -p aws-sdk-kms --precise 1.63.0
|
|
cargo update -p aws-sdk-s3 --precise 1.79.0
|
|
cargo update -p aws-sdk-sso --precise 1.62.0
|
|
cargo update -p aws-sdk-ssooidc --precise 1.63.0
|
|
cargo update -p aws-sdk-sts --precise 1.63.0
|
|
# aws-runtime/sigv4/credential-types/types and the aws-smithy-*
|
|
# crates bumped their MSRV to 1.91.1 in late 2026; pin to the last
|
|
# 1.91.0-compatible versions. The order matters — each downgrade
|
|
# only succeeds once everything that still pins it at a higher
|
|
# version has itself been downgraded.
|
|
cargo update -p aws-runtime --precise 1.5.12
|
|
cargo update -p aws-types --precise 1.3.9
|
|
cargo update -p aws-sigv4 --precise 1.3.5
|
|
cargo update -p aws-credential-types --precise 1.2.8
|
|
# aws-smithy-checksums must stay at or above 0.63.13: OpenDAL's S3
|
|
# service needs crc-fast ~1.9, and older releases pin it to ~1.3.
|
|
cargo update -p aws-smithy-checksums --precise 0.63.13
|
|
cargo update -p aws-smithy-runtime --precise 1.9.3
|
|
cargo update -p aws-smithy-http --precise 0.62.6
|
|
cargo update -p aws-smithy-eventstream --precise 0.60.14
|
|
cargo update -p aws-smithy-http-client --precise 1.1.3
|
|
cargo update -p aws-smithy-observability --precise 0.1.4
|
|
cargo update -p aws-smithy-query --precise 0.60.8
|
|
cargo update -p aws-smithy-runtime-api --precise 1.9.3
|
|
cargo update -p aws-smithy-async --precise 1.2.7
|
|
cargo update -p aws-smithy-types --precise 1.3.6
|
|
cargo update -p aws-smithy-xml --precise 0.60.11
|
|
cargo update -p home --precise 0.5.9
|
|
- name: cargo +${{ matrix.msrv }} check
|
|
env:
|
|
RUSTUP_TOOLCHAIN: ${{ matrix.msrv }}
|
|
run: cargo check --profile ci --workspace --tests --benches --all-features
|