Skip to main content

flink-local-dev

Run and validate an Apache Flink SQL job on the current machine, in an in-process MiniCluster driven by the Flink SQL Client, before promoting it to production. Use for local Flink SQL development, debugging a SQL script, bounded replay of development Kafka/JDBC/CDC sources, shadowing every sink with print/blackhole/local filesystem, EXPLAIN plan checks, output assertions, and the parity checklist that precedes handing the job to `flink-k8s-operator`. A local run never writes to a production sink, consumer group, or checkpoint path.

Quellinformationen

Repository
Datus-ai/Datus-Plugins
Letzte Quellaktivität
20. August 2026 um 09:04
Erkannte Sprache von SKILL.md
Englisch
Sterne
0
Forks
1

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
flink-local-dev
description
Run and validate an Apache Flink SQL job on the current machine, in an in-process MiniCluster driven by the Flink SQL Client, before promoting it to production. Use for local Flink SQL development, debugging a SQL script, bounded replay of development Kafka/JDBC/CDC sources, shadowing every sink with print/blackhole/local filesystem, EXPLAIN plan checks, output assertions, and the parity checklist that precedes handing the job to `flink-k8s-operator`. A local run never writes to a production sink, consumer group, or checkpoint path.
# Flink local development Validate Flink SQL logic where the code lives: one JVM, one MiniCluster, no Kubernetes, no Docker, no shared cluster. The job script that passes here is the same file that ships to production; everything local lives in a separate overlay. Copy the templates below into the project, replace every `__PLACEHOLDER__`, and delete the variants you do not use. They are starting points, not defaults. ## 1. Establish the target Collect or derive these before changing files: - the SQL script under validation, and which statements are the job (a single `INSERT INTO` or an `EXECUTE STATEMENT SET`) - the production Flink version this job will run on (`spec.flinkVersion` such as `v1_20` -> Flink `1.20.x`) - every source table and, for each, whether the local run uses a development endpoint or a generated stand-in - every sink table — each one needs a local shadow - the connector jars the script needs, and their versions - expected output: row count, sample rows, changelog kinds, or an invariant the result must satisfy Use this layout, next to the Operator manifests the `flink-k8s-operator` skill generates: ``` deploy/flink/<name>/ ├── sql/job.sql # the production artifact — identical locally └── local/ ├── local-session.sql # local session settings ├── local-sources.sql # bounded/dev source shadows (optional) ├── local-sinks.sql # sink shadows (required when the job writes) └── run-local-sql.sh # preflight + guards + SQL Client invocation ``` Inspect existing files first and never overwrite them silently. Ask which statements form the job when the script mixes DDL, DML, and ad-hoc queries. ## 2. Preflight the local runtime (fail closed) A local run needs a Flink distribution on this machine — `apache-flink` in a virtualenv is not one: ```bash echo "${FLINK_HOME:?set FLINK_HOME to a local Flink distribution}" "$FLINK_HOME/bin/flink" --version # -> Version: 1.20.1, Commit ID: ... java -version ``` Two invariants: - The distribution's minor version matches the production `flinkVersion`. A different minor version changes planner behaviour and connector compatibility, which defeats the point of validating locally. - The JDK is one that release supports. Flink 1.x releases target Java 8/11/17 and Flink 2.x dropped Java 8; check the release's own compatibility note rather than assuming. An unsupported JDK fails with `InaccessibleObjectException` or a module-access error, not a version message — point `JAVA_HOME` at a supported JDK instead of adding `--add-opens` flags. Then prove the in-process path works, before any real endpoint is involved: ```bash "$FLINK_HOME/bin/sql-client.sh" -f local/minicluster-smoke.sql ``` ### minicluster-smoke.sql ```sql -- Proves the in-process MiniCluster path works. Expect five +I[...] rows on -- stdout and exit code 0. Reads nothing, writes nothing, needs no connector jar. SET 'execution.target' = 'local'; SET 'table.dml-sync' = 'true'; SET 'parallelism.default' = '1'; SET 'sql-client.execution.result-mode' = 'tableau'; CREATE TEMPORARY TABLE local_smoke_source ( id BIGINT, label STRING ) WITH ( -- A sequence field bounds the table: it ends when the sequence completes. 'connector' = 'datagen', 'fields.id.kind' = 'sequence', 'fields.id.start' = '1', 'fields.id.end' = '5', 'fields.label.length' = '4' ); CREATE TEMPORARY TABLE local_smoke_sink ( id BIGINT, label STRING ) WITH ( 'connector' = 'print', 'print-identifier' = 'local_smoke_sink' ); INSERT INTO local_smoke_sink SELECT id, label FROM local_smoke_source; ``` `SET 'execution.target' = 'local'` selects Flink's `LocalExecutor`, which builds the JobGraph and submits it to a MiniCluster inside the client JVM: no JobManager or TaskManager daemon, nothing surviving the process, no `flink-conf.yaml` cluster address. The SQL Client documentation only describes the `start-cluster.sh` + remote-target workflow, so treat the local target as verified-by-probe — run the smoke script once per distribution before trusting it. Stop with an actionable message when `FLINK_HOME` is unset, the version does not match production, the JDK is unsupported, a connector factory is missing, or the smoke run fails. If a release rejects the local target, **do not** let the run fall through to the default target — with a real development source configured, that submits the job to whatever cluster `flink-conf.yaml` points at. Fix the version, or ask the user before starting a local standalone cluster (`"$FLINK_HOME/bin/start-cluster.sh"`, remote target, UI on `localhost:8081`) and stop it afterwards. ## 3. Bound the blast radius A local MiniCluster is isolated in compute only — its connectors reach exactly as far as the credentials you give them. Non-negotiable: 1. Sources may be real development endpoints; sinks may not. 2. Read-only credentials, and an identity of its own — never a production consumer group, CDC server id, replication slot, or service account. 3. Bounded reads by default, so a validation run cannot sit on a shared cluster for hours. 4. Local state and output stay under a local scratch directory. 5. Credentials live only in a git-ignored file. Ask the user to name each endpoint and confirm it is development or staging. Never infer it from the hostname, and never reuse an endpoint found in a production manifest. When the only available endpoint is production, stop and say so: generate the data instead (`datagen`, a filesystem fixture, or `VALUES`). Reuse the endpoints and accounts the project already has; do not invent hostnames or create credentials. **Kafka.** Set a dedicated `properties.group.id` (`local-dev-<user>-<job>`) — when the option is omitted the connector generates `KafkaSource-<tableIdentifier>`, which collides across runs. Leave checkpointing off: offsets are committed back to Kafka on checkpoint, and that is the one way a read-only source run can still change shared state. `earliest-offset` on a large topic reads the whole retention window; prefer a timestamp slice. Never declare a Kafka sink locally. **JDBC.** Use an account with `SELECT` only, and verify it — an account that can write is a failed guardrail even if this run happens not to write. Keep the scan small (a `LIMIT`ed query, a partitioned scan, a narrow predicate); a full-table scan on a shared development database is a visible load event. Lookup joins issue per-row queries: set a cache or bound the driving side first. **CDC.** The highest-risk local source, because it registers as a replication client on a shared database. MySQL `server-id` must be unique across every client, and a parallel incremental snapshot needs a range (`'server-id' = '<start>-<end>'`) wide enough for the reader count — a collision with production breaks the production job. Postgres `slot.name` must be unique, and a leftover slot retains WAL indefinitely and can fill the primary's disk, so drop it after the run. Prefer `scan.startup.mode = 'latest-offset'`: the default snapshot reads whole tables. A CDC source never ends — rely on the timeout and treat the run as a sampled observation. **Sinks.** Every `INSERT INTO`/`INSERT OVERWRITE` target gets a `CREATE TEMPORARY TABLE` shadow whose column list matches the production DDL exactly. Allowed local connectors: | Connector | Use it for | Note | |---|---|---| | `print` | inspecting rows and changelog kinds | rows land on client stdout | | `blackhole` | plan, throughput, and state checks | produces no output | | `filesystem` on a `file://` path | keeping output to diff between runs | commits part files only on checkpoint | A temporary table shadows the permanent or script-declared table of the same name and makes it inaccessible for the session, so the job script needs no edit. If the user insists on a real sink, that is no longer a local validation run: require explicit approval, a dedicated scratch topic/table (`local_dev_` prefix), and non-production credentials — then say plainly in the report that a real system was written to. **State paths.** Local checkpoint, savepoint, and output directories live under a scratch path such as `/tmp/datus-flink-local/<name>/`. Never point them at production state: a local job writing there can corrupt what a production job depends on. Validating against production state is copy-then-restore — copy the savepoint locally, restore from the copy. **Credentials.** Flink SQL DDL does not expand environment variables, so a development password in a `WITH` clause is a literal. Keep every overlay file that carries one out of version control (`deploy/flink/*/local/*.local.sql` in `.gitignore`); never write a credential into `sql/job.sql` — production reads secrets from Kubernetes Secrets; never echo an overlay file into the transcript, a log, or a report. Stop and ask instead of guessing when an endpoint's environment is unconfirmed, a sink has no local shadow, the credential offered can write, a CDC `server-id`/`slot.name` cannot be confirmed free, an unbounded source would run without a timeout, or a checkpoint path is not local. ## 4. Assemble the local overlay Three files, loaded in this order through the SQL Client's `-i` option. `-i` takes a single file, so the runner concatenates them; build the same file by hand (`cat local/local-session.sql local/local-sources.sql local/local-sinks.sql > /tmp/local-init.sql`) when invoking the client directly. An init script accepts `SET`, `RESET`, DDL, `USE`, and `LOAD/UNLOAD MODULE` — not queries or inserts. Leave `sql/job.sql` untouched. If a local run only works after editing the job script, that edit is either a real fix (keep it, it belongs in production) or a local workaround (move it to the overlay). ### local-session.sql ```sql -- Local validation session settings for __JOB_NAME__. -- Local only: never copy these into sql/job.sql or spec.flinkConfiguration. -- Required. Runs the job in a MiniCluster inside this client JVM instead of -- submitting it to whatever cluster flink-conf.yaml points at. SET 'execution.target' = 'local'; -- Required. Waits for the INSERT job to finish so the script's exit code -- reflects the job result instead of just the submission. SET 'table.dml-sync' = 'true'; -- Must match production. SET 'execution.runtime-mode' = '__RUNTIME_MODE__'; SET 'table.local-time-zone' = '__TIME_ZONE__'; -- Start at 1 to keep output readable; re-run at the production value once the -- logic is right, to expose ordering and keyed-state assumptions. SET 'parallelism.default' = '__PARALLELISM__'; SET 'sql-client.execution.result-mode' = 'tableau'; -- Off while validating logic: checkpointing adds latency and is what would -- commit Kafka offsets back to a shared consumer group. Enable it only to test -- a filesystem sink, state size, or restore behaviour — always to a local path. -- SET 'execution.checkpointing.interval' = '10s'; -- SET 'state.checkpoints.dir' = 'file://__LOCAL_STATE_DIR__/checkpoints'; -- Flink 2.x renamed the option above to execution.checkpointing.dir. -- Uncomment when a windowed or interval query emits nothing because one -- partition is idle and the watermark never advances. -- SET 'table.exec.source.idle-timeout' = '10s'; ``` ### local-sources.sql ```sql -- Optional local source overlay for __JOB_NAME__. The column list, types, and -- watermark must match the production DDL exactly. Read development endpoints -- only, read-only, bounded. Keep this file git-ignored when it carries a -- credential. -- Bounded slice of a development Kafka topic. CREATE TEMPORARY TABLE __SOURCE_TABLE__ ( __SOURCE_COLUMNS__ ) WITH ( 'connector' = 'kafka', 'topic' = '__DEV_TOPIC__', 'properties.bootstrap.servers' = '__DEV_BOOTSTRAP_SERVERS__', -- Dedicated group: never a production consumer group. 'properties.group.id' = 'local-dev-__LOCAL_RUN_ID__', -- Recent slice, so the run reads real data and still terminates. 'scan.startup.mode' = 'timestamp', 'scan.startup.timestamp-millis' = '__START_TIMESTAMP_MILLIS__', 'scan.bounded.mode' = 'latest-offset', 'format' = 'json', 'json.ignore-parse-errors' = 'false' ); -- Variant: generated data, when no development endpoint may be read. -- CREATE TEMPORARY TABLE __SOURCE_TABLE__ ( -- __SOURCE_COLUMNS__ -- ) WITH ( -- 'connector' = 'datagen', -- -- A sequence field bounds the table; 'number-of-rows' bounds a random one. -- 'fields.__SOURCE_KEY_COLUMN__.kind' = 'sequence', -- 'fields.__SOURCE_KEY_COLUMN__.start' = '1', -- 'fields.__SOURCE_KEY_COLUMN__.end' = '20' -- ); -- Variant: fixture files checked in next to the job, fully reproducible. -- CREATE TEMPORARY TABLE __SOURCE_TABLE__ ( -- __SOURCE_COLUMNS__ -- ) WITH ( -- 'connector' = 'filesystem', -- 'path' = 'file://__LOCAL_FIXTURE_DIR__/__SOURCE_TABLE__', -- 'format' = 'json' -- ); -- Variant: read-only development database. Keep the scan small, use an account -- that cannot write, and .gitignore this file before adding a password. -- CREATE TEMPORARY TABLE __SOURCE_TABLE__ ( -- __SOURCE_COLUMNS__ -- ) WITH ( -- 'connector' = 'jdbc', -- 'url' = 'jdbc:mysql://__DEV_DB_HOST__:3306/__DEV_DB__', -- 'table-name' = '__DEV_TABLE__', -- 'username' = '__DEV_READONLY_USER__', -- 'password' = '__DEV_READONLY_PASSWORD__' -- ); ``` ### local-sinks.sql ```sql -- Required local sink overlay for __JOB_NAME__. Every INSERT INTO / -- INSERT OVERWRITE target in the job script needs a shadow here, with the -- production column list. Only print, blackhole, and filesystem on a file:// -- path are allowed: a local run never writes to a real topic, table, or object -- store. CREATE TEMPORARY TABLE __SINK_TABLE__ ( __SINK_COLUMNS__ ) WITH ( 'connector' = 'print', 'print-identifier' = '__SINK_TABLE__' ); -- Variant: keep the output as files to diff between runs. The filesystem sink -- commits part files only on checkpoint, so enable -- execution.checkpointing.interval in local-session.sql when using this. -- CREATE TEMPORARY TABLE __SINK_TABLE__ ( -- __SINK_COLUMNS__ -- ) WITH ( -- 'connector' = 'filesystem', -- 'path' = 'file://__LOCAL_OUTPUT_DIR__/__SINK_TABLE__', -- 'format' = 'json' -- ); -- Variant: discard the rows, for plan, state, and throughput checks only. -- CREATE TEMPORARY TABLE __SINK_TABLE__ ( -- __SINK_COLUMNS__ -- ) WITH ( -- 'connector' = 'blackhole' -- ); ``` ## 5. Run it through the guarded runner Write this script into `local/run-local-sql.sh` and run the job through it. It fails closed: no unpinned session, no unshadowed sink, no non-local sink, no tracked credential. Never bypass a guard by calling `sql-client.sh` directly with a real endpoint configured. ### run-local-sql.sh ```bash #!/usr/bin/env bash # # Run a Flink SQL job in an in-process MiniCluster for local validation. # Exit codes: 0 job ok · 2 usage · 3 environment · 4 guard · 5 timeout · # anything else is the SQL Client's own exit code. set -euo pipefail JOBS=(); JARS=(); SESSION=""; SOURCES=""; SINKS=""; TIMEOUT_SECONDS=600; EXPECT="" USAGE="usage: run-local-sql.sh --job <file.sql>... --session <local-session.sql>
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen