Configuring domain sync for real-time spatial feeds

When synchronizing real-time spatial feeds across a federated mesh, the primary failure vector is schema drift in vector and tile payloads during cross-domain propagation. Silent coordinate-system mismatches (EPSG:4326 vs EPSG:3857) and topology degradation bypass standard validation layers, corrupting downstream tile generators and routing engines. This page is the concrete operational procedure for wiring a change-data-capture pipeline that enforces strict contract validation at the ingestion boundary and commits offsets deterministically so geometry payloads and their positions stay atomic. It sits directly under the Domain Sync Protocols for Spatial Data reference within the broader Federated Ownership & Routing Architecture, and it assumes the payload shape it propagates is already governed by Schema Contracts for Vector/Tile Data. Get the offset and contract handling right and you hold sub-second routing SLAs while preventing the cascading partition rebalances that desync a federated estate.

Change-data-capture sync flow for real-time spatial feeds A PostgreSQL logical replication slot feeds a Debezium source connector. The captured envelope is tested against the schema-registry contract: a violation is branched to the spatial.dlq.geo dead-letter topic, while a valid payload is published to the spatial.sync.* Kafka topics and then fans out to the tile generators and the cross-domain router. A dashed boundary marks the exactly-once region where offset commits bind to the producer transaction, spanning the connector, the contract check, and the topic write. exactly-once boundary — offset commits bound to producer transaction PostgreSQL wal_level=logical · slot Debezium connector pgoutput capture Registry contract valid? spatial.dlq.geo quarantine spatial.sync.* topics Kafka, partition-local Tile generators EPSG:3857 delivery Cross-domain router owning-domain nodes no yes

Prerequisites

Requirement Value / Assumption Notes
CDC source Debezium >= 2.4 PostgreSQL connector pgoutput logical decoding plugin
Source database PostgreSQL >= 14 with wal_level=logical Replication slot on mesh_spatial_registry
Stream platform Kafka Connect distributed cluster (>= 3.5) exactly.once.source.support capable
Schema registry Confluent-compatible registry at schema-registry.mesh.internal:8081 BACKWARD_TRANSITIVE compatibility
CRS contract EPSG:4326 canonical ingress; EPSG:3857 for tile delivery Validated against RFC 7946 GeoJSON
Inspection tools curl, jq, kafka-consumer-groups Reads registry + consumer-group lag
Access role mesh-sync-admin (RBAC) Required to PUT connector configs
Dead-letter topic spatial.dlq.geo Quarantine for contract violations

Step-by-Step Implementation

Sync resolution must decouple capture from dispatch: the connector captures spatial state changes, the registry enforces the contract, and offsets commit inside the producer transaction. Each step below is verifiable with a diagnostic command before you proceed.

1. Deploy the deterministic source connector

Deploy the following Kafka Connect distributed worker configuration. The architecture relies on schema.compatibility=BACKWARD_TRANSITIVE to permit additive field evolution while rejecting structural geometry mutations. The RegexRouter transform rewrites the destination topic name from the source table so spatial partition locality is preserved, and errors.tolerance=none routes any malformed envelope straight to spatial.dlq.geo instead of silently dropping it.

json
{
  "name": "spatial-feed-sync-prod",
  "config": {
    "connector.class": "io.debezium.connector.postgresql.PostgresConnector",
    "database.hostname": "pg-primary-geo.internal",
    "database.port": "5432",
    "database.dbname": "mesh_spatial_registry",
    "table.include.list": "public.realtime_feeds,public.vector_tiles",
    "plugin.name": "pgoutput",
    "schema.registry.url": "https://schema-registry.mesh.internal:8081",
    "key.converter": "io.confluent.connect.avro.AvroConverter",
    "key.converter.schema.registry.url": "https://schema-registry.mesh.internal:8081",
    "value.converter": "io.confluent.connect.json.JsonSchemaConverter",
    "value.converter.schema.registry.url": "https://schema-registry.mesh.internal:8081",
    "value.converter.schemas.enable": "true",
    "transforms": "routeByEnvelope",
    "transforms.routeByEnvelope.type": "org.apache.kafka.connect.transforms.RegexRouter",
    "transforms.routeByEnvelope.regex": "mesh_spatial_registry\\.public\\.(.*)",
    "transforms.routeByEnvelope.replacement": "spatial.sync.$1",
    "offset.flush.timeout.ms": "10000",
    "max.batch.size": "2048",
    "poll.interval.ms": "50",
    "tasks.max": "4",
    "errors.tolerance": "none",
    "errors.deadletterqueue.topic.name": "spatial.dlq.geo",
    "errors.log.enable": "true",
    "errors.log.include.messages": "true"
  }
}

ExtractField$Key extracts a single field from a Kafka record key (a Struct), not from the geometry payload, so it cannot route on a spatial envelope. Use RegexRouter on the topic name (derived from the source table) as shown, and implement spatial partitioning in a custom producer-side Partitioner — the Debezium connector does not natively support geometry-based routing transforms.

Apply via the Kafka Connect REST API. Use PUT for idempotent deployment, which overwrites existing connector state without creating duplicates:

bash
curl -X PUT -H "Content-Type: application/json" \
  --data @spatial-sync-config.json \
  http://connect-cluster.mesh.internal:8083/connectors/spatial-feed-sync-prod/config

Verify: confirm partition assignment and consumer-group stability before sending production load.

bash
kafka-consumer-groups \
  --bootstrap-server broker-1.mesh.internal:9092 \
  --describe \
  --group spatial-feed-sync-prod

2. Bind offset commits to the producer transaction

Spatial routing requires deterministic offset commits to prevent duplicate tile generation or coordinate-interpolation gaps. Enable exactly.once.source.support=enabled in the distributed worker configuration (connect-distributed.properties) so offset commits bind to the producer transaction and a geometry payload is never committed without its offset. offset.flush.timeout.ms=10000 prevents worker threads from blocking indefinitely during high-throughput ingestion bursts; if the timeout is breached, the connector pauses polling, logs OffsetFlushTimeoutException, and triggers backpressure handling.

Verify: confirm exactly-once is active on the running worker.

bash
curl -s http://connect-cluster.mesh.internal:8083/connectors/spatial-feed-sync-prod/status \
  | jq '.connector.state, .tasks[].state'

3. Validate the schema registry contract

Query the active schema version for the vector_tiles subject and cross-reference the geometry field type against the OGC GeoJSON specification RFC 7946. This is the contract that the gateway-level checks in Schema Contracts for Vector/Tile Data also enforce, so a payload that passes here is not rejected downstream.

bash
curl -s http://schema-registry.mesh.internal:8081/subjects/vector_tiles-value/versions/latest \
  | jq '.schema' | python3 -m json.tool

Failure indicators: a present crs property (RFC 7946 explicitly removes the crs member, so its presence signals a pre-RFC 7946 schema), type changed from object to string, or an altered coordinates array depth.

4. Isolate offset lag from payload corruption

When downstream consumers report InvalidGeometryException or tile misalignment, the first triage decision is schema drift versus offset lag. Run consumer-group lag analysis: high lag with zero dead-letter throughput indicates a downstream processing bottleneck, while zero lag with high dead-letter throughput indicates schema or topology violations.

bash
kafka-consumer-groups \
  --bootstrap-server broker-1.mesh.internal:9092 \
  --describe \
  --group spatial-feed-sync-prod \
  --members \
  --verbose

Verify: filter Connect worker logs for deterministic failure signatures.

bash
grep -E "InvalidGeometryException|SchemaVersionMismatch|OffsetCommitFailed|TopologyDegradation" \
  /var/log/kafka/connect-distributed.log | tail -n 50

5. Restart tasks and reset offsets safely

When a single task fails, restart only that task to avoid a full connector rebalance — a rebalance pauses every partition and is the most common cause of a self-inflicted sync gap.

bash
curl -X POST \
  http://connect-cluster.mesh.internal:8083/connectors/spatial-feed-sync-prod/tasks/0/restart

Never use --reset-offsets --to-earliest on production spatial topics; it replays the entire history into tile generators. Reset to a specific position aligned with the last valid geometry envelope checkpoint instead:

bash
kafka-consumer-groups \
  --bootstrap-server broker-1.mesh.internal:9092 \
  --group spatial-feed-sync-prod \
  --reset-offsets \
  --to-offset 3 \
  --topic spatial.sync.vector_tiles:0 \
  --execute

Configuration Reference

Field Scope Required value Effect
schema.compatibility Registry subject BACKWARD_TRANSITIVE Allows additive fields; rejects structural geometry mutation
exactly.once.source.support Worker (connect-distributed.properties) enabled Binds offset commit to producer transaction
offset.flush.timeout.ms Connector 10000 Caps offset-flush block time; triggers backpressure on breach
max.batch.size Connector 2048 Upper bound on records per poll batch
poll.interval.ms Connector 50 Capture cadence for sub-second feeds
tasks.max Connector 4 Parallel capture tasks; scale to 8 under P3 lag
errors.tolerance Connector none Routes bad envelopes to the dead-letter topic, no silent drops
errors.deadletterqueue.topic.name Connector spatial.dlq.geo Quarantine stream for contract violations
transforms.routeByEnvelope.regex RegexRouter mesh_spatial_registry\.public\.(.*) Source-table capture group
transforms.routeByEnvelope.replacement RegexRouter spatial.sync.$1 Destination topic, preserving partition locality

Common Failure Modes & Fixes

Pin every desync to a deterministic log signature first — the failure flag tells you which layer to act on.

SchemaVersionMismatch — zero consumer lag, high dead-letter throughput. Root cause: the registry rejected a payload for a BACKWARD_TRANSITIVE violation (a producer published a structural geometry mutation). Fix: pause the connector, revert the subject to the last stable version, and replay from the checkpoint — curl -X PUT http://connect-cluster.mesh.internal:8083/connectors/spatial-feed-sync-prod/pause.

TopologyDegradation — geometry rejected during WKT parsing. Root cause: self-intersection or invalid ring ordering in the source feature. Fix: quarantine the producing pipeline and validate the offending geometry with ogrinfo -al -so against the source table before re-admitting it; do not widen tolerance to force it through.

OffsetCommitFailed — task drops out of the consumer group. Root cause: the worker lost coordination with the group coordinator, usually a network partition or broker unavailability. Fix: confirm broker reachability (kafka-broker-api-versions --bootstrap-server broker-1.mesh.internal:9092), then restart only the affected task per Step 5.

Geometry-based routing silently fails — records land on the wrong partition. Root cause: an ExtractField$Key transform was used to route by envelope, but it only reads the record-key Struct. Fix: route by topic name with RegexRouter and move spatial partitioning into a custom producer Partitioner.

Sub-second routing SLA breached, TopologyDegradation reaching tile generators. Root cause: a corrupt envelope is propagating across domains faster than triage. Fix: isolate the affected domain, trip the Federated Ownership & Routing Architecture circuit breaker, and shed read traffic to the Fallback Chains for Geocoding Services until the contract is restored.

Tiered escalation keeps these actions bounded. Log every action in the incident tracker with the correlation ID from the X-Trace-Id header.

Severity Trigger condition Immediate action Escalation path
P3 Consumer lag > 500ms across > 2 partitions Scale tasks.max to 8; verify broker I/O throughput Platform Engineering
P2 Dead-letter throughput > 5% of ingestion; SchemaVersionMismatch Pause connector; revert registry to last stable; replay from checkpoint GIS Data Stewardship + Schema Owners
P1 Sub-second SLA breached; TopologyDegradation propagating Isolate domain; trip routing circuit breaker; route to fallback geocoding Incident Commander + Mesh Architecture Lead

For a P1, halt the connector, drain pending offsets to the current position, validate the contract against the latest stable registry release, then resume and watch consumer-lag and record-error-rate for 15 minutes before closing:

bash
curl -X PUT http://connect-cluster.mesh.internal:8083/connectors/spatial-feed-sync-prod/pause
kafka-consumer-groups \
  --bootstrap-server broker-1.mesh.internal:9092 \
  --group spatial-feed-sync-prod \
  --reset-offsets --to-current --execute

All spatial sync configurations must be version-controlled in the infrastructure-as-code repository, and manual curl overrides are prohibited outside active incident windows. For heavy spatial query backpressure, route work to the Async Execution for Heavy Spatial Queries pipelines rather than blocking the ingestion worker, and let validated changes travel through the Cross-Domain Routing Strategies layer.

FAQ

Why use BACKWARD_TRANSITIVE compatibility instead of plain BACKWARD?

Plain BACKWARD only checks the new schema against the immediately previous version. In a federated mesh, consumers in different domains lag at different schema versions, so a field removed two versions ago can still break a slow consumer. BACKWARD_TRANSITIVE validates the new schema against every prior version, which is what guarantees that a tile generator pinned to an older vector_tiles schema can still read the latest feed.

How does exactly-once support prevent duplicate tile generation?

With exactly.once.source.support=enabled, the connector writes the source record and its offset inside a single Kafka producer transaction. If the worker crashes mid-flush, the transaction aborts and neither the geometry payload nor the offset is committed, so on restart the connector re-reads from the last committed position without emitting a duplicate. Without it, an offset can commit before its payload is durably written, producing a coordinate-interpolation gap.

Why must crs be absent from the GeoJSON contract?

RFC 7946 removed the crs member and fixed GeoJSON to EPSG:4326 (WGS 84, longitude/latitude). A payload that still carries crs is a pre-RFC 7946 schema, and admitting it lets a producer silently ship EPSG:3857 coordinates labelled as EPSG:4326. The registry contract treats a present crs as a SchemaVersionMismatch so the mismatch is caught at ingestion, not in a downstream tile.

What is the correct way to reset offsets after a corrupt feed?

Never --to-earliest on a production spatial topic — it replays the full history into tile generators and triggers a rebuild storm. Reset to a specific offset (--to-offset <n> --topic <name>:<partition>) aligned with the last valid geometry-envelope checkpoint, or --to-current during a P1 drain. Always run with --dry-run first, then --execute.

Can I route records to partitions by spatial envelope inside the connector?

Not with a built-in single-message transform. RegexRouter only rewrites the topic name, and ExtractField$Key reads the key Struct, not the geometry. To partition by bounding box or geohash you implement a custom producer-side Partitioner that hashes the spatial envelope, keeping the connector responsible only for capture and topic routing.