First connector
This walk-through starts a node and the connectors runtime together, watches a source fan records out into topics that did not exist a moment earlier, and reads them back through a sink. It needs Docker and about ten minutes. Nothing is installed on the host.
Start the stack
The harness/aio directory has the compose files for a self-contained node. compose.connectors.yml is an overlay that adds the runtime next to it, so it stacks on whichever base file you prefer. The lite one is enough here.
bash
git clone https://github.com/picomq/picomq && cd picomq/harness/aio
docker compose -f compose.lite.yml -f compose.connectors.yml up --buildThe first build compiles the runtime and its plugins, which takes a while. Once both containers are up the runtime loads two connector definitions from harness/aio/connectors, and the stdout sink starts printing batches within a couple of seconds.
text
connectors-1 | Loading connector configuration from: /etc/picomq-connectors/connectors/random-source.toml
connectors-1 | Loading connector configuration from: /etc/picomq-connectors/connectors/stdout-sink.toml
connectors-1 | Resolved plugin path: /usr/local/lib/libpicomq_connector_random_source.so (found in /usr/local/lib)
connectors-1 | Resolved plugin path: /usr/local/lib/libpicomq_connector_stdout_sink.so (found in /usr/local/lib)
connectors-1 | Stdout sink with ID: 1 received: 7 messages, schema: json, topic: user-2, partition: 0, offset: 0, invocation: 1
connectors-1 | Stdout sink with ID: 1 received: 5 messages, schema: json, topic: user-0, partition: 0, offset: 0, invocation: 2The source
The source is the random plugin. It generates JSON records with a sequence number and a user_id drawn from a small pool.
toml
type = "source"
key = "random"
enabled = true
version = 0
name = "Random source"
path = "libpicomq_connector_random_source"
[[topics]]
topic = { strategy = "field", path = "user_id", template = "{value}" }
schema = "json"
batch_length = 100
linger_time = "5ms"
create_topics = true
[plugin_config]
interval = "1s"
messages_range = [5, 20]
payload_size = 64
user_pool = 4The interesting line is topic. Instead of a name it carries a rule, and each second the runtime does the following with the batch the plugin hands over.
- Reads
user_idfrom each record and uses it as the topic name. - Creates
user-0throughuser-3the first time each is seen, becausecreate_topicsis on. - Produces every record and waits for the broker to acknowledge all of them.
- Tells the plugin the batch is done, so it can advance its sequence counter.
From the node's side these are ordinary streams, created by a Kafka client. They show up in the dashboard and over HTTP like any other.
The sink
The sink is the stdout plugin. It does not list topics. It subscribes to a pattern, and the runtime re-checks the pattern against the broker every couple of seconds, so the four topics the source created were picked up without anyone naming them.
toml
type = "sink"
key = "stdout"
enabled = true
version = 0
name = "Stdout sink"
path = "libpicomq_connector_stdout_sink"
[[topics]]
pattern = "user-.*"
schema = "json"
batch_length = 100
poll_interval = "100ms"
[plugin_config]
print_payload = trueA source that names topics from data and a sink that follows a pattern is the shape most PicoMQ connector deployments take. Routing and templating covers the other strategies.
Look around
The runtime serves an HTTP API on port 8081, and the node sees the topics as streams.
bash
curl -s localhost:8081/sinks | jq # running sinks and their status
curl -s localhost:8081/sources/random | jq # one source in detail
curl -s localhost:8081/stats | jq # throughput counters
curl -s localhost:4437/user-1 | head # the topic, read as a stream over HTTPBreak it
Kill the runtime container and start it again.
bash
docker compose -f compose.lite.yml -f compose.connectors.yml kill connectors
docker compose -f compose.lite.yml -f compose.connectors.yml up connectorsTwo things happen on the way back up.
- The source resumes its sequence numbers where it left off. Its state, the count of messages produced, was checkpointed to the
connectors-statevolume after every acknowledged batch. - The sink resumes at the offset it last committed. The log shows no gap in the sequence numbers it prints.
Kill it during a batch instead, with kill -9 on the process rather than a stop, and the sink may print a handful of sequence numbers twice. That is the at-least-once guarantee doing what it says. Delivery guarantees explains exactly which window is exposed and how each sink absorbs it.
Your own connector
Drop another file into harness/aio/connectors and restart the runtime. This one lands every user-* topic in its own Postgres table, given a Postgres you have to hand. The Postgres sink page has every option.
toml
type = "sink"
key = "users_pg"
enabled = true
version = 0
name = "Users to Postgres"
path = "libpicomq_connector_postgres_sink"
[[topics]]
pattern = "user-.*"
schema = "json"
batch_length = 500
[plugin_config]
connection_string = "postgres://user:pass@host:5432/app"
target_table = "events_{topic}"
auto_create_table = trueFrom here, Sources and Sinks explain what the runtime did on each side, and Operations covers running it outside the harness.