feat: remove and replace indexes in place (sde-index protocol 2) - #87
Merged
Merged
Conversation
A design that drops an index in force - one no read uses while every write maintains it, or one replaced by a better one - no longer needs a copy. Protocol 2 of the signed sde-index packet: the prepared indexes are the ones in force without the removed ones, byte for byte and in order, followed by the new ones, if any; at least one is removed. Both loaders read it (IndexPlan.removed, INDEX_CHANGE_PROTOCOL); migration/180-187 pin the acceptances and a refusal per changed rule, and migration/149 now refuses protocol 3. Protocol 1 is unchanged. The Python operator checks every removed index on its table in the declared shape before any DDL, builds and qualifies what is new as before, records its decision and publishes the next map, and only then removes each index, one resumable step each: DROP INDEX CONCURRENTLY IF EXISTS on PostgreSQL; on ClickHouse a pending materialization is killed and the index dropped with alter_sync = 0; the catalogue is read back. Absent, or another object under the name, after the decision means ours is gone; the other object is left alone. The receipt carries protocol 2 and a row per removed index once built, none once abandoned. A state holding such a record uses storage contract 5, which operators knowing contracts 1 to 4 refuse. Measured before the design (docs/qualification/in-place-index-drop): a concurrent drop pauses no write; an open transaction that has read the table holds it, an older snapshot alone does not; a stopped drop leaves the index invalid yet maintained, and a second drop removes it; ClickHouse drops at once with alter_sync = 0. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Remove and replace indexes in place:
sde-indexprotocol 2A design that drops an index in force - one no read uses while every write maintains it, or one
replaced by a better one - used to need a fresh copy and a cutover, whose write pause grows with the
table (past about 400 000 rows on PostgreSQL it exceeds the cutover's 30 s budget and rolls back).
Protocol 2 of the signed in-place authorization removes indexes on the live tables instead.
Measured before the design
docs/qualification/in-place-index-drop/(PostgreSQL 15.19, ClickHouse 24.8.14.39, a writerinserting every 5 ms):
DROP INDEX CONCURRENTLYon 100 000 rows: 6.3 ms, longest gap between writes 7.9 ms (10.2 msbefore). ClickHouse
ALTER TABLE ... DROP INDEX ... SETTINGS alter_sync = 0: 13.2 ms, gone fromthe catalogue at once, reads still answer.
has read it - and not for older snapshots as a concurrent build does (a snapshot alone: the
drop finished in 6.5 ms). The first run of the probe had conflated the two; the record says so.
valid = false, ready = true(unused, still maintained); asecond drop removes it.
What changed
removed ones, byte for byte and in order, followed by new ones (possibly none); at least one is
removed.
IndexPlan.removed,IndexPlan.protocol,INDEX_CHANGE_PROTOCOL. Protocol 1 isunchanged. Vectors
migration/180-187(acceptances and a refusal per changed rule);migration/149now refuses protocol 3. 342 vectors.what is new is built and qualified as before; the decision is recorded and the next map published;
only then each index is removed, one resumable step each -
DROP INDEX CONCURRENTLY IF EXISTS(theIF EXISTSonly for an earlier drop, stopped client-side by the budget, finishing inthe server meanwhile), on ClickHouse a pending materialization killed and
DROP INDEXwithalter_sync = 0; the catalogue is read back. After the decision an absent index or another objectunder the name means ours is gone, and the other object is left alone.
removed- a row per removed index once built, none once abandoned.knowing contracts 1 to 4 refuse it (measured on the real previous operator: exit 2, the directory
unchanged).
docs/in-place-index.md(protocol 2, removal, evidence),docs/format-contract.md§7j,docs/physical-design.md,conformance/README.md, README.Evidence
python/tests/test_index_change_live.pyon both engines: a removal while a process on the map inforce writes; a replacement; recovery after every step after the decision (and nothing of the map
in force touched before publication); abandonment before the decision removing nothing in force;
an index in force absent, of another shape or another sort order refused before any DDL; a removal
held by an open reader until the budget ends and resumed; a resumed removal waiting behind the
stopped drop that then finishes first; a killed operator mid-removal; a foreign object under the
name after the decision left alone; a drop that leaves its index not reported done; a pending
ClickHouse materialization of the removed index killed.
python/tests/test_cutover_project.py: state contract 5 written and required.make checkwith both live engines on this head (fd54687): Python 2010 passed and the tenoptional orderbook skips, TypeScript 953 passed.
🤖 Generated with Claude Code