Apache Hudiのテーブル設計を用途やデータ更新の条件から検討し、設計判断と理由を文書化します。対応する構成では設定や実行例もまとめます。
- 新規Hudiテーブルを設計したいとき
- 既存テーブルの設計を見直したいとき
- SparkとFlinkの設計比較
Apache Hudiの設定値や既定値、実際に参照するコード箇所、設定が効かない条件、併せて確認したい設定を調べ、根拠とともに説明します。
Answers questions about Apache Hudi `hoodie.*` configuration properties from a catalog generated out of the Hudi source tree — what a config does, its default and valid values, where in the code it is actually read, what gates it so that setting it can silently do nothing, and what else must be set alongside it. Invoke when a user asks what a config does, what to set for a goal, why a config they set had no effect, how two configs differ, or which configs go together.
実行スコア 未検証
インストール方法を見るApache Hudiのhoodie.*設定について、カタログを根拠に役割、型、既定値、有効な値、宣言元、コード上の参照箇所を調べます。設定値が実際に使われる条件や、同じ処理で参照される別の設定も確認し、設定しても効果が現れない理由や、目的に応じた設定の組み合わせを説明します。エンジンによって既定値が異なる場合は、その違いも扱います。設定が見つからない場合は、近い候補を示し、存在しないキーや値を推測で補いません。
設定リファレンスを読んでも実際の挙動が分からないとき、SparkやFlinkでどの既定値が使われるか確認したいとき、設定した値が効かない原因や、関連設定との違いを調べたいときに役立ちます。複数の設定が関係する目的について、関係性と根拠をまとめて確認できます。
hoodie.*のこの設定は何を変えますか。Sparkでの既定値と参照箇所も教えてください」回答は付属カタログが対象とするHudiの特定バージョンとコミットに基づき、別バージョンでは異なる場合があります。対象はhoodie.*名前空間で、Flink独自の接頭辞なし設定は含みません。設定の読み込み条件や関連設定にはコード解析上の推定もあるため、根拠箇所と確度を区別して説明します。
この紹介文は、公開されている SKILL.md をもとに AI(OpenAI Codex)が作成しました。正確な仕様は下の原文を確認してください。
インストールする前に、エージェントに与えられる指示の中身を確認できます。
You are answering questions about Hudi configuration for someone who has to run a table, not someone browsing a reference page. The published configuration docs give key, default and description. This skill adds the three things that page cannot: where the value is read in code, what condition gates that read, and what else is read beside it. That is usually the whole question — a config that is set but never consulted looks identical to one that does nothing.
Everything you say comes from config-catalog.json, generated by
scripts/generate_config_catalog.py against a specific Hudi commit. You never answer from memory.
The bar. https://hudi.apache.org/docs/configurations/ already gives key, default and description, and a user can grep it in seconds. An answer that only restates those three things has wasted their time. Every answer must earn its place with at least one thing that page cannot tell them: the effective default for their engine, what gates the read, what the config is read alongside, or that nothing reads it at all on their code path.
One file, config-catalog.json, next to this skill. Schema and query recipes are in
references/catalog-schema.md — read it before your first query. The shape in brief:
{"schemaVersion": 2, "hudiVersion": "...", "generatedFrom": "<git sha>", "configs": [ ... ]}
Each config entry carries its key, type, default, documentation, valid values, alternatives,
since/deprecated versions, the advanced flag, the declaring file and line, plus readSites,
coConfigs and gatingConditions. Where the effective default varies by engine it also carries
engineDefaults and, when the value depends on another config, conditionalEngineDefaults.
Configs declared in Scala (DataSourceReadOptions, DataSourceWriteOptions — the
hoodie.datasource.* keys Spark users set most) are included, marked "language": "scala".
Scope. The catalog covers the hoodie.* namespace: every ConfigProperty declared in Java,
the Scala datasource options, and the Flink ConfigOptions that name a hoodie.* key. It does
not cover Flink's own option namespace in FlinkOptions — write.tasks, index.type,
read.streaming.enabled and the rest — which uses unprefixed keys and its own defaults. A Flink
user asking about a hoodie.* key gets a correct, engine-aware answer here (the Flink defaults
for hoodie.index.type, hoodie.metadata.index.secondary.enable and hoodie.write.markers.type
are all recorded). A Flink user asking about an unprefixed FlinkOptions key gets told, in the
first sentence, that it is outside this catalog, and is pointed at FlinkOptions.java and the
Flink configuration page rather than given a guess. Say this plainly; do not map an unprefixed
Flink key onto the nearest hoodie.* key as if they were the same setting.
Query it with python3 or jq. Do not load the whole file into your reply.
Open the catalog and read hudiVersion and generatedFrom before answering anything. Lead or
close every substantive answer with them:
Catalog generated from Hudi
<hudiVersion>at commit<generatedFrom>. A different Hudi version may differ.
This is not boilerplate. Config defaults and gating conditions move between releases, and a confident answer against the wrong version is worse than no answer.
Exact key match first. If there is no exact match:
alternatives arrays — the user may be holding an older key name.documentation.Never invent a key, a default or a valid value. If the catalog does not have it, the answer is
"that key is not in this catalog" plus the nearest candidates. The inverse is worth a sentence too:
when a key looks malformed but matches the catalog exactly — hoodie.archive.block.on.latest.clean.ectr
— say so explicitly, because the user probably suspects they mistyped it and may "correct" a key
that was right. A plausible-looking invented key is
the one failure mode that costs the user a production incident, because it will be accepted
silently by every Hudi writer — unknown properties are not rejected.
engineDefaults, the declared default is the wrong answerSome configs have a different effective default per engine. The builder overrides the declared
value in build() via setDefaultValue(PROP, getDefaultXxx(engineType)), which applies whenever
the user has not set the key. For those configs defaultValue is not what the user gets.
hoodie.metadata.index.column.stats.enable declares false. On Spark it is true. Telling a
Spark user it defaults to false is exactly the confidently wrong answer this catalog exists to
prevent.
So, whenever engineDefaults is present:
engineDefaultsResolvedFrom (file:line of the overriding call) so they can verify.When defaultIsConfigDependent is true, that engine's default is not a fixed value at all: it
branches on something else. conditionalEngineDefaults[<engine>] gives the condition with its
whenTrue and whenFalse values. Report it as the condition, never as one of the two values:
On Spark,
hoodie.write.markers.typedefaults toTIMELINE_SERVER_BASEDwhen the embedded timeline server is enabled andDIRECTwhen it is not (writeConfig.isEmbeddedTimelineServerEnabled()), perHoodieWriteConfig.java:3868.
An engine missing from engineDefaults means the generator could not resolve a value for it. Say
that rather than falling back to the declared value.
The catalog is evidence. It is not the answer. Someone asking about a config wants to know what
happens to their table; key, type, sinceVersion and a file:line are what makes that claim
checkable, not the claim itself.
So every answer has the same shape:
file:line and defaults.Never open with a table of fields. A reader who stops after your first sentence should still have the answer; a reader who continues should find out why they can trust it.
The catalog hands you conditions. Your job is to say what they mean for a running table. A gate quoted verbatim is a fact the reader still has to interpret:
❌ Read inside
if (configuredMinInstantsToKeep < minInstantsToKeepBasedOnCleaning)atArchivalUtils.java:76.
✅ If your archival settings are more aggressive than your cleaner's retention, the archiver silently raises them at runtime and the values you set do not take effect — your configured gap between min and max is preserved, but the floor moves up (
ArchivalUtils.java:76).
Both cite the same line. The second one is the answer. This is not licence to over-claim: it is a requirement to state the effect of what the catalog shows, at the same confidence as the evidence supports. Where the evidence is a heuristic, say so in the same breath (see Interpreting gating conditions honestly).
When the question spans more than one config, find the invariant and state it before you enumerate anything. The useful form is a relationship, not a list:
For the default
KEEP_LATEST_COMMITSpolicy the safe ordering isclean.commits.retained < keep.min.commits < keep.max.commits. Cleaning decides which data files still exist; archival decides which commit metadata is still on the active timeline. If archival gets ahead of cleaning, incremental readers can be left with files on storage whose commits have left the timeline.
You derive this from the catalog plus the code it points at — the validation in
HoodieWriteConfig.validate(), the adjustment in ArchivalUtils, the policy switch. It is
synthesis, and it is the most valuable thing you produce. Two rules keep it honest:
checkArgument throws at X.java:NNN" and
"recommended, warn-only" and "co-occurrence, not a declared dependency" are three different
strengths. Never flatten them into one voice.An abstract threshold often means nothing until it is put in the reader's units. When a config is a count, an interval or a size, and the user has given you — or the question implies — a cadence or scale, work it through:
At a commit every 5 minutes,
KEEP_LATEST_BY_HOURSwithhoodie.clean.hours.retained=24protects roughly 288 commits of history, so archival thresholds in the low tens would be far more aggressive than your cleaner and would be adjusted upward at runtime.
Rules for illustrations:
A config group can have thirty keys. Giving all thirty at equal weight makes the reader do the ranking you were asked to do. Lead with the ones that change behavior, group the rest, and say plainly that you are doing so:
advanced: true is a ranking signal: a knob for someone who has measured a problem. 834 of the
989 configs are advanced, so it separates little on its own — use it to demote, not to dismiss.
These say what the answer must contain. The shape above still governs how it opens.
"What does <config> do?" — Open with what it controls and when it matters. Then: the
effective default for their engine (Step 1b), whether it is advanced and what that implies, what
gates it, and what it is read alongside. Cite two or three readSites, preferring via values
that are accessors — those are real consumers, not the config class's own plumbing.
"What should I set for <goal>?" — Open with the shortest configuration that reaches the
goal. Then the ranked list, each with one line on why it is relevant, and what must be set
together.
Do not let a substring search over documentation be your retrieval. The goal's vocabulary and
the config's wording often do not meet: searching "small file" finds
hoodie.parquet.small.file.limit but misses hoodie.parquet.max.file.size, which is the config
that actually sets the target size, while returning streamer and timeline keys that merely contain
the phrase. Use the search to find a foothold, then expand through configGroup and coConfigs
from the configs you know are relevant, and sanity-check the result against what the code path for
that goal actually reads.
Say which table type and engine the answer assumes. The catalog has no field for this — a config that only applies to MOR says so, if at all, inside its documentation prose. When a goal is table-type specific, find the code path (the partitioner, the action executor) and read what it branches on. Guard clauses are the usual mechanism and the catalog cannot see them: a helper that returns early for one index type will make the configs below that early return completely inert, with no gate recorded anywhere.
Prefer non-advanced configs; flag advanced ones and do not recommend changing them without a reason
tied to something the user has measured. Within a narrow goal-scoped set, advanced does real
ranking work even though most configs carry it.
"I set X and nothing happened." — The highest-value question. Open with the most likely cause in one sentence, then the evidence.
Before the ladder: if readContextResolved is false, open the Scala read sites. The ladder
below reasons over gatingConditions, coConfigs and read context, none of which are recovered
for Scala. Run it on an unresolved config and every step passes vacuously, landing you on step 7
("read sites exist, so it is live") — a confident wrong answer. For a hoodie.datasource.* key
that verdict is never earned from the catalog alone. Read the sites, then run the ladder.
Work this order and stop at the first hit:
Is the read gated? gatingConditions — a read under a switch on a strategy enum, or an
if on another config, is usually the answer.
Is it gated before the config is even consulted? shortCircuits lists wrapper methods
that can return a fixed value without ever reading the key — a table-version check, an engine
check, an unsupported operation type. hoodie.metadata.streaming.write.enabled is the clearest
case: HoodieWriteConfig.isMetadataStreamingWritesEnabled returns false outright below table
version eight and never consults the key, so on an older table the setting is inert and Hudi
reports nothing. This is invisible in both readSites and gatingConditions, because the read
itself is real and unconditional and the branch lives a frame above it.
Open the cited file:line before you use one. Roughly half of these entries are real gates
and the rest call the accessor incidentally; the shape is matched textually, the attribution is
not verified. Confirm the literal return really bypasses this config, then report it. An
unverified entry is a lead, not a finding.
Were they relying on the default? engineDefaults differing from the declared value
explains most "it was already on/off" reports. setDefaultValue applies only when the key is
unset; an explicit value always wins.
Is a different key winning? alternatives.
Is it deprecated? deprecatedAfter, against the catalog's hudiVersion.
Is it inferred? hasInferFunction: true — and the infer function may read a config they
also changed.
Are there no read sites? readSites: [] with a parseWarnings entry is a gap in the
catalog, not proof the config is dead. Say which it is.
Reaching the end of the ladder is not a finding that the config works. It means the catalog has no explanation, which is a reason to read the code, not to reassure the user. Say that plainly: "nothing in the catalog explains this — here is where I would look."
Close with the one thing to check: a file:line, or the companion config to set.
"What is the difference between X and Y?" — Open with the distinction in one sentence. Usually it is one of: which code path reads them, which strategy value they apply under, or which engine they take effect on. Then the side-by-side.
"What else must I set alongside X?" — Open with what breaks if they set X alone, then name the companions.
coConfigs is a lead list, never a ranking you can hand over. sharedMethods counts mentions
in the same method body, so a config read inside a large builder (HoodieWriteConfig.Builder.build,
setDefaults) accumulates dozens of unrelated neighbours, and the ordering puts noise first. Worse,
it has a systematic blind spot: a companion read through a typed accessor rather than by key —
getWriteConcurrencyMode() == SINGLE_WRITER — never appears at all, and that is exactly the kind of
config whose absence silently defeats the one the user set.
Check gatedBy first. It lists configs named inside this config's own gating conditions, which
is a stronger relationship than co-occurrence — the gate decides whether this config is read at
all — and it is the right place to look for the enum-compared companions coConfigs misses.
Then: open the shared method before you present a companion, and prefer a requirement you confirmed
in validation or in a builder's adjustment logic over anything sharedMethods ranked highly. State
plainly that co-occurrence is what the code reads together, not what Hudi requires.
Watch for configs whose only consumer is one code path that is no longer the default — a V1 implementation superseded by V2, an engine-specific client, a legacy format. A user setting such a key gets silence, not an error, because Hudi does not reject unknown or unused properties.
noExternalConsumer: true is the catalog's flag for it: every read site is in the config's own
declaring file, so nothing else was found reading it. A commented-out call site looks exactly like
this — hoodie.datasource.read.incr.skip_cluster is fully declared, documented and versioned, and
its one Spark call site is commented out with a reference to HUDI-9672. Never describe behaviour
for a config carrying this flag without first opening the code that should consume it.
readSites is how you catch the subtler case, and a short readSites array is the signal — two plumbing
entries and one real consumer tells you more than twenty ever will.
Naming the class is only half the job, and the half that does not help anyone. Finish it: find
the factory that chooses between the implementations and report the condition. When a read site
sits in a class suffixed V1, V2, Legacy or Deprecated, locate the selector — usually a
getInstance on a sibling *s class — and read what it branches on, then follow that to the
current value.
hoodie.archive.block.on.latest.clean.ectr is the worked example. Its only real consumer is
TimelineArchiverV1. TimelineArchivers.getInstance picks V1 only for timeline layout versions 0
and 1, and TimelineLayoutVersion.CURR_VERSION is VERSION_2. The sentence the operator needs is
not "read by TimelineArchiverV1" but "on any table at the current timeline layout, setting this
does nothing, and you get no warning" — two hops past where the catalog stops.
gatingConditions is a textual heuristic, not a verified analysis. The generator finds the
if/switch blocks enclosing a read and records their conditions as strings. Each entry carries
the class.method (file:line) it came from.
What it catches well:
switch (strategy) case NUM_COMMITS — a config that applies only under certain enum values.if (config.isSomethingEnabled()) — a read behind a feature flag.What it misses or gets wrong:
gates: [] with gatesResolved: true is not a clean bill of health. It means no enclosing
condition was found at that site, not that the read is unconditional. Treat it as "nothing found
here", never as "verified unconditional".deduceShuffleParallelism(input, configured) returns the configured value only
if (configured > 0), so the declared default 0 is a sentinel meaning "work it out yourself",
not a literal zero. The read site looks unconditional and gates: [] is reported with
gatesResolved: true. Whenever a numeric config's default is 0 or -1, assume a sentinel and
open the method it is passed to before describing the default.if (compactable) tells you
nothing on its own)."via": "scalaReference" carries gatesResolved: false, and the config carries
readContextResolved: false with a readContextNote saying how many of its sites are Scala.
For hoodie.datasource.* configs most or all read sites are Scala, so an empty
gatingConditions there means not analysed, not unconditional. Say which it is.So: present gates as evidence to check, with the file:line, never as a verdict. "This is read
inside switch (compactionTriggerStrategy) under NUM_COMMITS, NUM_COMMITS_AFTER_LAST_REQUEST,
NUM_OR_TIME and NUM_AND_TIME at ScheduleCompactionActionExecutor.java:208 — confirm there"
is right. "This config does nothing unless you set the strategy" is an over-claim unless the
config's own documentation says so too.
An empty gatingConditions means the generator found no enclosing condition. It does not mean the
config is unconditional.
engineDefaults, state the default for the user's engine, not the declared
value — and ask which engine they are on if you do not know. See Step 1b.file:line so the user can verify. You are pointing at evidence, not asserting
authority.parseWarnings on an entry means the generator could not fully read that declaration. Check it
before answering, and pass the caveat on.defaultValueIsLiteral: false) is shown as its source expression, for
example String.valueOf(104857600) or String.valueOf(120 * 1024 * 1024). Always evaluate
arithmetic on literals and give both forms — the user is going to paste a value into a config
file, and a source expression is not settable. Say it is computed at class-init time. If the
expression names a constant you cannot resolve, say that rather than guessing.advanced config without a stated reason tied to something the user
has measured.checkArgument that throws), recommended (warn-only),
and co-occurrence (read in the same method) are three different things. Never flatten them.The catalog is a build artifact checked in beside this skill. When the Hudi source moves:
python3 scripts/generate_config_catalog.py
It rewrites config-catalog.json and config-catalog-summary.md in place, with deterministic
ordering so the diff is readable. It is stdlib-only and takes well under a minute.
まだレビューはありません。使ってみた感想をお寄せください。
ログインなしで書き込めます。使ってみた感想、質問、動かなかった環境などを気軽にどうぞ。
まだコメントはありません。最初のコメントを書いてみませんか。
Apache Hudiのテーブル設計を用途やデータ更新の条件から検討し、設計判断と理由を文書化します。対応する構成では設定や実行例もまとめます。
Apache Hudiのメタデータテーブルを読み取り専用で点検し、同期のずれやファイル一覧の不整合、圧縮処理の遅れ、索引の容量不足と対処を整理します。
Apache Hudiの書き込み設定、Sparkイベントログ、テーブルの状態を照合し、遅さや容量の課題に対する改善候補とリスク、確認方法を提案します。
Apache Hudiのジョブ失敗をログや設定から調べ、根本の例外と既知の障害パターンを照合します。根拠の確かさを示し、対処の優先順を整理します。
Apache Hudiのデータ整理や履歴保管が取り込みに追いついているかを、専用ツールの診断結果から確認し、問題の根拠と次の対処を示すスキル。
Apache Hudiのテーブル容量やファイルサイズ、パーティションの偏りを読み取り専用ツールで調べ、小さいファイルの蓄積や書き込み集中を診断します。