Data Mirror
A production-grade Change Data Capture (CDC) synchronization engine. Continuously monitor database changes and propagate them to your sync backend in real time.
1 Introduction
The Data Mirror is a production-grade, containerized Change Data Capture (CDC) synchronization engine. It continuously monitors changes in one or more PostgreSQL or MySQL databases and propagates those changes in real time to a search engine of your choice (Meilisearch, Elasticsearch, OpenSearch, or any custom backend).
The engine is distributed as a Docker image. You configure it entirely through environment variables and a single YAML schema file that you mount into the container. There is no application code to write - unless you wish to integrate a custom sync backend or perform advanced data transformations via Python scripts.
Key Capabilities
Two CDC Modes
Debezium + Kafka for scale, or Direct Stream (WAL/Binlog) for zero-infrastructure simplicity.
Declarative YAML Schema
Support for one-to-one, one-to-many, many-to-one, and many-to-many relationships.
Python Transform Scripts
Built-in field transformations and User-Controlled Python Scripts for advanced logic.
Smart Batch Processing
Deduplication and configurable accumulation windows for optimal performance.
Initial Snapshot Support
Resume semantics to bulk-load tables without downtime.
2 Architecture Overview
The engine is composed of the following logical layers:
CDC Modes in Detail
CDC_MODE=wal or postgres_wal
PostgreSQL WAL Mode
The engine creates a PostgreSQL replication slot and publication, then streams row-level changes directly from the Write-Ahead Log.
-
•
Tracks position using LSN checkpoint
file (
/data/lsn_checkpoint.txt) - • Resumes from last LSN on restart, ensuring zero data loss
- • Uses internal in-memory broker by default
CDC_MODE=mysql_binlog
MySQL Binlog Mode
The engine connects to MySQL as a replica and reads the binary log directly.
-
•
Requires
binlog_format=ROWandbinlog_row_image=FULL - • Automatically handles MySQL 8.4+ compatibility
-
•
Tracks position using JSON checkpoint
file (
/data/mysql_binlog_checkpoint.json) - • Supports comma-separated table inclusion/exclusion via environment variables
CDC_MODE=debezium
Debezium Mode
An external Debezium Connect instance
monitors PostgreSQL and publishes change
events to Kafka. Debezium applies the ExtractNewRecordState Single Message Transform (SMT), which flattens events into simple before/after records.
- • The engine waits for the Debezium Connect REST API to become available before starting
- • It automatically registers a Debezium connector for each database referenced in the schema
- • On shutdown, the engine deregisters its Debezium connectors for clean teardown
-
•
The Kafka topic naming convention follows:
{server_name}.{schema}.{table}
3 System Requirements
wal_level=logical (Required based on usage)
binlog_format=ROW (Required based on usage)
Installation & Setup Guide
1. Docker & Docker Compose
Required to run the Data Mirror engine and its peripheral services.
2. Python v3.9+
Required for running custom transformation scripts or local testing utilities.
sudo apt update
sudo apt install python3 python3-pip
# Requires Homebrew
brew install python
Windows users can download the installer from python.org.
3. Databases & Search Engines
Use Docker to quickly initialize your source and sink environments.
docker run -itd -p 7700:7700 getmeili/meilisearch:latest
docker run -d --name pg-source \
-e POSTGRES_PASSWORD=pass \
-p 5432:5432 \
postgres:latest -c wal_level=logical
psql -c "SHOW wal_level;"
-- Should return: logical
# If it shows replica or minimal, update postgresql.conf:
# In postgresql.conf
wal_level = logical
# Then restart PostgreSQL
sudo systemctl restart postgresql
4 Quick Start
Step 1: Create Your Schema File
Create a file called
schema.yaml
that defines which tables to sync and how to map
them.
schemas:
- database: "mydb"
index: "products"
enabled: true
table: "products"
schema: "public"
primary_key: "id"
columns:
- id
- name
- price
- description
searchable_attributes:
- name
- description
filterable_attributes:
- price
sortable_attributes:
- price
Step 2: Run the Data Mirror Engine
Option A: WAL Mode (Simplest)
docker run -d \
--name data-mirror \
-e SOURCE_DB_TYPE=postgres \
-e CDC_MODE=wal \
-e POSTGRES_HOST=host.docker.internal \
-e POSTGRES_PORT=5432 \
-e POSTGRES_USER=postgres \
-e POSTGRES_PASSWORD=your_password \
-e SINK_BACKEND=meilisearch \
-e SINK_HOST=http://host.docker.internal:7700 \
-e SINK_API_KEY=your_master_key \
-v ./schema.yaml:/app/schema.yaml \
--add-host=host.docker.internal:host-gateway \
data-mirror
Option B: MySQL Binlog Mode
docker run -d \
--name data-mirror \
-e SOURCE_DB_TYPE=mysql \
-e CDC_MODE=mysql_binlog \
-e MYSQL_HOST=host.docker.internal \
-e MYSQL_PORT=3306 \
-e MYSQL_USER=root \
-e MYSQL_PASSWORD=your_password \
-e MYSQL_CDC_ENABLED=true \
-e SINK_BACKEND=meilisearch \
-e SINK_HOST=http://host.docker.internal:7700 \
-e SINK_API_KEY=your_master_key \
-v ./schema.yaml:/app/schema.yaml \
--add-host=host.docker.internal:host-gateway \
data-mirror
Option C: Debezium + Kafka Mode
docker run -d \
--name data-mirror \
-e SOURCE_DB_TYPE=postgres \
-e CDC_MODE=debezium \
-e BROKER_TYPE=kafka \
-e KAFKA_BROKER=kafka:29092 \
-e KAFKA_GROUP_ID=data-mirror \
-e KAFKA_AUTO_OFFSET_RESET=earliest \
-e DEBEZIUM_SERVER_NAME=dbserver1 \
-e CONNECT_HOST=http://debezium:8083 \
-e POSTGRES_HOST=host.docker.internal \
-e POSTGRES_PORT=5432 \
-e POSTGRES_USER=postgres \
-e POSTGRES_PASSWORD=your_password \
-e SINK_BACKEND=meilisearch \
-e SINK_HOST=http://host.docker.internal:7700 \
-e SINK_API_KEY=your_master_key \
-v ./schema.yaml:/app/schema.yaml \
--add-host=host.docker.internal:host-gateway \
data-mirror
Alternatively, use Docker Compose for a complete Debezium + Kafka stack:
services:
# Zookeeper for Kafka
zookeeper:
image: confluentinc/cp-zookeeper:7.5.0
hostname: zookeeper
container_name: zookeeper
ports:
- "2181:2181"
environment:
ZOOKEEPER_CLIENT_PORT: 2181
ZOOKEEPER_TICK_TIME: 2000
networks:
- cdc-network
healthcheck:
test: ["CMD", "nc", "-z", "localhost", "2181"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
deploy:
resources:
limits:
memory: 512M
reservations:
memory: 256M
# Kafka Broker
kafka:
image: confluentinc/cp-kafka:7.5.0
hostname: kafka
container_name: kafka
depends_on:
zookeeper:
condition: service_healthy
ports:
- "9092:9092"
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: "zookeeper:2181"
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT
KAFKA_LISTENERS: INTERNAL://0.0.0.0:29092,EXTERNAL://0.0.0.0:9092
KAFKA_ADVERTISED_LISTENERS: INTERNAL://kafka:29092
KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
KAFKA_LOG_RETENTION_HOURS: 168
networks:
- cdc-network
healthcheck:
test:
["CMD-SHELL", "kafka-topics --bootstrap-server localhost:29092 --list"]
interval: 10s
timeout: 10s
retries: 5
start_period: 30s
deploy:
resources:
limits:
memory: 2G
reservations:
memory: 512M
debezium:
image: debezium/connect:3.0.0.Final
hostname: debezium
container_name: debezium
depends_on:
kafka:
condition: service_healthy
ports:
- "8083:8083"
environment:
BOOTSTRAP_SERVERS: kafka:29092
GROUP_ID: debezium-group
CONFIG_STORAGE_TOPIC: debezium_configs
OFFSET_STORAGE_TOPIC: debezium_offsets
STATUS_STORAGE_TOPIC: debezium_statuses
KEY_CONVERTER: org.apache.kafka.connect.json.JsonConverter
VALUE_CONVERTER: org.apache.kafka.connect.json.JsonConverter
CONNECT_KEY_CONVERTER_SCHEMAS_ENABLE: "false"
CONNECT_VALUE_CONVERTER_SCHEMAS_ENABLE: "false"
CONNECT_PRODUCER_BATCH_SIZE: 32768
CONNECT_PRODUCER_LINGER_MS: 10
# Increase memory
KAFKA_HEAP_OPTS: "-Xms512m -Xmx1024m"
networks:
- cdc-network
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8083/connectors"]
interval: 15s
timeout: 10s
retries: 5
start_period: 30s
deploy:
resources:
limits:
memory: 2G
reservations:
memory: 512M
data-mirror:
image: data-mirror-preview
container_name: data-mirror
depends_on:
kafka:
condition: service_healthy
debezium:
condition: service_healthy
ports:
- 8080:8080
- 8090:8090
env_file:
- .env
volumes:
- ./schema.yaml:/app/schema.yaml
- ./transforms:/app/transformers
extra_hosts:
- host.docker.internal:host-gateway
networks:
- cdc-network
networks:
cdc-network:
driver: bridge
name: cdc-network
Step 3: Verify
# Check logs
docker logs -f data-mirror
# Check pipeline health (REST API)
curl -H "Authorization: Bearer changeme" http://localhost:8090/api/pipeline
# Check metrics (Prometheus)
curl http://localhost:8080/metrics
5 Environment Variable Reference
Core Configuration
| Variable | Default | Description |
|---|---|---|
| SOURCE_DB_TYPE | postgres | Database type: postgres, mysql |
| CDC_MODE | debezium | The capture mode. Options: debezium, wal (or postgres_wal), mysql_binlog (or binlog). |
| BROKER_TYPE | kafka | Message broker for event transport. Options: kafka, redis, local. |
| INTERNAL_BROKER_ENABLED | false | When true, uses an in-memory queue for direct stream modes, bypassing external brokers. |
| SINK_BACKEND | meilisearch | Targeted search engine. Options: meilisearch, elasticsearch, opensearch. |
| LOG_LEVEL | INFO | Log level (DEBUG, INFO, WARNING, ERROR). Emitted as structured JSON. |
| TRANSFORM_SCRIPTS_DIR | /app/transformers |
Directory to look for custom Python transform scripts specified in schema.yaml.
|
| CUSTOM_BACKENDS_PATH | /app/custom_backends | Base directory where the engine scans for custom sync backend plugin folders. |
Database Configuration
| Variable | Default | Description |
|---|---|---|
| POSTGRES_HOST | localhost | PostgreSQL host |
| POSTGRES_PORT | 5432 | PostgreSQL port |
| POSTGRES_USER | postgres | PostgreSQL user (requires replication/superuser for WAL mode). |
| POSTGRES_PASSWORD | postgres | PostgreSQL password |
| MYSQL_HOST | localhost | MySQL host. (Used if SOURCE_DB_TYPE=mysql). |
| MYSQL_PORT | 3306 | MySQL port. |
| MYSQL_USER | root | MySQL user (requires REPLICATION SLAVE for binlog mode). |
| MYSQL_PASSWORD | — | MySQL password. |
| DATABASE_POOL_SIZE | 10 | Number of connections to maintain per database. |
Important: The database name is not configured here. Each schema entry in your schema.yaml specifies its own database field. The engine creates a separate connection pool for each unique database.
Sync Backend Configuration
| Variable | Default | Description |
|---|---|---|
| SINK_HOST | — | The URL of the search engine instance (e.g., http://localhost:7700 for Meilisearch, http://localhost:9200 for Elasticsearch). This is read directly by the sync backend plugin. |
| SINK_API_KEY | — | API key or authentication token for the search engine. For Meilisearch, this is the master key. For Elasticsearch, this may be the API key or basic auth credentials. |
Kafka Broker Configuration
| Variable | Default | Description |
|---|---|---|
| KAFKA_BROKER | localhost:9092 | Kafka bootstrap servers. |
| KAFKA_GROUP_ID | data-mirror | Consumer group ID. |
| KAFKA_AUTO_OFFSET_RESET | earliest | Where to start consuming if no committed offset exists. Options: earliest(from the beginning) or latest(only new messages). |
Redis Broker Configuration
| Variable | Default | Description |
|---|---|---|
| REDIS_HOST | localhost | Redis host. |
| REDIS_PORT | 6379 | Redis port. |
| REDIS_PASSWORD | — | Redis password. |
| REDIS_DB | 0 | Redis database number. |
Debezium Configuration
| Variable | Default | Description |
|---|---|---|
| CONNECT_HOST | http://localhost:8083 | Debezium Connect REST API URL. |
| DEBEZIUM_SERVER_NAME | dbserver1 | Prefix for Kafka topics. |
Direct Stream Configuration (Postgres & MySQL)
| Variable | Default | Description |
|---|---|---|
| PG_REPLICATION_SLOT | sync_engine_slot | PostgreSQL logical replication slot name. |
| PG_PUBLICATION_NAME | sync_engine_pub | PostgreSQL publication name. |
| MYSQL_CDC_ENABLED | false | Must be true for mysql_binlog mode. |
| MYSQL_BINLOG_CHECKPOINT_PATH | /data/binlog_checkpoint.json | File to persist MySQL binlog position. |
| MYSQL_BINLOG_SERVER_ID | 5401 | Unique ID for the MySQL binlog reader. |
Sync & Batch Processing Configuration
| Variable | Default | Description |
|---|---|---|
| SCHEMA_PATH | /app/schema.yaml | Path inside the container where the engine looks for the schema YAML file. |
| ENABLE_BATCHING | true | When enabled, events are accumulated into batches before processing. When disabled, each event is processed individually. |
| SYNC_MAX_BATCH_SIZE | 500 | Maximum number of CDC events to accumulate in a single batch before triggering processing. |
| SYNC_BATCH_DELAY_MS | 2000 | Maximum time in milliseconds to wait while accumulating events before flushing the batch. |
| SYNC_POLLING_STRATEGY | linger | Strategy for event polling. Options: linger (latency-optimized) or deadline (throughput-optimized). |
| SYNC_MAX_SINK_BATCH_SIZE | 1000 | Maximum number of documents to send to the sink backend in a single bulk API call. |
| SYNC_MAX_RELATION_FAN_OUT | 1000 | Maximum number of parent records to re-index when a related (child or junction) table changes. |
| SYNC_INITIAL_SNAPSHOT | false | When set to true, the engine performs a full table scan of all configured schemas on startup. |
| SYNC_SNAPSHOT_BATCH_SIZE | 500 | Number of rows to fetch per batch during the initial snapshot. |
| SYNC_LSN_CHECKPOINT_PATH | /data/lsn_checkpoint.txt | File path inside the container where the WAL LSN checkpoint is persisted. |
| HEALTH_CHECK_PORT | 8080 | Port for the HTTP health check server. Exposes /health, /ready, /metrics, and /stats endpoints. |
| ENABLE_PROMETHEUS | true | Enable or disable the Prometheus metrics server. |
| METRICS_PORT | 9090 | Port for the dedicated Prometheus metrics HTTP server. |
| ENABLE_METRICS | true | Master toggle for all metrics collection. |
| ENVIRONMENT | production | Application environment setting (e.g., production, development). |
| LOG_LEVEL | INFO | Logging verbosity. Options: DEBUG, INFO, WARNING, ERROR. |
| LOG_FORMAT | json | Format of the stdout logs. Options: json, console. |
| DATABASE_MAX_OVERFLOW | 20 | Maximum number of additional connections to open beyond the pool size during high concurrency. |
| SYNC_BATCH_QUERY_TIMEOUT_SECONDS | 300 | Maximum time allowed for large bulk enrichment queries before timing out. |
| API_ENABLED | true | Enable or disable the REST API for management and monitoring. |
| API_PORT | 8090 | Port for the REST API server. |
| DASHBOARD_API_KEY | changeme | Basic authentication token required to access the REST API endpoints. |
6 Schema Configuration Reference
The schema file is the heart of the sync engine. It
is a YAML file mounted at
/app/schema.yaml. It contains a list of schema definitions under
the top-level
schemas key.
6.1 Root Schema Properties
schemas:
- database: "mydb" # Source database name
index: "products" # Target search index name (created automatically)
enabled: true # Set to false to disable this sync task
table: "products" # Source table name
schema: "public" # Database schema/namespace (Postgres only)
primary_key: "id" # The primary key column for the target index
columns: ["*"] # List of columns to sync. Use ["*"] for all.
# Advanced Properties
ranking_rules: ["words", "typo", "proximity", "attribute", "sort", "exactness"] # Meilisearch optimization
filters: ["category", "price"] # List of filterable attributes (sync backend dependent)
where_clause: "price > 0 AND status = 'active'" # SQL filter for snapshot fetching
transform_script: "product_cleaner.py" # Path to a custom Python transformation script
6.2 Transformations
Transformations are applied after data is fetched from the database and before it is sent to the sync backend.
transform:
# Rename columns in the output document
rename:
style_code: "styleId"
business_name: "businessName"
# Cast column values to specific types
cast:
price: "float"
stock: "int"
allow_backorder: "bool"
# Compute derived fields
computed:
- field: "discount_percentage"
expression: "((mrp - price) / mrp * 100) if mrp > 0 else 0"
- field: "in_stock"
expression: "stock > 0"
6.3 Relationships
One-to-One
A single related record is embedded as a JSON object.
relationships:
- name: "vendor"
type: "one_to_one"
table: "vendors"
join_condition:
parent_key: "vendor_id"
child_key: "id"
One-to-Many
Multiple related records are embedded as a JSON array.
relationships:
- name: "tags"
type: "one_to_many"
table: "product_tags"
join_condition:
parent_key: "id"
child_key: "product_id"
Many-to-Many
Joins through a junction table.
relationships:
- name: "categories"
type: "many_to_many"
table: "categories"
through_table: "product_categories"
join_condition:
parent_key: "id"
through_parent_key: "product_id"
through_child_key: "category_id"
child_key: "id"
6.4 Python Transform Scripts
For complex logic that cannot be expressed via YAML (e.g. conditional field updates, external data enrichment, or complex calculations), you can provide a custom Python script.
How to use
- Create a
.pyfile (e.g.transform_product.py). - Mount the folder containing the script to
/app/transformers. - In
schema.yaml, settransform_script: transform_product.py.
def transform(doc: dict) -> dict:
# Perform custom logic
if doc.get("price") > 1000:
doc["is_premium"] = True
# Return the modified dict
return doc
Workflow:
The script is loaded lazily on first use. If an error occurs inside your transform()
function, the engine logs the traceback but continues processing with the original
document to prevent pipeline failure.
7 Initial Snapshot
When deploying against an existing database, set
SYNC_INITIAL_SNAPSHOT=true
to perform a full table scan on startup.
Records current WAL LSN, iterates through all rows using cursor-based pagination, and pushes to sync backend.
After snapshot completes, WAL streaming resumes from recorded LSN, catching up on any changes during snapshot.
Tip: Mount a Docker volume
at
/data to
persist the LSN checkpoint across container
restarts. This prevents re-running the
snapshot.
8 Management REST API (Port 8090)
All requests must include an
Authorization: Bearer {DASHBOARD_API_KEY}
header.
Configuration Endpoints
/api/config/schemas
Returns active schema mappings
/api/config/settings
Returns engine configuration and connection
health
/api/config/sync/{index_name}
Triggers manual re-sync for specified
index
/api/config/reload
Reloads schema and Python scripts from
disk
Pipeline Monitoring
/api/pipeline
Comprehensive health check with WAL lag,
rates, memory
/api/pipeline/recent_events
Returns 50 most recent CDC events
8.3 DLQ & Error Management
The engine includes a Dead-Letter Queue (DLQ) for records that fail processing. These endpoints allow you to inspect, retry, or purge failed records.
/api/errors
Paginated, searchable list of failed records
/api/errors/stats
Error rate, health scores, and DLQ statistics
/api/errors/{id}
Get detailed error payload and traceback
/api/errors/{id}/retry
Immediately retry a specific failed record
/api/errors/retry-all
Drain broker DLQ and retry all messages
/api/errors/retry-all-inmemory
Retry all retryable in-memory DLQ records
/api/errors
Purge all records from the DLQ
9 Monitoring & Observability
Health Check Endpoints (Port 8080)
| Endpoint | Description |
|---|---|
| /health | Liveness probe. Returns 200 if process is running. |
| /ready | Readiness probe. Returns 200 when processor is initialized. |
| /metrics | Prometheus metrics in text exposition format. |
| /stats | Human-readable JSON summary of current metrics. |
Prometheus Metrics
cdc_events_processed_total
Counter
cdc_events_failed_total
Counter
cdc_event_processing_duration_seconds
Histogram
cdc_kafka_consumer_lag
Gauge
cdc_db_queries_total
Counter
10 Production Deployment
Persist the LSN Checkpoint
Always mount a volume at /data to resume from last position after restart.
Use WAL Mode for Simplicity
Unless you have Kafka infrastructure, WAL mode is simpler with fewer moving parts.
Tune Batch Parameters
Increase SYNC_MAX_BATCH_SIZE for throughput, decrease for latency requirements.
Set SYNC_MAX_RELATION_FAN_OUT
Prevent runaway re-indexing for broadly-referenced lookup tables.
Monitor with Prometheus
Scrape /metrics endpoint and set alerts on cdc_events_failed_total and consumer lag.
PostgreSQL Replication User
Ensure user has REPLICATION privilege:
ALTER USER postgres WITH
REPLICATION;
11 Advanced Operational Topics
11.1 Multi-Database Synchronization
The Data Mirror can synchronize data from multiple independent databases in a single instance. This is achieved by specifying different database names in your schema.yaml.
- • The engine maintains a separate connection pool for each unique database found in the schema.
- • Global connection settings (Host, Port, User, Password) apply to all databases.
- • For PostgreSQL, ensure the user has the REPLICATION attribute on all target databases.
11.2 Broker Decoupling & Kafka-less Operation
Historically, Debezium required Kafka for schema history storage. Data Mirror now allows for "Kafka-less" operation in several ways:
1. Direct Streams
Using CDC_MODE=wal or mysql_binlog with INTERNAL_BROKER_ENABLED=true removes the need for any external broker (Kafka or Redis) entirely.
2. Redis Broker
You can use BROKER_TYPE=redis to use Redis as the primary message transport instead of Kafka.
3. File-based Schema History
When running Debezium without Kafka, the engine automatically configures Debezium to use local file-based schema history (FileSchemaHistory), persisting it in the /data directory.
12 Smart Batch Processing
The engine employs an intelligent batching strategy to minimize database queries and sync backend API calls:
Event Accumulation
CDC events are buffered in memory for up to SYNC_BATCH_DELAY_MS milliseconds or until SYNC_MAX_BATCH_SIZE events are collected.
Deduplication
Within a batch, if the same record ID appears multiple times (e.g. rapid updates), only the last event is kept.
Relationship Resolution
When child/junction rows change, the engine identifies affected parent record IDs and fetches complete documents in bulk.
Bulk Fetch
Optimized recursive JSON queries fetch complete document trees in a single database round-trip per batch.
Bulk Push
Documents are sent using batch APIs, respecting the SYNC_MAX_SINK_BATCH_SIZE limit.
13 Pre-flight Checks
Before processing any events, the engine runs a series of pre-flight checks to validate connectivity and configuration:
-
1. Debezium Connectivity (Debezium mode only): Verifies that the Connect REST API is reachable.
-
2. Sync Backend Connectivity Connects to backend and performs a health check.
-
3. Broker Connectivity Verifies configured message broker is reachable.
-
4. Database Connectivity Verifies PostgreSQL/MySQL is reachable for each schema database.
If any pre-flight check fails, the engine logs a critical error and exits immediately, preventing silent failures.
14 Custom Sync Backend Plugin
You can integrate any search engine by creating a custom backend plugin. The plugin is loaded at runtime—no need to rebuild the Docker image.
Plugin Structure
manifest.json
Declares plugin metadata, dependencies, and class mapping.
backend.py
Contains the Python class implementing required interface.
{
"name": "my_custom_search",
"description": "My custom sync backend plugin",
"requires": ["some_library"],
"pip_dependencies": ["some_library>=1.0.0"],
"backend_classes": {
"my_custom": "MyCustomBackend"
}
}
Required Interface
Your backend class must inherit from SinkBackendPlugin and implement the following
asynchronous methods. For a full example, refer to the source of the ElasticsearchBackend
distributed with the engine.
from backends.base import SinkBackendPlugin
class MyCustomBackend(SinkBackendPlugin):
async def connect(self):
"""Establish connection to the sink."""
async def disconnect(self):
"""Close connection to the sink."""
async def upsert_document(self, index_name, document, doc_id):
"""Insert or update a single document."""
async def delete_document(self, index_name, doc_id):
"""Delete a document."""
async def batch_upsert(self, index_name, documents, primary_key="id"):
"""Batch insert or update (list of (doc, id) tuples)."""
async def batch_delete(self, index_name, doc_ids):
"""Batch delete multiple document IDs."""
async def create_index(self, index_name, settings, primary_key="id"):
"""Create index with settings (ranking, filters, etc)."""
async def delete_index(self, index_name):
"""Delete an index."""
async def index_exists(self, index_name) -> bool:
"""Return True if index exists."""
async def get_index_stats(self, index_name) -> dict:
"""Return index info (document count, size, etc)."""
async def get_document_sample(self, index_name, limit=1) -> list:
"""Fetch a sample of documents for debugging/validation."""
async def wait_for_pending_tasks(self, index_name):
"""Wait for background indexing tasks to settle."""
async def health_check(self) -> bool:
"""Return True if the backend is reachable and healthy."""
Registration & Usage
1. Specify your backend via environment variable:
SINK_BACKEND=my_custom
2. Mount the plugin folder via Docker Compose:
volumes:
- ./my_plugin:/app/custom_backends/my_custom
Or via docker run:
docker run -v ./my_plugin:/app/custom_backends/my_custom \
-e SINK_BACKEND=my_custom \
data-mirror-preview
15 Docker Volume Mounts
Volume Mounts
| Host Path | Container Path | Purpose |
|---|---|---|
| ./schema.yaml | /app/schema.yaml | Required. Your schema definition file. |
| volume or dir | /data | Recommended. Persists LSN checkpoint. |
| ./my_backend/ | /app/custom_backends/ | Optional. Custom backend plugin. |
16 Exposed Ports
Health check server
/health, /ready, /metrics, /stats
Management REST API
Configuration, pipeline control & DLQ management
17 Logging
The engine uses structured JSON logging by default, which is ideal for log aggregation systems like Elasticsearch or Datadog.
You can configure the log format and level using LOG_FORMAT and LOG_LEVEL environment variables.