本文へ移動
cccskills
無料GitHub で公開日本語紹介

connector-source

データベース、API、キューなど外部システムをポーリングし、Apache Iggyのストリームへメッセージを送るSourceプラグインの作成・変更を支援します。

原文原文の説明を見る

Author a new Apache Iggy connector source plugin under core/connectors/sources/. Sources poll an external system (DB, API, queue) and produce messages into Apache Iggy streams. Load when creating, modifying, or reviewing a source crate. Use for source plugin authoring. NOT for runtime internals (see connector-runtime).

apache/iggyスター 5,223(GitHub 5,223 + cccskills 0)

実行スコア 未検証

インストール方法を見る

こんなときに便利

  • DBのデータを取り込むSourceを作りたいとき
  • APIの情報を定期取得したいとき
  • カーソルとACK・NACK処理を確認したいとき

日本語での紹介

できること

データベース、API、キューなどの外部システムをポーリングし、Apache Iggyのストリームへメッセージを送るSourceプラグインの作成・変更・レビューを支援します。ポーリング間隔、スキーマ選択、メッセージIDや時刻の扱いに加え、カーソルなどの状態をバッチ送信の結果に合わせて確定する方法を確認できます。ACK(送信と状態保存の完了通知)とNACK(完了を確認できなかった通知)の処理、再配信、外部データの削除や更新を行うタイミングも扱います。

こんなときに便利

外部のデータソースからApache Iggyへデータを取り込むプラグインを作るときに向いています。再起動後の再開位置を保ちたい場合や、送信失敗時にデータを失わないよう状態管理を見直したい場合にも役立ちます。

使い方の例

  • 「PostgreSQLをポーリングするSourceプラグインの設計を手伝ってください」
  • 「このSourceのカーソルをACK後に確定する方法を確認してください」
  • 「NACK時に同じレコードを再取得できるテストを整理してください」

注意点

実装対象はRustのcdylibプラグインです。外部インフラを使うSourceには統合テストの手順が示されています。Sourceトレイト自体の変更やランタイム内部の変更は対象外です。

この紹介文は、公開されている SKILL.md をもとに AI(OpenAI Codex)が作成しました。正確な仕様は下の原文を確認してください。

含まれるファイル(2)

  • SKILL.md11.9 KB
  • TEMPLATE.md5.7 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Writing an Apache Iggy Connector Source

A source is a Rust cdylib that implements iggy_connector_sdk::Source and exposes FFI symbols via the source_connector! macro. The runtime calls poll() in a loop, applies transforms, encodes via the configured Schema, sends to Apache Iggy, and persists the returned ConnectorState after every successful send.

Universal connector rules (SecretString, benchmark, verbose flag, drop accounting, filter contract, exemplar patterns) live in connectors-overview. This skill covers only what's source-specific.

Contents

STOP and ask the user before

  • Changing the SDK trait surface (Source::open / poll / on_batch_result / close) - that's an SDK change.
  • Adding a long-running side task in the plugin - the runtime owns lifecycle, and orphans survive close(). Sanctioned only where the source is itself a server the runtime cannot drive, as in http_source's listener, and then only with an explicit shutdown in the last close() that awaits its tasks before returning.
  • Persisting unbounded state - State is rewritten every batch.
  • Adding a source that requires authoritative offsets external to Apache Iggy without coordinating retention.

Quick reference

  • Skeleton: TEMPLATE.md (load on demand).
  • Exemplars: random_source (minimal + canonical state tests), postgres_source (cursor / delete-after-read / processed-column modes, restart-survives-state tests), elasticsearch_source (scroll cursor), influxdb_source (time-series scan).

Hard rules

poll() signature is &self

The macro shares the source as Arc<T> across the FFI callback and forwarding loop. Signature: async fn poll(&self) -> ... - any mutable state behind tokio::sync::Mutex. Single most common new-contributor mistake.

Lock discipline

Never hold the state Mutex across upstream I/O. Build a candidate from committed state, then stage it until the runtime reports the batch result:

let mut candidate = self.state.lock().await.clone();
let rows = client.query(&sql, &[&candidate.cursor]).await?;
candidate.cursor = Some(new_cursor);
let persisted = ConnectorState::serialize(&candidate, CONNECTOR_NAME, self.id)
    .ok_or_else(|| Error::Serialization("failed to serialize source state".into()))?;
*self.pending.lock().await = Some(candidate);

Delivery acknowledgment

on_batch_result (added by #3855) is how a source learns what happened to the batch it just returned. The SDK keeps exactly one batch in flight: it will not call poll() again until this returns. By default it stops after MAX_CONSECUTIVE_NACKS (5) consecutive NACKs; a source can override batch_policy() to disable the limit when its accepted input cannot be replayed after restart. A stop is reported to the runtime only when the plugin exports the current SDK's stop callback, so rebuild older source plugins.

  • Ack means the runtime sent the batch and persisted its state. Nack means it could not confirm both, which is not the same as neither happening: a batch that reached the topic but whose state save failed is NACKed. A missing runtime result leaves the batch pending; it does not produce a synthetic NACK. A source that replays on Nack is at-least-once, not exactly-once.
  • The trait has a default no-op, which suits only a source with no staged cursor and no destructive work. If poll() advances a cursor, deletes rows, or drains an in-memory buffer, omitting this loses data silently and nothing will tell you. random_source and http_source implement it; the other shipped sources take the default and skip rows on a NACK.
  • Stage in poll(), apply on Ack, discard or replay on Nack. A source whose input is pushed to it, rather than re-readable upstream, has to hold the batch itself: see http_source's staging.
  • Returning Err from it stops the source immediately, so it is not a retry signal.

State persistence

  • ConnectorState is Vec<u8> via MessagePack (rmp_serde). Use ConnectorState::serialize(&state, NAME, id) and ConnectorState::deserialize::<State>(NAME, id).
  • poll() must not commit cursors or destructive work. Return messages with candidate state and keep the corresponding work staged.
  • The runtime sends the batch, saves its candidate state to {state_path}/source_{key}.state, then calls on_batch_result(Ack). Commit staged in-memory state and external delete or mark operations only on ACK. A NACK discards the candidate so the same data can be polled again.
  • A crash between poll() returning and state persistence leaves the prior cursor for the next poll, so downstream must tolerate at-least-once delivery.
  • Cursor sources that stage state until on_batch_result can attach state to the corresponding message batch. Control-plane state that is independent of a publish can ride an empty batch instead.
  • Return state: None for an empty poll when no watermark changed. If an empty poll advances a watermark, stage and return the new state through the same ACK handshake.
  • The runtime can still NACK an empty batch when state storage is latched or a pending checkpoint cannot resolve. Never treat hand-off as durable; on_batch_result reports the outcome.
  • Treat candidate-state serialization failure as a poll error. Do not send messages without the state needed to resume them safely.
  • Keep State small - rewritten every batch. No unbounded vecs.

The SDK allows one in-flight batch. Five consecutive NACKs stop a source using the default policy and require a manual restart. Returning Err from on_batch_result is fatal regardless of the NACK limit, so retry transient backend failures inside the callback before returning an error. If the runtime has not reported ACK or NACK after 30 seconds, the SDK warns and keeps waiting for that result without polling or replaying the batch. Once the result is received, the SDK waits for on_batch_result to finish, so the callback must bound its own connection acquisition and retry budget.

Sleep first

poll() must sleep(self.poll_interval).await before any work. Without it, an empty source spins a CPU.

Schema selection

Match ProducedMessages.schema to the bytes in messages[i].payload:

  • JSON-serialized rows → Schema::Json
  • Already-protobuf bytes → Schema::Proto
  • Already-avro bytes → Schema::Avro
  • Opaque → Schema::Raw

IDs and timestamps

  • ProducedMessage.id: Option<u128> - set when a natural ID exists (DB PK, document id). It rides the wire as the message id, which is what a consumer can dedupe an at-least-once duplicate on.
  • Iggy itself does not dedupe on that id. The server's only read of the field is core/server/src/http/wire.rs, which mints a fresh uuid when the incoming id is 0 and otherwise passes it through. Its dedup path is a per-client request-id watermark (dedup_clients_max) that never looks at this field.
  • origin_timestamp: Option<u64> - source-system event time in nanoseconds. Lets downstream sinks reason about lag.
  • timestamp and checksum are Iggy-side - leave None.

Concurrency

  • Runtime spawns ONE poll() task per source. No concurrent poll().
  • Don't spawn your own long-running Tokio tasks: the runtime owns lifecycle. The exception is a source that listens rather than polls, which has to own its listener; http_source is the worked example, and it shuts its tasks down in the last close() rather than leaving them to outlive the connector.

Errors

ScenarioVariant
Bad config in new()/open()Error::InitError
Cannot reach external system at startupError::InitError or Error::Connection
Transient fetch failure (retry-worthy)Error::Connection or Error::HttpRequestFailed
Permanent fetch failure (auth, schema gone)Error::PermanentHttpError
Row failed to serializeError::Serialization(...)
State serialization failedError::Serialization(...)

Returning Err from poll() is only logged by the SDK's FFI bridge (sdk/src/source.rs::handle_messages) - the loop continues, the next poll() runs. Connector status does NOT flip to Error from a poll failure. Status Error is set by the runtime only on transform/encode failure, Iggy send failure, or state save failure (runtime/src/source.rs::source_forwarding_loop calls to context.sources.set_error). To surface a poll failure as unhealth, raise it through the metric counter or escalate to Error::InitError from open().

Logging

info!("Opened <connector> connector ID: {}, endpoint: {}", self.id, ...);
info!("Restored state for <connector> ID: {id}, cursor: {:?}", ...);
debug!("Polled {} rows for <connector> ID: {}", rows.len(), self.id);
warn!("Transient fetch failure for <connector> ID: {}, will retry: {error}", self.id);
error!("Failed to <op> for <connector> ID: {}, error: {error}", self.id);
info!("Closed <connector> connector ID: {}, total produced: {}", self.id, ...);

Iggy consumer-loop labels use literal API names (offset=, current_offset=).

Common pitfalls

  1. async fn poll(&mut self) - won't compile. Use &self + Mutex<State>.
  2. Holding state.lock() across the fetch I/O - blocks close(), causes shutdown timeouts.
  3. Forgetting to sleep - 100% CPU on idle source.
  4. Committing a cursor or deleting source data in poll() - stage it and wait for ACK.
  5. Unbounded data in State - rewritten every batch. keep O(constant).
  6. std::sync::Mutex - blocks the executor. Use tokio::sync::Mutex.
  7. Not setting ProducedMessage.id when a stable ID exists - leaves a consumer nothing to dedupe a replayed duplicate on. It does not make the write idempotent server-side, because nothing there reads it.
  8. Spawning side tasks - the runtime owns the scheduler. The one exception is a source that listens rather than polls and must own its listener (see Concurrency and the STOP list); it owes an explicit shutdown in the last close() that awaits its tasks.

Tests

Mandatory four canonical source state tests (see connector-testing for the full pattern). Copy from sources/random_source/src/lib.rs::tests. Plus config defaults, payload building, schema selection.

Integration tests under core/integration/tests/connectors/<backend>/ for any source backed by external infra. Use #[iggy_harness] + a TestFixture backed by testcontainers-modules. Reference: core/integration/tests/connectors/postgres/postgres_source.rs (multi-mode tests) + restart.rs (state survives restart). Exercise both ACK and NACK paths when the source stages cursors or destructive work.

Before declaring done

cargo fmt --all
cargo sort --no-format --workspace
cargo clippy -p iggy_connector_<name>_source --all-targets -- -D warnings
cargo test -p iggy_connector_<name>_source

# Integration tests:
cargo test -p integration -- connectors::<backend>::<test_name>

Update core/connectors/sources/README.md and add a sample TOML under core/connectors/runtime/example_config/connectors/.


Discussion / help: see AGENTS.md.

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

コメント

ログインなしで書き込めます。使ってみた感想、質問、動かなかった環境などを気軽にどうぞ。

まだコメントはありません。最初のコメントを書いてみませんか。

送信するとすぐに公開されます。メールアドレスや電話番号などの個人情報は書かないでください。URL は 2 つまでです。

0 / 1000 文字

このフォームは reCAPTCHA で保護されており、Google のプライバシーポリシーと利用規約が適用されます。

同じリポジトリのスキル

connector-review

無料概要あり

Apache Iggyのコネクター変更を複数の専門視点から厳しくレビューし、指摘ごとに独立した検証を行って、修正が必要な問題を整理します。

  • コネクターのPRをマージ前に確認したいとき
  • ブランチの変更を専門視点でレビューしたいとき
  • 指摘の妥当性を独立して検証したいとき
apache/iggy5,2232026年10月29日 更新

connector-runtime

無料概要あり

Apache Iggyとコネクタープラグインをつなぐランタイムの開発に使います。読み込みやデータ転送、状態保存、設定、HTTP API、メトリクスなどの変更箇所を把握できます。

  • プラグインの読み込みや管理を変更したいとき
  • ソース・シンクの転送処理を調べたいとき
  • 状態保存や設定取得を修正したいとき
apache/iggy5,2232026年10月29日 更新

connector-sdk

無料概要あり

Apache Iggy Connectors SDKのスキーマ、エンコーダー、デコーダー、エラー型やFFIなどを追加・変更するときに、関連箇所と互換性への影響を整理するスキルです。

  • SDKにスキーマ形式を追加したいとき
  • ストリーム変換器を実装したいとき
  • 公開APIやFFIの変更を検討するとき
apache/iggy5,2232026年10月29日 更新

connector-sink

無料概要あり

Apache Iggyのストリームからメッセージを受け取り、データベースやHTTPなど外部システムへ送るSinkプラグインの作成・変更を支援します。

  • DBへ書き込むSinkを作りたいとき
  • HTTP連携用のSinkを追加したいとき
  • リトライやバッチ処理を見直したいとき
apache/iggy5,2232026年10月29日 更新

connector-testing

無料概要あり

Apache Iggyのコネクター向けに、ユニットテストや実サービスを使う統合テストを設計・作成し、命名規則や状態復元などのテスト慣例に沿って確認します。

  • コネクターのユニットテストを追加したいとき
  • ソースの状態復元を検証したいとき
  • 実サービスを使う統合テストを書くとき
apache/iggy5,2232026年10月29日 更新

connector-transform

無料概要あり

Apache Iggyコネクターのメッセージにフィールド追加・削除や形式変換などの処理を加える変換機能を設計し、設定連携やテストまで整えます。

  • メッセージにフィールドを追加したいとき
  • 条件に応じてメッセージを除外したいとき
  • データ形式を変換したいとき
apache/iggy5,2232026年10月29日 更新

apache のスキルをすべて見る

このスキルの問題を報告する