u70b3 commented on issue #66497:
URL: https://github.com/apache/doris/issues/66497#issuecomment-5204040291
## Revised Design Proposal v2 — Asynchronous Lance Index Lifecycle
This revision supersedes the previous proposal and incorporates the review
feedback from @Gabriel39.
### Major changes from v1
- Heavy index creation, replacement, and incremental maintenance no longer
run synchronously in FE. Production execution is modeled as a durable
asynchronous job, with FE orchestration and BE or external-service execution.
- The SQL path introduces a neutral parsed index specification. Doris
resolves the target table and catalog before choosing internal-index
serialization or Lance-specific validation and dispatch.
- Vector syntax reuses the Doris category-level surface: **USING ANN** with
**index_type=IVF_PQ**.
- **BUILD INDEX** is added to the Phase-1 lifecycle as the incremental
operation for newly appended or otherwise uncovered fragments.
- **SHOW INDEX** returns only bounded logical summaries. Physical UUIDs,
dataset versions, and fragment membership are exposed through structured,
filterable metadata surfaces.
- REST behavior is capability-based. REST mutations go through Lance
Namespace APIs and never bypass the metadata service with vended credentials.
- The initial type set is narrowed to IVF_PQ, BTREE, and BITMAP. Lance
INVERTED/FTS and IVF_FLAT are deferred until the Doris query path is verified
end to end.
- Index-name resolution, post-commit reconciliation, metadata refresh
retries, and local-filesystem restrictions are defined explicitly.
## 1. Goals and Phase-1 boundary
Phase 1 provides a production-oriented lifecycle for user-visible Lance
indexes:
- create IVF_PQ vector indexes and BTREE/BITMAP scalar indexes;
- inspect logical index definitions, aggregate coverage, and logical index
count;
- inspect physical index segments through a separate metadata surface;
- incrementally index newly appended or uncovered fragments;
- atomically replace/rebuild an index by logical name;
- drop an index by logical name;
- provide durable status, cancellation, concurrency limits,
retry/reconciliation, and metadata refresh;
- support Directory Catalog operations and capability-based REST Namespace
operations.
Lance dataset manifests or the Namespace service remain the authoritative
index metadata. Doris does not persist a second copy of Lance index definitions
in its internal table/index metadata. Doris persists only its own asynchronous
job state and the information needed to reconcile an external operation.
Historical-version tables remain read-only for index mutation. Inspection
uses the selected snapshot; mutation targets only the latest writable table
state.
An FE-side synchronous native build is not part of the production
implementation. If retained temporarily for development, it must be
experimental, disabled by default, and protected by strict dataset-size,
fragment-count, and concurrency limits. It does not satisfy Phase-1 completion.
## 2. SQL surface
### Vector creation
~~~sql
CREATE INDEX [IF NOT EXISTS] idx
ON lance_ctl.db.tbl (vec_col)
USING ANN
PROPERTIES (
"index_type" = "IVF_PQ",
"metric" = "l2",
"num_partitions" = "256",
"num_sub_vectors" = "16"
);
~~~
### Scalar creation
~~~sql
CREATE INDEX idx
ON lance_ctl.db.tbl (scalar_col)
USING BTREE
PROPERTIES (
"zone_size" = "4096"
);
CREATE INDEX idx_bitmap
ON lance_ctl.db.tbl (category_col)
USING BITMAP;
~~~
### Full replacement and incremental maintenance
~~~sql
CREATE OR REPLACE INDEX idx
ON lance_ctl.db.tbl (vec_col)
USING ANN
PROPERTIES (
"index_type" = "IVF_PQ",
"metric" = "l2",
"num_partitions" = "256",
"num_sub_vectors" = "16"
);
BUILD INDEX idx ON lance_ctl.db.tbl;
~~~
**CREATE OR REPLACE INDEX** performs a full rebuild and atomically replaces
the logical index. **BUILD INDEX** indexes only fragments not covered by the
current logical index and maps to the Lance incremental optimization path.
### Inspection, status, cancellation, and drop
~~~sql
SHOW INDEX FROM lance_ctl.db.tbl;
SHOW BUILD INDEX FROM db
WHERE TableName = "tbl";
CANCEL BUILD INDEX ON lance_ctl.db.tbl (job_id);
DROP INDEX [IF EXISTS] idx ON lance_ctl.db.tbl;
~~~
The existing Doris **SHOW BUILD INDEX** and **CANCEL BUILD INDEX** surfaces
are extended to Lance external-index jobs instead of introducing a second
job-control vocabulary.
### Statement semantics
- **USING** is required for Lance tables.
- **IF NOT EXISTS** and **OR REPLACE** are mutually exclusive.
- Plain **CREATE INDEX** fails if the logical name exists.
- **IF NOT EXISTS** verifies the visible logical definition and returns a
reconciled no-op when it already matches. A conflicting definition with the
same name is an error rather than a silent no-op.
- **CREATE OR REPLACE INDEX** performs a full rebuild. It is convergent by
name but not strictly idempotent: a retry can produce new segment UUIDs and a
new dataset version.
- **BUILD INDEX** is incremental and does not change the logical definition.
- Phase 1 supports one indexed column per statement. Composite scalar
indexes are deferred.
- Unknown index types, unknown properties, unsupported column types, and
invalid property values fail during analysis.
- Create, replace, and build return after a durable job is accepted, not
after native index construction completes.
- Drop is a metadata operation. It may complete synchronously when the
selected provider exposes an atomic drop, but it still uses the same
post-commit reconciliation and refresh rules.
## 3. Neutral command model and catalog dispatch
The current internal path must not translate every parsed definition into
persisted **catalog.Index** before the target catalog is known.
The revised analysis flow is:
~~~text
parsed SQL
-> ParsedIndexSpec
-> resolve catalog / database / table
-> select InternalIndexProvider or LanceIndexProvider
-> provider-specific validation
-> internal schema-change operation or Lance external-index job
~~~
**ParsedIndexSpec** contains only SQL-level information:
- logical name;
- target columns;
- category from USING;
- properties;
- IF NOT EXISTS / OR REPLACE;
- operation kind: create, replace, incremental build, or drop.
For an internal OLAP table, the existing provider validates and translates
the specification into Doris **catalog.Index** and the legacy schema-change
path.
For a Lance external table, **LanceIndexProvider** produces a Lance-specific
immutable definition and operation request. Lance types and physical metadata
are never added to Doris internal **IndexType** serialization.
The provider boundary is also used by SHOW, BUILD, DROP, reconciliation, and
capability checks so lifecycle behavior does not drift across separate command
implementations.
## 4. Asynchronous execution model
### Durable job
Create, replace, and incremental build create a persistent **LanceIndexJob**
before heavy work starts. The minimum states are:
~~~text
PENDING
-> PREPARING
-> BUILDING
-> COMMITTING
-> REFRESHING
-> FINISHED
Terminal or recovery states:
FAILED
CANCELLING
CANCELLED
COMMITTED_REFRESH_PENDING
OUTCOME_UNKNOWN
~~~
The persisted job records:
- catalog, table identity, dataset URI or Namespace table ID;
- logical index name and normalized definition;
- operation kind and starting dataset version;
- old segment UUIDs for replace/reconciliation;
- provider and executor identity;
- worker task IDs or remote transaction ID;
- current state, progress, timestamps, and sanitized failure information.
FE failover reconstructs unfinished jobs from the edit log and resumes
polling, reconciliation, commit, or refresh. It does not blindly restart native
training.
### Responsibility split
FE performs:
- SQL analysis and table-level privilege checks;
- durable job creation and state transitions;
- executor selection and concurrency admission;
- optimistic commit/finalization when the selected execution model separates
build from commit;
- outcome reconciliation;
- metadata invalidation, refresh edit log, and retry of post-commit refresh.
BE workers or an external service perform:
- column scans;
- vector training;
- scalar/vector segment construction;
- native CPU, memory, and object-store I/O work;
- periodic progress reporting and cooperative cancellation.
FE metadata locks are not held during native training. Doris serializes
conflicting jobs for the same dataset and logical index name, while Lance
transaction rules remain authoritative for conflicts with external writers.
### Resource and cancellation behavior
- Jobs are admitted through explicit per-catalog and cluster-wide
concurrency limits.
- BE execution is associated with workload-management accounting where the
executor supports it.
- Cancellation stops unscheduled work and signals active workers.
- A production executor must define whether cancellation is cooperative or
hard. Doris must not report **CANCELLED** while native work is still able to
commit.
- If an executor cannot reliably cancel an in-flight native call, the job
remains **CANCELLING** until the operation ends and is reconciled.
- Native exception strings are not parsed to implement automatic conflict
retry. Typed conflict results are used where the provider exposes them.
- Same-name concurrent create/replace conflicts are surfaced as retryable
failures unless a later reconciliation proves that this job's requested
postcondition was committed.
## 5. Provider and catalog behavior
### Directory Catalog
Directory-backed datasets use direct Dataset operations. Heavy builds must
execute outside FE.
For object-store-backed datasets, workers use the catalog credentials and
the dataset URI resolved from the latest table metadata.
A local/file dataset is mutable only when the deployment guarantees that the
path is visible with identical contents to the selected executor and to every
FE that may reconcile or finalize the operation. Otherwise mutation is
rejected. Local filesystem support is intended primarily for single-node
development and tests; it must not silently depend on the current master FE's
local disk.
### REST Namespace Catalog
REST behavior is capability-based:
- **SHOW INDEX** uses Namespace list/stats operations.
- Create, replace, and drop use the Namespace index operations when
supported by the server.
- Managed-versioning and service-side transaction policy are preserved.
- Doris never performs a direct Dataset mutation with vended credentials as
a fallback for an unsupported Namespace mutation.
- An unsupported Namespace operation returns a clear capability error.
- If the Namespace service returns an asynchronous transaction or operation
ID, the Doris job tracks and polls it.
- Physical-segment inspection is exposed only when the Namespace capability
supplies the required metadata; Doris does not bypass the service to obtain it.
## 6. Initial type and property matrix
Only types verified by both creation and the Doris query path are enabled.
### IVF_PQ
- SQL category: **ANN**
- Required property: **index_type=IVF_PQ**
- Column count: exactly one
- Column shape: fixed-size list
- Initial element types: FLOAT16, FLOAT32, UINT8, and INT8; FLOAT64 is
deferred because the pinned lance-c creation surface does not advertise it
- Nullability: the vector field and its elements must be non-null in Phase 1
- Metrics: L2, COSINE, and DOT
- **num_partitions**: optional positive integer; SDK default is used when
absent
- **num_sub_vectors**: optional positive integer; must divide the vector
dimension
- **num_bits**: fixed to 8 in Phase 1
- Advanced centroids, codebooks, sample rate, training iterations, and
HNSW/RQ/SQ properties are not user-visible in Phase 1
- Build/query compatibility is verified against the exact Lance Java SDK and
lance-c versions pinned by Doris
### BTREE
- Column count: exactly one
- Initial column types: integral numeric, floating numeric, DECIMAL, STRING,
DATE, DATETIME, and TIMESTAMPTZ types already supported by the Lance predicate
pushdown path
- Nullable columns are supported only with end-to-end coverage for equality,
range, IS NULL, and IS NOT NULL over indexed and unindexed fragments
- Optional **zone_size** must be a positive integer
- Distributed-only/internal fields such as range identifiers are not exposed
as user properties
### BITMAP
- Column count: exactly one
- Initial column types: BOOLEAN, integral numeric, STRING, and DATE
- Nullable columns are supported with explicit NULL predicate tests
- No user-visible properties in Phase 1
- Distributed shard identifiers remain executor-internal
### Deferred types
- **IVF_FLAT** remains disabled until an index built by the lifecycle path
is consumed by the Doris vector query path in an end-to-end test.
- Lance **INVERTED** is not exposed as Doris INVERTED. It is an FTS/BM25
index with different query and tokenizer semantics. A later design should
expose it as **FTS** together with Doris FTS query support.
- Other Lance scalar and vector types are rejected rather than passed
through.
## 7. Logical and physical metadata surfaces
Lance has a logical index identified by name, columns, type, definition, and
aggregate coverage. It may contain multiple physical segments with independent
UUIDs, dataset versions, and fragment sets.
### SHOW INDEX
The existing 13-column Doris schema is retained. It returns one row per
logical index/column:
- **Key_name**: logical name;
- **Column_name**: indexed column;
- **Index_type**: ANN, BTREE, or BITMAP;
- **Properties**: bounded logical details only, such as Lance physical type,
metric, indexed/unindexed row and fragment counts, and supported index-specific
parameters.
The Properties field never contains a segment array or fragment-ID list.
Logical index count is the number of user-visible logical descriptions,
excluding system indexes. It is not derived from physical manifest entries. No
separate count statement is added.
### Physical metadata
A filterable **lance_index_segments** system table or TVF exposes one row
per physical segment:
- catalog, database, table;
- logical index name;
- segment UUID;
- physical index type and version;
- dataset version;
- indexed row and fragment counts;
- creation timestamp when available.
Fragment membership is exposed through a streaming/filterable
**lance_index_segment_fragments** surface with one row per segment UUID and
fragment ID, instead of embedding an unbounded list in one MySQL result field.
Both surfaces read live metadata from the selected Lance snapshot. They do
not persist a copy in Doris catalog metadata.
## 8. Authorization and identifier semantics
- CREATE, CREATE OR REPLACE, BUILD, DROP, and CANCEL require table-level
ALTER.
- SHOW INDEX, SHOW BUILD INDEX, and physical metadata inspection require
table-level SHOW.
- Remote storage still uses catalog credentials; Doris table privilege
remains the user-facing authorization boundary.
Doris preserves the display case returned by Lance but resolves
user-supplied index names case-insensitively, matching existing Doris/MySQL
identifier behavior.
- New indexes whose names differ only by case are rejected.
- Create, build, replace, and drop first resolve a unique case-insensitive
logical match, then pass the exact physical name to Lance.
- If a pre-existing external dataset contains case-only collisions, SHOW
displays all of them, while mutation by that ambiguous name fails with an
explicit ambiguity error.
- Phase 1 does not claim quoted exact-name resolution because the current
parsed identifier path does not reliably preserve a distinct quoted-name
matching mode.
## 9. Versioning, concurrency, and visibility
- A successful create, replace, build, or drop commits a new table version
atomically.
- Queries already pinned to an older version continue to use that version.
- Mutation of a historical-version table is rejected.
- Ordinary mutation and live inspection target the latest version.
- Concurrent append is compatible with a build; newly appended fragments may
remain uncovered and are reported as unindexed.
- BUILD INDEX later adds coverage for uncovered fragments.
- Overwrite, restore, fragment rewrite, or data replacement follows Lance
transaction-conflict rules.
- Doris does not hold table metadata locks for the lifetime of a build.
- Doris prevents two active jobs for the same dataset and case-insensitive
logical name, but it does not assume it controls external writers.
- Queries over a partially indexed table must combine indexed results with
scans of unindexed fragments. End-to-end tests must prove result equivalence
with index use disabled.
## 10. Failure recovery and metadata refresh
The implementation distinguishes three outcomes.
### Confirmed pre-commit failure
The external manifest or Namespace transaction was not committed. The job
becomes FAILED. Doris does not run post-DDL refresh. Unreferenced artifacts are
reclaimed later by Lance cleanup according to retention policy.
### Confirmed commit
The requested postcondition is visible in the latest snapshot:
- create: a matching logical definition is present;
- replace: the definition matches and the old segment identity has been
replaced;
- incremental build: coverage has advanced or is already complete;
- drop: the logical name is absent.
Doris invalidates and refreshes table metadata and emits the normal
external-DDL refresh edit log. A refresh failure does not turn the job into an
ordinary build failure; the job becomes **COMMITTED_REFRESH_PENDING** and
retries refresh.
### Unknown or post-commit failure
After FE failover, timeout, lost worker response, or an ambiguous commit
result, Doris reopens the latest snapshot or queries the Namespace transaction
and verifies the operation-specific postcondition.
- If the postcondition holds, Doris records the commit, runs refresh, and
finishes the job.
- If it does not hold and the provider proves no commit occurred, the job
fails or is safely retried according to the operation policy.
- If the outcome remains unknowable, the job remains **OUTCOME_UNKNOWN** and
exposes a diagnostic rather than silently rebuilding.
A reconciled **IF NOT EXISTS** or **IF EXISTS** no-op still
invalidates/refreshes Doris metadata when it observes external state that may
be newer than the local cache.
SHOW INDEX is the user-visible reconciliation tool, but it is not a
substitute for repairing Doris metadata caches.
## 11. Test plan
Tests cover:
- parser and to-SQL behavior for ANN/index_type, BTREE, BITMAP, OR REPLACE,
BUILD, SHOW, CANCEL, and DROP;
- internal-table behavior remaining unchanged after neutral command
refactoring;
- ALTER/SHOW privileges;
- the complete type/property matrix and rejection of unknown values;
- duplicate, IF NOT EXISTS, case-only duplicate, ambiguous pre-existing
name, replace, incremental build, and drop semantics;
- durable job state transitions, FE restart/failover recovery, concurrency
limits, cancellation, and retryable conflicts;
- successful commit followed by edit-log/refresh failure and subsequent
reconciliation;
- logical versus physical metadata and multi-segment logical counts;
- append after create, coverage decay, incremental BUILD, and query
correctness across indexed plus unindexed fragments;
- historical-version mutation rejection;
- local/file visibility restrictions;
- REST list/stats, supported mutation, unsupported capability,
managed-versioning, and asynchronous transaction polling;
- end-to-end IVF_PQ, BTREE, and BITMAP query use through the Doris BE reader.
Regression tests use isolated writable datasets and generate expected result
files through the normal regression-test scripts.
## Decisions requested from reviewers
### Q1: First production executor
The pinned lance-c v0.1.2 exposes synchronous create/drop/list/count
operations but does not expose the Java SDK's distributed segment
build/merge/commit or incremental optimize APIs.
Which Phase-1 execution target is acceptable?
1. A durable asynchronous job executed on one BE using the current direct
lance-c build API, with distributed build and incremental BUILD requiring
lance-c extensions before they are enabled.
2. Extend lance-c first with uncommitted segment build, merge/commit,
optimize, progress, and cancellation APIs, then implement distributed BE
execution as part of this issue.
3. Use an external index-build service where available, with Directory
Catalog mutation deferred until a production executor exists.
My preference is option 2 for the complete production lifecycle. If option 1
is acceptable as an intermediate milestone, it must not claim hard cancellation
or incremental maintenance until the required native APIs exist.
### Q2: Job-control surface
Is extending the existing SHOW BUILD INDEX and CANCEL BUILD INDEX commands
preferable to introducing Lance-specific SHOW/CANCEL job commands? Reuse keeps
a common Doris lifecycle vocabulary, but the result schema may need additive
fields for catalog, operation kind, external transaction ID, and reconciliation
state.
### Q3: REST Phase-1 scope
Should Phase 1:
- support REST SHOW plus create/drop whenever the Namespace service
advertises the corresponding capabilities; or
- limit REST to SHOW/list/stats until asynchronous Namespace transaction
behavior is exercised against a real compatible service?
In both cases Doris will not bypass the Namespace service with direct
dataset mutation.
### Q4: Physical metadata surface
Is a pair of filterable surfaces — one row per physical segment and one row
per segment/fragment mapping — acceptable, or should Phase 1 expose only
segment summaries and defer fragment membership?
References:
- Doris existing CREATE/BUILD/SHOW/CANCEL index command paths
- #65730 and #66340
- Lance distributed indexing: https://lance.org/guide/distributed_indexing/
- Lance Spark CREATE INDEX and SHOW INDEXES
- Lance Namespace index operations
- Pinned Lance Java SDK v9.1.0-beta.3 and lance-c v0.1.2
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]