Skip to content

[data] Make checkpoint restore pluggable via CheckpointConfig checkpoint_filter_cls / checkpoint_manager_cls - #65676

Open
wingkitlee0 wants to merge 5 commits into
ray-project:masterfrom
wingkitlee0:data-checkpoint-filter-cls
Open

[data] Make checkpoint restore pluggable via CheckpointConfig checkpoint_filter_cls / checkpoint_manager_cls#65676
wingkitlee0 wants to merge 5 commits into
ray-project:masterfrom
wingkitlee0:data-checkpoint-filter-cls

Conversation

@wingkitlee0

@wingkitlee0 wingkitlee0 commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Why are these changes needed?

The checkpoint restore path currently hardcodes its two concrete classes — IdColumnCheckpointManager (loads checkpointed IDs) and NumpyArrayBasedCheckpointFilter (filters blocks against them) — even though both ABCs (CheckpointManager, CheckpointFilter) already define the interfaces. This PR makes the restore path pluggable via two optional CheckpointConfig fields, checkpoint_manager_cls and checkpoint_filter_cls, mirroring the existing pluggability pattern on the write side (CheckpointWriter.create dispatches on config).

Motivation: the restore path is the main scalability pressure point for large datasets (see #60200, #61509). Concretely, to checkpoint a 10B+ row partitioned Parquet dataset, the current design doesn't scale — all checkpointed IDs are coalesced and sorted into a single in-memory array (~80 GB at 10B uint64 IDs). Supporting that scale requires checkpointing that takes advantage of the dataset's partitioning: a custom manager loads checkpoint IDs as per-partition shards instead of one array, and a matching custom filter checks each block only against its shard. This PR exposes the interfaces for that customization without changing default behavior; today it requires forking or monkeypatching internals.

Changes:

  • CheckpointConfig(checkpoint_filter_cls=..., checkpoint_manager_cls=...): both optional, validated to be subclasses of their respective ABCs, default None (existing behavior unchanged).
  • create_checkpoint_filter_op resolves the manager class and _CheckpointFilterFn.init_checkpoint_filter resolves the filter class from the config; covers both the V1 Read and V2 ReadFiles paths since both go through create_checkpoint_filter_op.
  • CheckpointFilter, NumpyArrayBasedCheckpointFilter, CheckpointManager, and IdColumnCheckpointManager are annotated @DeveloperAPI and lazily re-exported from ray.data.checkpoint (lazy to avoid the checkpoint_filter -> ray.data.context -> ray.data.checkpoint import cycle).
  • The contracts are documented on the ABCs and in the CheckpointConfig docstring: the filter is constructed per actor with (checkpoint_config, checkpointed_ids_ref); the manager is constructed on the driver with (checkpoint_config=..., data_context=...) and its load_checkpoint returns (ObjectRef, int) — the ref is passed opaquely to the filter, the size feeds the per-actor memory reservation, and (None, 0) skips the filter operator entirely.

Not duplicating existing work: no open PR touches checkpoint filter pluggability (searched gh pr list for CheckpointFilter/checkpoint filter). Related-but-different open issues: #60704 asks for custom placement of the filter in the plan, #54520 for per-op config — this PR is complementary to both (it changes which filter runs, not where).

Related issue number

Related: #60200, #61509, #60704

Checks

  • I've signed off every commit (DCO).
  • I've run pre-commit hooks (pre-commit run --files ...) — all hooks pass (semgrep skipped: unsupported on Linux ARM64 locally).
  • Tests:
    • pytest python/ray/data/tests/test_checkpoint.py run locally: 68 passed, 3 failed — the 3 (test_partial_failure_no_duplicates{,_partitioned,_row_based}) fail identically on unmodified master in this environment (ModuleNotFoundError: No module named 'test_checkpoint' from the mid-test ray.init() when pytest runs from the repo root) and all 3 pass on both master and this branch when run from python/ray/data/tests/. Not related to this change.
    • New tests (10, all passing): TestCheckpointConfig::test_{invalid,valid}_checkpoint_filter_cls and test_{invalid,valid}_checkpoint_manager_cls (config validation), test_custom_checkpoint_filter_cls (end-to-end: a no-op filter subclass keeps already-checkpointed rows, proving the custom class is instantiated in the filter actor), and test_custom_checkpoint_manager_cls (end-to-end: a manager subclass returning (None, 0) skips filtering despite checkpoint data being present, proving the custom manager drives the restore).
  • AI assistance was used for this change (Claude Code); the submitting human has reviewed every changed line and run the tests locally.

Notes for reviewers:

  • Validation rejects abstract classes (inspect.isabstract) in addition to the usual issubclass check. This is stricter than Ray's other pluggable-class params (ExecutionCallback, ShuffleAggregation), which are instantiated on the driver right after validation and so fail loudly on their own. Here the filter class is serialized to and instantiated inside remote filter actors, so without early validation an abstract class only fails deep in the actor pool as a confusing crash/retry loop — validating at CheckpointConfig construction surfaces the mistake immediately on the driver.
  • DeveloperAPI classes aren't required by the API-discrepancy check, so no doc/source/data/api/checkpoint.rst entry is included; happy to add one if preferred.
  • The dict-form DataContext.checkpoint_config path (CheckpointConfig(**value)) picks up the new kwargs automatically.

🤖 Generated with Claude Code

wingkitlee0 and others added 2 commits August 22, 2026 18:16
…nt_filter_cls

Add an optional checkpoint_filter_cls field to CheckpointConfig so users
can plug in a custom CheckpointFilter subclass for the restore path,
mirroring the existing pluggability of the write side. The class is
validated at config construction and instantiated once per checkpoint
filter actor with (checkpoint_config, checkpointed_ids_ref); default
behavior (NumpyArrayBasedCheckpointFilter) is unchanged. CheckpointFilter
and NumpyArrayBasedCheckpointFilter are annotated DeveloperAPI and lazily
re-exported from ray.data.checkpoint to avoid the import cycle through
ray.data.context.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Kit Lee <7000003+wingkitlee0@users.noreply.github.com>
…int_manager_cls

Complements checkpoint_filter_cls: a custom filter that consumes a
different loaded representation (Arrow-native, sharded) needs control
over how checkpoint data is loaded, which lives in
CheckpointManager.load_checkpoint. Add checkpoint_manager_cls with the
same validation and default-preserving semantics, annotate
CheckpointManager and IdColumnCheckpointManager as DeveloperAPI, and
document the (ObjectRef, int) contract between manager and filter.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Kit Lee <7000003+wingkitlee0@users.noreply.github.com>
@wingkitlee0 wingkitlee0 changed the title [data] Support custom checkpoint filter via CheckpointConfig.checkpoint_filter_cls [data] Make checkpoint restore pluggable via CheckpointConfig checkpoint_filter_cls / checkpoint_manager_cls Aug 22, 2026
…ses, annotate __getattr__

- Deduplicate the checkpoint_filter_cls / checkpoint_manager_cls
  validation into _validate_checkpoint_cls.
- Reject abstract classes at config construction: unlike other pluggable
  class params in Ray (ExecutionCallback, ShuffleAggregation), these are
  instantiated inside remote filter actors, so an abstract class would
  otherwise fail as a confusing actor crash instead of an immediate
  InvalidCheckpointingConfig.
- Add type annotations to the lazy __getattr__ in ray.data.checkpoint.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Kit Lee <7000003+wingkitlee0@users.noreply.github.com>
@wingkitlee0
wingkitlee0 marked this pull request as ready for review August 22, 2026 23:59
@wingkitlee0
wingkitlee0 requested a review from a team as a code owner August 22, 2026 23:59

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for custom checkpoint filters and managers by allowing users to override checkpoint_filter_cls and checkpoint_manager_cls in CheckpointConfig. It includes validation to ensure the provided classes are concrete subclasses of CheckpointFilter and CheckpointManager, exposes these classes via lazy imports to prevent circular dependencies, and adds comprehensive unit tests. The review feedback suggests using a relative import instead of an absolute import in __init__.py to improve robustness, and updating the CheckpointFilter docstring to clarify that the checkpointed IDs reference is passed opaquely rather than always being a sorted NumPy array.

Comment thread python/ray/data/checkpoint/__init__.py Outdated
Comment thread python/ray/data/checkpoint/checkpoint_filter.py Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

Reviewed by Cursor Bugbot for commit 55b1b63. Configure here.

Comment thread python/ray/data/checkpoint/checkpoint_filter.py Outdated
…_ref

- CheckpointFilter.__init__ now accepts (config, checkpoint_ref) matching
  how the restore path constructs filters, so a subclass that only
  implements filter_rows_for_block works without defining its own
  constructor (Cursor Bugbot finding).
- Clarify ABC and CheckpointConfig docstrings: the ref is whatever the
  checkpoint manager's load_checkpoint returned, a sorted NumPy array
  only by default (Gemini finding).
- Use a relative import in the package __getattr__ (Gemini finding).
- test_custom_checkpoint_filter_cls now subclasses the ABC directly with
  no __init__, covering the inherited-constructor path end-to-end.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Kit Lee <7000003+wingkitlee0@users.noreply.github.com>
@wingkitlee0 wingkitlee0 added the go add ONLY when ready to merge, run all tests label Aug 23, 2026
@ray-gardener ray-gardener Bot added performance data Ray Data-related issues community-contribution Contributed by the community labels Aug 23, 2026
…orts

CI's pyrefly check covers ray/data/checkpoint/__init__.py and flags
__all__ names that are only resolvable through the runtime __getattr__.
Add a TYPE_CHECKING import block so the lazily-exported names are
statically visible; runtime resolution is unchanged (still lazy, still
cycle-free). Verified with pyrefly==0.51.0: 0 errors on the file.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Kit Lee <7000003+wingkitlee0@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community-contribution Contributed by the community data Ray Data-related issues go add ONLY when ready to merge, run all tests performance

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant