Repository navigation
[python] Use Native execution by default - #11144
Conversation
| ) | ||
| prepared = owner._prepare_native_commit(non_empty_messages) | ||
| # Native 0.5.0 does not validate retained row-ID field identities against | ||
| # the schema used by each publication attempt. Python performs that check |
There was a problem hiding this comment.
commit_table_messages inspects message.new_files here before calling owner._prepare_native_commit(non_empty_messages) (and even when commit.native.enabled is false).
This is the direct cause of the 5 failing tests in both Python / Native (3.10–3.13) and Python / Python Execution:
native_write_fork_safety_test.py::test_native_preparation_propagates_fork_errors_without_python_mutations[direct-commit]native_write_fork_safety_test.py::test_native_preparation_propagates_fork_errors_without_python_mutations[cause-commit]native_write_fork_safety_test.py::test_native_preparation_propagates_fork_errors_without_python_mutations[context-commit]native_write_fork_safety_test.py::test_ordinary_native_preparation_failures_keep_the_safe_python_fallback[ordinary-error-commit]native_write_fork_safety_test.py::test_ordinary_native_preparation_failures_keep_the_safe_python_fallback[missing-runtime-commit]
In native_write_fork_safety_test.py (lines 60 - 68), operation == 'commit' creates message = Mock() with message.is_empty.return_value = False on a data-evolution table and patches native_messages_supported. Iterating for file in message.new_files here raises TypeError: 'Mock' object is not iterable before _prepare_native_commit is ever reached.
In addition, retained row-ID column identity validation only applies to partial-column updates (file.write_cols is not None), not every file with first_row_id is not None. Moving this check into native_messages_supported (or checking file.first_row_id is not None and file.write_cols is not None inside native_messages_supported / prepare_native_commit) keeps commit message validation in one place and fixes all 5 CI jobs:
| # the schema used by each publication attempt. Python performs that check | |
| prepared = owner._prepare_native_commit(non_empty_messages) |
| @@ -723,7 +723,7 @@ class CoreOptions: | |||
| SCAN_NATIVE_PLAN_ENABLED: ConfigOption[bool] = ( | |||
| ConfigOptions.key("scan.native-plan.enabled") | |||
There was a problem hiding this comment.
Flipping both SCAN_NATIVE_PLAN_ENABLED and READ_NATIVE_ENABLED defaults to True here breaks single-option opt-out (scan.native-plan.enabled=false) in native_plan_requested() (paimon-python/pypaimon/read/read_dispatch.py:408-415):
def native_plan_requested(scan) -> bool:
"""Resolve Native planning preference without loading a disabled backend."""
if scan.table.options.native_plan_enabled():
return True
if not scan.table.options.native_read_enabled():
return False
from pypaimon.execution.native.runtime import native_runtime_available
return native_runtime_available()Previously, when both options defaulted to False, falling through to scan.table.options.native_read_enabled() allowed read.native.enabled=true to also opt into native split planning. Now that READ_NATIVE_ENABLED defaults to True, if a user explicitly sets scan.native-plan.enabled=false (without also setting read.native.enabled=false), scan.table.options.native_plan_enabled() returns False, but scan.table.options.native_read_enabled() still returns True from its new default—so native_plan_requested(scan) still returns True and ignores scan.native-plan.enabled=false (which is also why the tests in this PR had to add 'read.native.enabled': 'false' alongside 'scan.native-plan.enabled': 'false').
Similarly, configure_indexed_read(table) in paimon-python/pypaimon/read/read_dispatch.py:250-259 still checks if not any(options.contains(option) for option in (CoreOptions.READ_NATIVE_ENABLED, CoreOptions.SCAN_NATIVE_PLAN_ENABLED)): and copies {CoreOptions.READ_NATIVE_ENABLED.key(): 'true'} into table.options.options, which overrides the PYPAIMON_TEST_EXECUTION=python / @pytest.mark.python_read test-lane default in conftest.py:246-252 whenever pypaimon_rust is installed.
If SCAN_NATIVE_PLAN_ENABLED is explicitly configured in scan.table.options, native_plan_requested(scan) should respect it directly:
def native_plan_requested(scan) -> bool:
"""Resolve Native planning preference without loading a disabled backend."""
if scan.table.options.options.contains(CoreOptions.SCAN_NATIVE_PLAN_ENABLED):
return scan.table.options.native_plan_enabled()
if not (scan.table.options.native_plan_enabled() or scan.table.options.native_read_enabled()):
return False
from pypaimon.execution.native.runtime import native_runtime_available
return native_runtime_available()| On Python 3.10+, `pypaimon-rust>=0.5.0` is installed by default. | ||
| `write.native.enabled` and `commit.native.enabled` both default to `true`, | ||
| selecting Rust for supported Parquet/BLOB writes and REST snapshot publication. | ||
| Set either option to `false` to select its Python execution path. |
There was a problem hiding this comment.
With write.native.enabled, read.native.enabled, and scan.native-plan.enabled all defaulting to True now, setting write.native.enabled=false alone does not select the Python execution path in create_row_id_plan_execution() (paimon-python/pypaimon/write/backend_dispatch.py:351-360):
def create_row_id_plan_execution(table, plan):
"""Select pure routing once; execution errors never replay another backend."""
if (table.options.native_write_enabled() or table.options.native_read_enabled()
or table.options.native_plan_enabled()):
from pypaimon.execution.native.write.row_id_plan import create_native_row_id_plan, NativeRowIdPlan
native = create_native_row_id_plan(table, plan)
if native is not None:
return NativeRowIdPlan(table, plan, native=native)
from pypaimon.execution.python.write.row_id_plan import PythonRowIdPlan
return PythonRowIdPlan(plan)Previously, when all three options defaulted to False, native_write_enabled() or native_read_enabled() or native_plan_enabled() worked as an opt-in check. Now that all three default to True, setting write.native.enabled=false leaves native_read_enabled() and native_plan_enabled() as True, so create_row_id_plan_execution() still routes row-ID updates (update_by_row_id) to NativeRowIdPlan unless the user disables all three options.
Should create_row_id_plan_execution() check if table.options.native_write_enabled(): (or respect an explicit write.native.enabled=false) so that setting write.native.enabled=false selects PythonRowIdPlan as documented here?
leaves12138
left a comment
There was a problem hiding this comment.
Reviewed the Native-default switch and the explicit Python opt-outs, missing-runtime fallback, and compatibility guards for bucketed append sequence ranges, shredded VARIANT field IDs, ORC shared-shredding MAP reads, and retained-row-ID update publication.
Validated against the released pypaimon-rust==0.5.0 runtime on Python 3.10:
- Native regression selection: 1,077 passed, exercising native planning, reads, writes, commits, and all seven update categories.
- Python-execution selection: 334 passed; 743 Native-only cases skipped as expected.
- CI command-boundary test: passed, including both execution modes and success/failure propagation.
All current GitHub check runs are successful. I did not rerun the full Java interoperability or Torch suites locally. No blocking issues found in the reviewed head.
Purpose
Make Native execution the default with the released pypaimon-rust 0.5.0 wheel. Install pypaimon-rust>=0.5.0 automatically on Python 3.10+ and enable native planning, reading, writing and committing for supported operations. Preserve explicit Python selection and existing fallback; Python 3.6–3.9 continues to use Python execution.
Respect explicit planner, reader and writer opt-outs independently. Indexed reads preserve the selected Python reader, and Python writes keep row-ID routing in Python. Perform compatibility checks within Native preparation so disabled backends and fork-safety errors retain their dispatch behavior.
Preserve Java/Python contracts where the released runtime needs Python execution:
Run modern Python CI against the released wheel and maintain a dedicated Python Execution job. Keep full-suite Native coverage gates out of short interoperability sessions and bootstrap pip before dependency-group installation. Use explicit Python baselines and normalize unordered primary-key scan results in tests.
Use spawn for multi-worker Torch DataLoaders to avoid inheriting Polars worker locks. Preserve writer-failure injection across both backends and update installation, execution and multiprocessing documentation.
Tests
The complete GitHub Actions matrix passed for commit
691cf96a86, including Native Python 3.10–3.13, Python Execution, Java/Python interoperability and all Java jobs.