Skip to content

[DNM][CORE] Replace string-based native conf key lists with declarative NativeConfRegistry#12549

Draft
jackylee-ch wants to merge 8 commits into
apache:mainfrom
jackylee-ch:config-pass-to-native
Draft

[DNM][CORE] Replace string-based native conf key lists with declarative NativeConfRegistry#12549
jackylee-ch wants to merge 8 commits into
apache:mainfrom
jackylee-ch:config-pass-to-native

Conversation

@jackylee-ch

@jackylee-ch jackylee-ch commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

[DNM] Do Not Merge — for design discussion.

This PR fully replaces the hard-coded, string-based native conf key lists in GlutenConfig.getNativeSessionConf / getNativeBackendConf with a declarative registration mechanism, so each module declares its own native confs on demand instead of common code maintaining giant key lists.

The mechanism

  1. NativeConfRegistry (gluten-core): a central registry of conf keys to pass to native side, keyed by NativeScope:

    • RUNTIME: dynamic, passed each time a native runtime instance is created;
    • BACKEND: static, passed once during native backend initialization;
    • ALL: both.

    Runtime and backend scopes are tracked separately, so the same key can carry different semantics per scope. Each entry supports:

    • defaultToPass: if defined, the key is always passed, using the default value when unset by user (replaces the old "configs having default values" lists);
    • transform: applied to user-set values before passing (replaces the old per-key special cases such as byte-unit conversion for spark.shuffle.file.buffer and upper-casing for spark.sql.legacy.timeParserPolicy).
  2. ConfigBuilder.passToNative(): Gluten config entries declare native passing right at their definitions. No scope argument — the native scope is derived from the conf's staticness: static confs (buildStaticConf) go to BACKEND, dynamic confs (buildConf) go to RUNTIME:

val COLUMNAR_MAX_BATCH_SIZE =
  buildConf("spark.gluten.sql.columnar.maxBatchSize")
    .passToNative()   // dynamic conf -> passed on each native runtime creation
    .intConf
    .createWithDefault(4096)

val DEBUG_CUDF =
  buildStaticConf("spark.gluten.sql.debug.cudf")   // static conf -> passed at backend init
    .passToNative()
    .passDefault()    // native relies on the key: pass the default when unset
    .booleanConf
    .createWithDefault(false)

passDefault() marks that the entry's own default value (parsed form, e.g. a bytesConf default "1KB" is delivered as "1024") is passed even when the conf is not set by user.

Confs whose native consumption scope differs from the derived one (e.g. static velox cachePrefetchMinPct read on runtime creation; dynamic memory-sizing confs consumed at backend init) register explicitly via NativeConfRegistry.register at the end of their conf object body, with comments explaining the mismatch.

  1. NativeConfRegistry.register: for non-Gluten keys (SQLConf / Spark core / Hadoop / S3 / GCS) that have no Gluten ConfigEntry, and for the scope-mismatch cases above. Common registrations live in registerNativeConfs() in GlutenConfig; velox-only keys are registered from VeloxConfig in backends-velox instead of common code. Registrations are naturally modular: a backend's registrations only take effect when its conf object loads, so backend-specific keys never leak across deployments.

What is removed

  • The nativeKeys set (40+ hard-coded strings, including velox-specific keys living in common code) in GlutenConfig.
  • The two "configs having default values" Seqs and all per-key special-case code in getNativeSessionConf / getNativeBackendConf. Both methods now do: registry selection + existing prefix rules + UGI tokens (session only).
  • BackendSettingsApi.extraNativeSessionConfKeys / extraNativeBackendConfKeys (no backend ever overrode them).
  • GlutenConfigUtil.mapByteConfValue (superseded by entry-level transform).

Behavior compatibility

The selected key/value results of getNativeSessionConf / getNativeBackendConf are intended to be identical to before. Notable equivalence mappings:

Old code New declaration
nativeKeys filter .passToNative() on dynamic confs / register(key, RUNTIME)
session default-value Seq .passToNative().passDefault() / register(..., defaultToPass)
mapByteConfValue special cases register(..., transform = byte conversion)
timeParserPolicy.toUpperCase register(..., transform = upper-case)
backend default-value Seq .passToNative().passDefault() on static confs / register(..., BACKEND, defaultToPass)
backend keys filter .passToNative() on static confs / register(key, BACKEND)
velox keys in common code declared in VeloxConfig (backends-velox)

Prefix-based rules (spark.gluten.sql.columnar.backend.<backend>, spark.hadoop.fs.s3a. etc.) are kept as-is, since they are pattern rules rather than string key registrations.

Staticness alignment with native consumption scope

Static confs are for native backend initialization (immutable afterwards); non-static confs are for native session. Confs whose declared staticness did not match their actual native consumption were fixed:

  1. Static but consumed per native runtime -> made non-static

    • spark.gluten.sql.columnar.backend.velox.cudf.enableTableScan: read by native in WholeStageResultIterator / SubstraitToVeloxPlan on every query, not at backend init, so it is session-tunable now.
  2. Non-static but consumed only at native backend init -> made static

    • spark.gluten.velox.awsSdkLogLevel, spark.gluten.velox.fs.s3a.retry.mode: only read when building the reused native HiveConnector at backend init; session changes never took effect.
    • spark.gluten.memoryOverhead.size.in.bytes: only set on SparkConf at driver start (VeloxListenerApi), consumed by Velox backend init for global memory sizing.
    • spark.gluten.velox.s3UseProxyFromEnv, spark.gluten.velox.s3PayloadSigningPolicy: promoted from raw string registrations to static ConfigEntrys in VeloxConfig.
  3. Non-static and consumed by both backend init and session -> allowed, dual-registered and documented in code

Conf Backend-init consumption Session consumption Special note
spark.gluten.sql.debug keep user glog levels per-task debug dumps log level frozen at startup; session toggle only affects per-query dumps
spark.gluten.sql.columnar.cudf one-time GPU env init per-query CPU/GPU offload decision enabling in session without startup enablement will not initialize GPU
spark.gluten.memory.task.offHeap.size.in.bytes CH external sort/agg settings Velox per-task spill limit mutated at runtime by GlutenAutoAdjustStageResourceProfile
spark.gluten.memory.offHeap.size.in.bytes CH spill thresholds (JVM-side only) must stay non-static for resource-profile adjustment; BACKEND-only native registration
spark.gluten.numTaskSlotsPerExecutor Velox io/spill thread sizing (key must always be present, default passed when unset) (JVM-side only) same as above

Design document

docs/developers/NativeConfPassing.md documents the full design: the two delivery channels (BACKEND at native backend init, RUNTIME at native runtime creation), scope derivation from conf staticness, the passToNative()/passDefault() builder API, explicit NativeConfRegistry.register for non-Gluten keys, prefix rules, and the enumerated dual-scope confs with their split semantics.

How was this patch tested?

  • New/extended NativeConfRegistrySuite covering: scope derivation from conf staticness, parsed-default registration for bytes confs, passDefault constraint checks, per-scope mixed semantics, duplicate registration rejection, defaultToPass and transform semantics.
  • Existing config suites in gluten-core / gluten-substrait pass.
  • Compiled gluten-core, gluten-substrait, backends-velox with -Pspark-3.5,backends-velox (checkstyle/scalastyle/spotless clean). backends-clickhouse Java code compiles; the delta33 Scala compile failure on this machine is pre-existing and unrelated.

AI disclosure: this PR was developed with the assistance of Cursor (Fable 5), with human review of all changes.

…ister native confs

Currently, conf keys passed to native side are maintained in hard-coded
string lists (`nativeKeys` in GlutenConfig, `extraNative*ConfKeys` in
backend settings) that are decoupled from where the configs are defined.
This introduces an additive registration mechanism:

- `ConfigBuilder.passToNative(scope)` marks a Gluten config entry to be
  passed to native side, registered automatically on entry creation.
- `NativeConfScope` (SESSION/BACKEND/BOTH) declares whether the conf is
  passed on each native runtime creation, once at backend init, or both.
- `NativeConfRegistry` collects registrations; raw Spark/Hadoop keys can
  be registered via `registerRaw`, optionally with a default value that
  is always passed.
- `getNativeSessionConf`/`getNativeBackendConf` additionally include
  registered confs, without changing existing key lists or prefix rules.

This enables incremental migration of the hard-coded lists and allows
each module to register its own native confs on demand.

Generated-by: Claude (Cursor Agent)
@github-actions github-actions Bot added the CORE works for Gluten Core label Jul 17, 2026
@github-actions

Copy link
Copy Markdown

Run Gluten Clickhouse CI on x86

…iveConfRegistry

Complete the migration from hard-coded native conf key lists to the declarative
passToNative mechanism:

- NativeConfRegistry now tracks session/backend scopes separately, and supports
  per-entry defaultToPass (always-send with default) and transform (value
  normalization such as byte-unit conversion and upper-casing).
- ConfigBuilder gains passToNative(scope) and passToNativeWithDefault(scope,
  default) that auto-register entries on creation, including parsed default
  values (e.g. bytes confs pass numeric bytes).
- GlutenConfig: remove the nativeKeys set, the hard-coded default value lists
  and per-key special cases in getNativeSessionConf/getNativeBackendConf; both
  methods now select confs from NativeConfRegistry plus the existing prefix
  rules. Non-Gluten keys (SQLConf/Spark core/Hadoop/S3/GCS) are registered via
  NativeConfRegistry.registerRaw in one place.
- GlutenCoreConfig/VeloxConfig: declare native passing at conf definitions;
  velox-only raw keys are registered in backends-velox instead of common code.
- Remove BackendSettingsApi.extraNativeSessionConfKeys/extraNativeBackendConfKeys
  (no overrides existed) and the now-unused GlutenConfigUtil.mapByteConfValue.
- Extend NativeConfRegistrySuite to cover scopes, defaults, transforms and
  duplicate registration.

Generated-by: Cursor 2.4.28 (Fable 5)
@github-actions github-actions Bot added the VELOX label Jul 17, 2026
@github-actions

Copy link
Copy Markdown

Run Gluten Clickhouse CI on x86

@jackylee-ch jackylee-ch changed the title [DNM][CORE] Introduce ConfigBuilder.passToNative to declaratively register native confs [DNM][CORE] Replace string-based native conf key lists with declarative NativeConfRegistry Jul 17, 2026
…ope enum to NativeScope

Address review feedback on API ergonomics:

- Merge passToNative/passToNativeWithDefault into one method:
  passToNative(scope, alwaysPass, default). alwaysPass = true means the conf
  is always delivered to native with its (parsed) default when unset by user.
- Rename the scope enum NativeConfScope -> NativeScope with clearer values:
  SESSION -> RUNTIME (passed on each native runtime creation), BOTH -> ALL;
  BACKEND unchanged (passed once at native backend initialization).
- Rename NativeConfRegistry.registerRaw -> register, selectSessionConf ->
  selectRuntimeConf, isSessionKey -> isRuntimeKey accordingly.

Generated-by: Cursor 2.4.28 (Fable 5)
@github-actions

Copy link
Copy Markdown

Run Gluten Clickhouse CI on x86

…PassToNative property

Address review feedback:

- passToNative(scope) now takes only the scope, defaulting to ALL; users may
  narrow it to BACKEND or RUNTIME.
- Remove the default parameter: the entry's own (parsed) default value is used.
- Split always-pass semantics into a separate chainable alwaysPassToNative()
  property instead of a passToNative parameter. It requires passToNative() and
  an entry default value.
- COLUMNAR_SHUFFLE_CODEC(_BACKEND) are optional entries without defaults, so
  their always-pass-empty-string behavior moves to raw registrations in
  registerNativeConfs().

Generated-by: Cursor 2.4.28 (Fable 5)
@github-actions

Copy link
Copy Markdown

Run Gluten Clickhouse CI on x86

…ssToNative to passDefault

Address review feedback:

- passToNative() no longer takes a scope. The native scope is derived from the
  conf's staticness: static confs (buildStaticConf) are passed to native
  backend initialization; dynamic confs (buildConf) are passed on each native
  runtime creation. buildStaticConf marks the builder via markStatic().
- Confs whose consumption scope differs from the derived one (e.g. static
  velox cachePrefetchMinPct read at runtime creation, dynamic memory sizing
  confs consumed at backend init) drop the builder marker and register
  explicitly through NativeConfRegistry.register at the end of their conf
  object body, with comments explaining the mismatch.
- Rename alwaysPassToNative() to the shorter passDefault(), conveying that the
  entry's default value is passed even when the conf is not set by user.

Generated-by: Cursor 2.4.28 (Fable 5)
@github-actions

Copy link
Copy Markdown

Run Gluten Clickhouse CI on x86

Follow the rule that static confs are for native backend initialization
(immutable afterwards) and non-static confs are for native session:

1. Static but consumed per native runtime -> make it non-static:
   - velox cudf.enableTableScan

2. Non-static but consumed only at native backend init -> make it static:
   - velox awsSdkLogLevel, fs.s3a.retry.mode (HiveConnector built once)
   - core memoryOverhead.size.in.bytes (set on SparkConf at driver start only)
   Also promote raw-registered s3UseProxyFromEnv / s3PayloadSigningPolicy
   to static ConfigEntries.

3. Non-static and consumed by both backend init and session -> keep
   dual registration and document the special semantics:
   - debug enabled, cudf enabled, task offheap size
   - offheap size / task slots stay non-static solely because
     GlutenAutoAdjustStageResourceProfile mutates them at runtime.

Generated-by: Cursor 2.4.28 (Fable 5)
@github-actions github-actions Bot added the DOCS label Jul 18, 2026
@github-actions

Copy link
Copy Markdown

Run Gluten Clickhouse CI on x86

Documents the NativeConfRegistry design: the two delivery channels
(BACKEND at backend init, RUNTIME at native runtime creation), scope
derivation from conf staticness, the passToNative/passDefault builder
API, explicit registration for non-Gluten keys, and the enumerated
dual-scope confs with their split semantics.

Generated-by: Cursor 2.4.28 (Fable 5)
@github-actions

Copy link
Copy Markdown

Run Gluten Clickhouse CI on x86

…ass lazily evaluated

- NativeConfRegistrySuite: filter select results to the keys under test so
  assertions are not affected by real registrations from conf objects (e.g.
  GlutenCoreConfig) loaded by other suites in the same JVM.
- NativeConfRegistry: evaluate defaultToPass on each selection instead of
  snapshotting it at registration. spark.sql.session.timeZone's default
  follows the current JVM default time zone, so a snapshot taken at conf
  object initialization passes a stale time zone to native.
@github-actions

Copy link
Copy Markdown

Run Gluten Clickhouse CI on x86

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CORE works for Gluten Core DOCS VELOX

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant