mirror of
https://github.com/lancedb/lancedb.git
synced 2026-11-14 22:56:54 +00:00
A broad documentation pass across the areas LanceDB Enterprise and Embedded diverge on most: **Data Curation** -- GPU Function execution, Slurm scheduling (and what Slurm is, for readers new to it), blob late materialization as its own page, joins, aggregation/profiling, sampling, near-duplicate detection, chunking, distributed shuffle, and IVF-partition clustering. **Feature Engineering** -- computed columns and materialized views, restructured to lead with the Function-based path and show the SQL form side by side with Python throughout; a single Auto Refresh page instead of duplicating scheduling details on every refresh page; adaptive execution; checkpointing; GPU and Slurm placement for refresh workers; a Training Cache page for the PyTorch data loader's automatic cache benefit. **Security & Governance** -- an Audit Log reference (schema, access, grants), Secrets promoted to an ordinary catalog object with their own SQL DDL and a vending/rotation guide, and privilege-table fixes verified against the authorization source. **Deployment & Maintenance** -- Horizontal Scaling and OpenTelemetry moved into a real Deployment area; Automated Cleanup folded into Intelligent Auto-Maintenance (every other maintenance decision already lived there); RTE Compaction and Advanced Conflict Resolution documented in depth. **Landing page** -- the Enterprise feature comparison table reorganized (several redundant or stale rows removed, several new areas added), and the "why Enterprise" bullets replaced with ones tied to a concrete page (Performance, Scale, Fault tolerance, Multicloud deployment, Simplify operations) instead of vague promises. Plus a full pass addressing lancedb-gatekeeper's review: a real test-safety bug (a quickstart test could silently overwrite and delete a deployment's own `frames` table), a fabricated SQL statement with no backing implementation, an actual dedup-guarantee bug in `take_row_ids`, and dozens of documentation-accuracy fixes verified against source rather than taken on faith -- each either fixed, or left with an explicit note on why it needs a feature or deployment owner's sign-off instead of being guessed at. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_018PXMCbRH6i5w47TsjeEUMT --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
167 lines
6.2 KiB
Plaintext
167 lines
6.2 KiB
Plaintext
---
|
|
title: "Cluster records by index partition"
|
|
sidebarTitle: "Clustering"
|
|
description: "Group records by the partition an existing IVF vector index already assigns them to, without training a new clustering model."
|
|
icon: circle-nodes
|
|
keywords: ["clustering", "ivf_partition", "ivf", "vector index", "group by", "centroid"]
|
|
availability:
|
|
embedded: unavailable
|
|
enterprise: available
|
|
---
|
|
|
|
`ivf_partition` groups records by the partition a vector falls into, using the
|
|
centroids of an IVF-family vector index you already built for search -- no
|
|
separate clustering model to train or maintain. Use it as a `GROUP BY` key to
|
|
aggregate per cluster, or to sample across clusters instead of across rows.
|
|
|
|
## Example data {#example-data}
|
|
|
|
Connect with an [Enterprise connection](/basics/catalog#enterprise).
|
|
|
|
<Accordion title="Create the example table">
|
|
|
|
Use the same `curation_embeddings` fixture as [dedupe](/curation/dedupe#example-data)
|
|
-- eight records, where rows 10 and 20 are near-identical, 30, 40 and 50
|
|
form one looser cluster, and 60, 70 and 80 are each distinct and
|
|
increasingly far apart -- but build the index with more partitions.
|
|
Clustering wants partitions fine-grained enough to tell the two clusters
|
|
apart; deduplication only needs a distance threshold within one:
|
|
|
|
<CodeGroup>
|
|
```python Python icon="python"
|
|
table.create_index("embedding", config=IvfFlat(num_partitions=5, distance_type="l2"))
|
|
table.wait_for_index(["embedding_idx"])
|
|
```
|
|
|
|
```typescript TypeScript icon="square-js"
|
|
await table.createIndex("embedding", {
|
|
config: lancedb.Index.ivfFlat({ numPartitions: 5, distanceType: "l2" }),
|
|
});
|
|
await table.waitForIndex(["embedding_idx"], 300);
|
|
```
|
|
|
|
```rust
|
|
use std::time::Duration;
|
|
|
|
use lancedb::index::vector::IvfFlatIndexBuilder;
|
|
use lancedb::index::Index;
|
|
use lancedb::DistanceType;
|
|
|
|
table
|
|
.create_index(
|
|
&["embedding"],
|
|
Index::IvfFlat(IvfFlatIndexBuilder::default().num_partitions(5).distance_type(DistanceType::L2)),
|
|
)
|
|
.execute()
|
|
.await?;
|
|
table.wait_for_index(&["embedding_idx"], Duration::from_secs(300)).await?;
|
|
```
|
|
</CodeGroup>
|
|
|
|
`ivf_partition` reads an existing index's trained centroids; it does not
|
|
train one. The column needs an up-to-date, single-segment IVF-family index
|
|
before the function runs, the same requirement [`dedupe`](/curation/dedupe#example-data)
|
|
has.
|
|
|
|
</Accordion>
|
|
|
|
## Group records by partition {#group}
|
|
|
|
`ivf_partition(column)` returns the partition each row's vector falls into.
|
|
Rows closer to the same centroid than to any other land in the same
|
|
partition, so the near-identical cat photos (10, 20) group together, the
|
|
three dog photos (30, 40, 50) group together, and the three increasingly
|
|
distant photos (60, 70, 80) are more likely to each keep their own partition:
|
|
|
|
<CodeGroup>
|
|
```sql SQL icon="database"
|
|
SELECT ivf_partition(embedding) AS cluster, array_agg(id) AS ids, count(*) AS n
|
|
FROM curation_embeddings
|
|
GROUP BY cluster
|
|
ORDER BY cluster
|
|
```
|
|
</CodeGroup>
|
|
|
|
The exact partition numbers depend on how the index's centroids were trained,
|
|
but rows that are close together in embedding space consistently land in the
|
|
same group. Aggregate anything per cluster this way -- `count(*)` for cluster
|
|
sizes, `avg(score)` for a per-cluster quality signal, or `array_agg(id)` to
|
|
collect candidate rows for a downstream pass.
|
|
|
|
## Sample across clusters instead of across rows {#sample}
|
|
|
|
Pass a selection mode to group by only a subset of the index's partitions,
|
|
instead of all of them:
|
|
|
|
<CodeGroup>
|
|
```sql SQL icon="database"
|
|
-- The first 2 partitions, deterministically
|
|
SELECT ivf_partition(embedding, 'first', 2) AS cluster, array_agg(id) AS ids
|
|
FROM curation_embeddings
|
|
GROUP BY cluster;
|
|
|
|
-- 2 partitions chosen at random, reproducible via the seed
|
|
SELECT ivf_partition(embedding, 'random', 2, 42) AS cluster, array_agg(id) AS ids
|
|
FROM curation_embeddings
|
|
GROUP BY cluster;
|
|
```
|
|
</CodeGroup>
|
|
|
|
`'first'` takes the first `count` partitions and accepts no seed, since the
|
|
result is already deterministic. `'random'` requires a seed, so the same
|
|
call reproduces the same partitions. Either way, the result is a
|
|
spatially-stratified sample across the embedding space: one that covers
|
|
each selected region of the data, rather than a plain row sample that can
|
|
easily pick many near-duplicate rows from a dense cluster and miss a sparse
|
|
one entirely.
|
|
|
|
## Keep a clustering current with a materialized view {#materialized}
|
|
|
|
Wrap a `GROUP BY ivf_partition(...)` query in a
|
|
[materialized view](/management/materialized-view) so the grouping stays
|
|
current as new records arrive:
|
|
|
|
<CodeGroup>
|
|
```sql SQL icon="database"
|
|
CREATE MATERIALIZED VIEW curation_embeddings_clustered AS
|
|
SELECT ivf_partition(embedding) AS cluster, array_agg(id) AS ids
|
|
FROM curation_embeddings
|
|
GROUP BY cluster
|
|
```
|
|
</CodeGroup>
|
|
|
|
A refresh reassigns every row's partition from the index's current centroids,
|
|
so retraining the index with
|
|
[`replace=True`](/feature-engineering/add-computed-column) regroups the view
|
|
on the next refresh. See
|
|
[Refresh a materialized view](/feature-engineering/refresh-materialized-view)
|
|
for manual and automatic refresh options.
|
|
|
|
## A typical use case: deduplication {#dedupe}
|
|
|
|
Near-duplicate records are close together in embedding space, so they
|
|
consistently land in the same or a neighboring partition. Grouping by
|
|
`ivf_partition` first, then comparing distances only within each group, finds
|
|
near-duplicate candidates without a full pairwise scan across the whole
|
|
table.
|
|
|
|
This is exactly the approach [`dedupe` and `duplicate_pairs`](/curation/dedupe)
|
|
use internally, packaged as a table function with a `distance_threshold`
|
|
instead of a `GROUP BY` clause to write yourself. Reach for those directly for
|
|
deduplication; use `ivf_partition` when the task is a different kind of
|
|
per-cluster grouping or sampling, not deduplication specifically.
|
|
|
|
## Related guides {#next}
|
|
|
|
<CardGroup cols={2}>
|
|
<Card title="IVF_PARTITION" icon="circle-nodes" href="/sql/dql/ivf-partition">
|
|
The full function signature: arguments, sampling modes, and positional syntax.
|
|
</Card>
|
|
<Card title="Deduplicate near-duplicates" icon="clone" href="/curation/dedupe">
|
|
The packaged table function for the deduplication use case above.
|
|
</Card>
|
|
<Card title="Statistical Sampling" icon="dice" href="/curation/statistical-sampling">
|
|
Row-level and fragment-level sampling, without clustering.
|
|
</Card>
|
|
</CardGroup>
|