Skip to content

Commit f22bcdd

Browse files
committed
Remove deprecated checkpoint system and dead code from index migrator
- Remove QuantizationCheckpoint, BatchUndoBuffer, trigger_bgsave_and_wait, async_trigger_bgsave_and_wait from reliability.py (dead code) - Remove checkpoint_path parameter from MigrationExecutor.apply() and AsyncMigrationExecutor.apply() - Remove deprecated --resume CLI flag and legacy_resume handling - Remove TestResumeDeprecation test class - Update docs to use --backup-dir as primary crash-safe recovery mechanism - Update cli.rst to document current flags (--backup-dir, --workers, etc.) - Replace 'checkpoint' wording with 'state file/tracking' in batch commands
1 parent ecff7d1 commit f22bcdd

7 files changed

Lines changed: 77 additions & 425 deletions

File tree

docs/api/cli.rst

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,7 @@ Manage document-preserving index migrations. This command group provides subcomm
479479
* - ``batch-plan``
480480
- Generate a batch migration plan for multiple indexes
481481
* - ``batch-apply``
482-
- Execute a batch migration plan with checkpointing
482+
- Execute a batch migration plan with state tracking
483483
* - ``batch-resume``
484484
- Resume an interrupted batch migration
485485
* - ``batch-status``
@@ -560,17 +560,23 @@ Execute a reviewed drop/recreate migration plan. Use ``--async`` for large migra
560560
- Description
561561
* - ``--async``
562562
- Run migration asynchronously (recommended for large quantization jobs)
563+
* - ``--backup-dir``
564+
- Directory for vector backup files. Enables crash-safe resume and rollback.
565+
* - ``--batch-size``
566+
- Keys per pipeline batch (default 500)
567+
* - ``--workers``
568+
- Number of parallel workers for quantization (default 1). Requires ``--backup-dir``.
569+
* - ``--keep-backup``
570+
- Keep backup files after successful migration (default: auto-delete)
563571
* - ``--query-check-file``
564572
- Path to a YAML file with post-migration query checks
565-
* - ``--resume``
566-
- Path to a checkpoint file for crash-safe recovery
567573

568574
**Example**
569575

570576
.. code-block:: bash
571577
572578
rvl migrate apply --plan plan.yaml
573-
rvl migrate apply --plan plan.yaml --async --resume checkpoint.yaml
579+
rvl migrate apply --plan plan.yaml --async --backup-dir /tmp/backups --workers 4
574580
575581
rvl migrate wizard
576582
^^^^^^^^^^^^^^^^^^

docs/user_guide/how_to_guides/migrate-indexes.md

Lines changed: 59 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -403,12 +403,12 @@ warnings:
403403

404404
| Interruption Point | Documents | Index | Recovery |
405405
|--------------------|-----------|-------|----------|
406-
| After drop, before quantize | Unchanged | **None** | Re-run apply (or `--resume` if checkpoint exists) |
407-
| During quantization | Partially quantized | **None** | Re-run with `--resume` to continue from checkpoint |
406+
| After drop, before quantize | Unchanged | **None** | Re-run apply (or pass `--backup-dir` to resume from backup) |
407+
| During quantization | Partially quantized | **None** | Re-run with same `--backup-dir` to resume from last batch |
408408
| After quantization, before create | Quantized | **None** | Re-run apply (will recreate index) |
409409
| After create | Correct | Rebuilding | Wait for index ready |
410410

411-
The underlying documents are **never deleted** by `drop_recreate` mode. For large quantization jobs, use `--resume` to enable checkpoint-based recovery. See [Crash-safe resume for quantization](#crash-safe-resume-for-quantization) below.
411+
The underlying documents are **never deleted** by `drop_recreate` mode. For large quantization jobs, use `--backup-dir` to enable crash-safe recovery. See [Crash-safe resume for quantization](#crash-safe-resume-for-quantization) below.
412412

413413
## Step 4: Apply the Migration
414414

@@ -444,7 +444,7 @@ The migration executor follows this sequence:
444444
- Writes back the converted vector to the same document
445445
- Processes documents in batches of 500 using Redis pipelines
446446
- Skipped for JSON storage (vectors are re-indexed automatically on recreate)
447-
- **Checkpoint support**: For large datasets, use `--resume` to enable crash-safe recovery
447+
- **Backup support**: For large datasets, use `--backup-dir` to enable crash-safe recovery and rollback
448448

449449
**STEP 4: Key renames** (if changing key prefix)
450450
- If the migration changes the key prefix, renames each key from old prefix to new prefix
@@ -495,15 +495,36 @@ See {doc}`/concepts/index-migrations` for detailed async vs sync guidance.
495495

496496
### Crash-safe resume for quantization
497497

498-
When migrating large datasets with vector quantization (e.g. float32 to float16), the re-encoding step can take minutes or hours. If the process is interrupted (crash, network drop, OOM kill), you don't want to start over. The `--resume` flag enables checkpoint-based recovery.
498+
When migrating large datasets with vector quantization (e.g. float32 to float16), the re-encoding step can take minutes or hours. If the process is interrupted (crash, network drop, OOM kill), you don't want to start over. The `--backup-dir` flag enables crash-safe recovery.
499499

500500
#### How it works
501501

502-
1. **Pre-flight estimate**: before any mutations, `apply` prints a disk space estimate showing RDB snapshot cost, AOF growth (if enabled), and post-migration memory savings.
503-
2. **BGSAVE safety snapshot**: the migrator triggers a Redis `BGSAVE` and waits for it to complete before modifying any data. This gives you a point-in-time snapshot to fall back on.
504-
3. **Checkpoint file**: when `--resume` is provided, the migrator writes a YAML checkpoint after every batch of 500 documents. The checkpoint records how many keys have been processed and the last batch of keys written.
505-
4. **Batch undo buffer**: if a single batch fails mid-write, original vector values are rolled back via pipeline before the error propagates. Only the current batch is held in memory.
506-
5. **Idempotent skip**: on resume, vectors that were already converted are detected by byte-width inspection and skipped automatically.
502+
When you pass `--backup-dir`, the migrator saves original vector bytes to disk before mutating them. Two files are created:
503+
504+
```
505+
<backup-dir>/
506+
migration_backup_<index_name>.header # JSON: phase, progress counters, field metadata
507+
migration_backup_<index_name>.data # Binary: length-prefixed batches of original vectors
508+
```
509+
510+
The **header file** is a small JSON file that tracks progress through a state machine:
511+
512+
```
513+
dump → ready → active → completed
514+
```
515+
516+
- **dump**: original vectors are being read from Redis and written to the data file, one batch at a time
517+
- **ready**: all original vectors have been backed up; safe to proceed with quantization
518+
- **active**: quantization is in progress; the header tracks which batches have been written back to Redis
519+
- **completed**: all batches have been quantized and the migration finished successfully
520+
521+
The header is atomically updated (temp file + rename) after every batch, so a crash never corrupts it.
522+
523+
The **data file** is append-only binary. Each batch is stored as a 4-byte big-endian length prefix followed by a pickled blob containing the batch's keys and their original vector bytes.
524+
525+
On resume, the executor loads the header, sees how many batches were already quantized (`quantize_completed_batches`), and skips ahead in the data file to continue from the next unfinished batch.
526+
527+
**Disk usage:** approximately `num_docs × dims × bytes_per_element`. For example, 1M docs with 768-dim float32 vectors ≈ 2.9 GB.
507528

508529
#### Step-by-step: using crash-safe resume
509530

@@ -533,97 +554,61 @@ If AOF is enabled:
533554
rvl migrate estimate --plan migration_plan.yaml --aof-enabled
534555
```
535556

536-
**2. Apply with checkpoint enabled:**
557+
**2. Apply with backup enabled:**
537558

538559
```bash
539560
rvl migrate apply \
540561
--plan migration_plan.yaml \
541-
--resume quantize_checkpoint.yaml \
562+
--backup-dir /tmp/migration_backups \
542563
--url redis://localhost:6379 \
543564
--report-out migration_report.yaml
544565
```
545566

546-
The `--resume` flag takes a path to a checkpoint file. If the file does not exist, a new checkpoint is created. If it already exists (from a previous interrupted run), the migrator resumes from where it left off.
567+
The `--backup-dir` flag takes a directory path. If no backup exists there, a new one is created. If one already exists (from a previous interrupted run), the migrator resumes from where it left off.
547568

548569
**3. If the process crashes or is interrupted:**
549570

550-
The checkpoint file (`quantize_checkpoint.yaml`) will contain the progress:
551-
552-
```yaml
553-
index_name: products_idx
554-
total_keys: 1000000
555-
completed_keys: 450000
556-
completed_batches: 900
557-
last_batch_keys:
558-
- 'products:449501'
559-
- 'products:449502'
560-
# ...
561-
status: in_progress
562-
checkpoint_path: quantize_checkpoint.yaml
571+
The header file will contain the progress:
572+
573+
```json
574+
{
575+
"index_name": "products_idx",
576+
"fields": {"embedding": {"source": "float32", "target": "float16", "dims": 768}},
577+
"batch_size": 500,
578+
"phase": "active",
579+
"dump_completed_batches": 2000,
580+
"quantize_completed_batches": 900
581+
}
563582
```
564583

584+
This tells you: all 2000 batches of original vectors were backed up, and 900 of them have been quantized so far.
585+
565586
**4. Resume the migration:**
566587

567588
Re-run the exact same command:
568589

569590
```bash
570591
rvl migrate apply \
571592
--plan migration_plan.yaml \
572-
--resume quantize_checkpoint.yaml \
593+
--backup-dir /tmp/migration_backups \
573594
--url redis://localhost:6379 \
574595
--report-out migration_report.yaml
575596
```
576597

577598
The migrator will:
578-
- Detect the existing checkpoint and skip already-processed keys
579-
- Re-enumerate documents via SCAN (the index was already dropped before the crash)
580-
- Continue quantizing from where it left off
581-
- Print progress like `[4/6] Quantize vectors: 450,000/1,000,000 docs`
599+
- Detect the existing backup and skip already-quantized batches
600+
- Continue quantizing from batch 901 onward
601+
- Print progress like `Quantize vectors: 450,000/1,000,000 docs`
582602

583603
**5. On successful completion:**
584604

585-
The checkpoint status is set to `completed`. You can safely delete the checkpoint file.
586-
587-
#### What gets rolled back on batch failure
588-
589-
If a batch of 500 documents fails mid-write (e.g. Redis returns an error), the migrator:
590-
1. Restores original vector bytes for all documents in that batch using the undo buffer
591-
2. Saves the checkpoint (so progress up to the last successful batch is preserved)
592-
3. Raises the error
593-
594-
This means you never end up with partially-written vectors in a single batch.
605+
The backup phase is set to `completed`. By default, backup files are **automatically deleted** after a successful migration. Pass `--keep-backup` to retain them for post-migration auditing or potential rollback.
595606

596607
#### Limitations
597608

598-
- **Same-width conversions** (float16 to bfloat16, or int8 to uint8) are **not supported** with `--resume`. These conversions cannot be detected by byte-width inspection, so idempotent skip is impossible. The migrator will refuse to proceed and suggest running without `--resume`.
599-
- **JSON storage** does not need vector re-encoding (Redis re-indexes JSON vectors on `FT.CREATE`). The checkpoint is still created for consistency but no batched writes occur.
600-
- The checkpoint file must match the migration plan. If you change the plan, delete the old checkpoint and start fresh.
601-
602-
#### Python API with checkpoints
603-
604-
```python
605-
from redisvl.migration import MigrationExecutor
606-
607-
executor = MigrationExecutor()
608-
report = executor.apply(
609-
plan,
610-
redis_url="redis://localhost:6379",
611-
checkpoint_path="quantize_checkpoint.yaml",
612-
)
613-
```
614-
615-
For async:
616-
617-
```python
618-
from redisvl.migration import AsyncMigrationExecutor
619-
620-
executor = AsyncMigrationExecutor()
621-
report = await executor.apply(
622-
plan,
623-
redis_url="redis://localhost:6379",
624-
checkpoint_path="quantize_checkpoint.yaml",
625-
)
626-
```
609+
- **Same-width conversions** (float16 to bfloat16, or int8 to uint8) are **not supported** for resume. These conversions cannot be detected by byte-width inspection, so idempotent skip is impossible.
610+
- **JSON storage** does not need vector re-encoding (Redis re-indexes JSON vectors on `FT.CREATE`). The backup is still created for consistency but no batched writes occur.
611+
- The backup must match the migration plan. If you change the plan, delete the old backup directory and start fresh.
627612

628613
## Step 5: Validate the Result
629614

@@ -706,7 +691,7 @@ rvl migrate validate \
706691
- `--indexes` : Explicit list of index names
707692
- `--indexes-file` : File containing index names (one per line)
708693
- `--schema-patch` : Path to shared schema patch YAML
709-
- `--state` : Path to checkpoint state file
694+
- `--state` : Path to batch state file for resume
710695
- `--failure-policy` : `fail_fast` or `continue_on_error`
711696
- `--accept-data-loss` : Required for quantization (lossy changes)
712697
- `--retry-failed` : Retry previously failed indexes on resume
@@ -1038,14 +1023,14 @@ rvl migrate batch-apply \
10381023

10391024
**Flags for batch-apply:**
10401025
- `--accept-data-loss` : Required when quantizing vectors (float32 → float16 is lossy)
1041-
- `--state` : Path to checkpoint file (default: `batch_state.yaml`)
1026+
- `--state` : Path to batch state file (default: `batch_state.yaml`)
10421027
- `--report-dir` : Directory for per-index reports (default: `./reports/`)
10431028

10441029
**Note:** `--failure-policy` is set during `batch-plan`, not `batch-apply`. The policy is stored in the batch plan file.
10451030

10461031
### Resume After Failure
10471032

1048-
Batch migration automatically checkpoints progress. If interrupted:
1033+
Batch migration automatically tracks progress in the state file. If interrupted:
10491034

10501035
```bash
10511036
# Resume from where it left off
@@ -1157,7 +1142,7 @@ print(f"Successful: {report.summary.successful}/{report.summary.total_indexes}")
11571142

11581143
4. **Review skipped indexes**: The `skip_reason` often indicates schema differences that need attention.
11591144

1160-
5. **Keep checkpoint files**: The `batch_state.yaml` is essential for resume. Don't delete it until the batch completes successfully.
1145+
5. **Keep state files**: The `batch_state.yaml` is essential for resume. Don't delete it until the batch completes successfully.
11611146

11621147
## Performance Tuning
11631148

redisvl/cli/migrate.py

Lines changed: 5 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class Migrate:
4343
"\trollback Restore original vectors from a backup directory (undo quantization)",
4444
"\tvalidate Validate a completed migration plan against the live index",
4545
"\tbatch-plan Generate a batch migration plan for multiple indexes",
46-
"\tbatch-apply Execute a batch migration plan with checkpointing",
46+
"\tbatch-apply Execute a batch migration plan with state tracking",
4747
"\tbatch-resume Resume an interrupted batch migration",
4848
"\tbatch-status Show status of an in-progress or completed batch migration",
4949
"\n",
@@ -238,13 +238,6 @@ def apply(self):
238238
help="Keep backup files after successful migration (default: auto-delete).",
239239
default=False,
240240
)
241-
# Deprecated alias for --backup-dir (was --resume in previous versions)
242-
parser.add_argument(
243-
"--resume",
244-
dest="legacy_resume",
245-
help=argparse.SUPPRESS, # hidden from help
246-
default=None,
247-
)
248241
parser.add_argument(
249242
"--report-out",
250243
help="Path to write migration_report.yaml",
@@ -267,29 +260,6 @@ def apply(self):
267260
if args.num_workers < 1:
268261
parser.error("--workers must be >= 1")
269262

270-
# Handle deprecated --resume flag
271-
if args.legacy_resume is not None:
272-
import warnings
273-
274-
# Fail fast if the value looks like a checkpoint file (old semantics)
275-
if os.path.isfile(args.legacy_resume) or args.legacy_resume.endswith(
276-
(".yaml", ".yml")
277-
):
278-
parser.error(
279-
"--resume semantics have changed: it now expects a backup "
280-
"directory, not a checkpoint file. Use --backup-dir <dir> instead."
281-
)
282-
283-
warnings.warn(
284-
"--resume is deprecated and will be removed in a future version. "
285-
"Use --backup-dir instead: the backup directory replaces "
286-
"checkpoint files for crash-safe resume and rollback.",
287-
DeprecationWarning,
288-
stacklevel=1,
289-
)
290-
if args.backup_dir is None:
291-
args.backup_dir = args.legacy_resume
292-
293263
# Validate --workers > 1 requires --backup-dir
294264
if args.num_workers > 1 and args.backup_dir is None:
295265
parser.error("--workers > 1 requires --backup-dir")
@@ -779,7 +749,7 @@ def batch_plan(self):
779749
self._print_batch_plan_summary(args.plan_out, batch_plan)
780750

781751
def batch_apply(self):
782-
"""Execute a batch migration plan with checkpointing."""
752+
"""Execute a batch migration plan with state tracking."""
783753
parser = argparse.ArgumentParser(
784754
usage=(
785755
"rvl migrate batch-apply --plan <batch_plan.yaml> "
@@ -794,7 +764,7 @@ def batch_apply(self):
794764
)
795765
parser.add_argument(
796766
"--state",
797-
help="Path to checkpoint state file",
767+
help="Path to batch state file for resume",
798768
default="batch_state.yaml",
799769
)
800770
parser.add_argument(
@@ -848,9 +818,7 @@ def batch_resume(self):
848818
"[--plan <batch_plan.yaml>] [--retry-failed]"
849819
)
850820
)
851-
parser.add_argument(
852-
"--state", help="Path to checkpoint state file", required=True
853-
)
821+
parser.add_argument("--state", help="Path to batch state file", required=True)
854822
parser.add_argument(
855823
"--plan", help="Path to batch_plan.yaml (optional, uses state.plan_path)"
856824
)
@@ -912,9 +880,7 @@ def batch_status(self):
912880
parser = argparse.ArgumentParser(
913881
usage="rvl migrate batch-status --state <batch_state.yaml>"
914882
)
915-
parser.add_argument(
916-
"--state", help="Path to checkpoint state file", required=True
917-
)
883+
parser.add_argument("--state", help="Path to batch state file", required=True)
918884
args = parser.parse_args(sys.argv[3:])
919885

920886
state_path = Path(args.state).resolve()

redisvl/migration/async_executor.py

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,6 @@ async def apply(
509509
batch_size: int = 500,
510510
num_workers: int = 1,
511511
keep_backup: bool = False,
512-
checkpoint_path: Optional[str] = None, # deprecated, use backup_dir
513512
) -> MigrationReport:
514513
"""Apply a migration plan asynchronously.
515514
@@ -558,19 +557,6 @@ async def apply(
558557
report.finished_at = timestamp_utc()
559558
return report
560559

561-
# Handle deprecated checkpoint_path parameter
562-
if checkpoint_path is not None:
563-
import warnings
564-
565-
warnings.warn(
566-
"checkpoint_path is deprecated and will be removed in a future "
567-
"version. Use backup_dir instead.",
568-
DeprecationWarning,
569-
stacklevel=2,
570-
)
571-
if backup_dir is None:
572-
backup_dir = checkpoint_path
573-
574560
# Check if we are resuming from a backup file (post-crash).
575561
from redisvl.migration.backup import VectorBackup
576562

0 commit comments

Comments
 (0)