pgsync is a free, open source data engineering & integration project written in Python and released under MIT. It has 1,433 GitHub stars, 213 forks and 216 open issues, and was last pushed 21 hours ago. On this registry it ranks #36 of 53 tracked projects in Data Engineering & Integration, with 5 head-to-head comparisons available. It gained 10 stars over the last 30 tracked days.

What is pgsync?

PGSync is a change data capture tool that syncs PostgreSQL, MySQL, or MariaDB to Elasticsearch or OpenSearch in real time, aimed at teams that want to keep a relational database as the source of truth while serving fast search from a separate index.

What it is

PGSync is an open-source, MIT-licensed Python project that reads changes from a relational database and writes them into a search engine. It lives in the Data & Analytics / Data Engineering & Integration space and works with PostgreSQL, MySQL, and MariaDB on the source side and Elasticsearch or OpenSearch on the destination side. The document structure is declared in a JSON schema file rather than in application code, and PGSync applies that schema to denormalize nested relationships into search documents. Changes are captured through logical replication on the source database, which the README describes as propagation via WAL or binlog depending on the engine.

The concrete thing it replaces is a hand-built ETL or indexing pipeline. Without a tool like this, a team maintaining search over relational data has to write and operate its own code to detect row changes, join parent and child tables, build nested documents, and bulk index them, while also handling checkpoints, retries, and ordering. PGSync removes that custom code: the mapping lives in JSON, the sync process runs as a long-lived worker, and the README states that documents appear in commit order and that the process resumes from checkpoints after a crash. It also states that the CDC approach keeps database impact minimal.

Key capabilities

  • Real-time sync from PostgreSQL, MySQL, or MariaDB into Elasticsearch or OpenSearch using logical replication, with bulk indexing on the destination.
  • Zero-code configuration: document shape is defined in a JSON schema file passed as --config schema.json, with no ETL pipeline to build.
  • Nested document handling that automatically denormalizes complex relationships between tables into a single search document.
  • Fault tolerance through checkpointing, so the process resumes from its last checkpoint after a crash.
  • Transactionally consistent writes, with documents appearing in the index in commit order.
  • A one-time bootstrap command for initial setup, alongside the pgsync sync command.
  • Docker distribution under the image toluaina1/pgsync, driven by environment variables PG_URL, ELASTICSEARCH_URL, and REDIS_HOST.
  • Companion connectors: django-pgsync for Django projects, and pgsync-clickhouse, a change-data-capture sink that streams PostgreSQL to ClickHouse, mirroring tables or building denormalized wide tables on ReplacingMergeTree, with exactly-once-on-replay keyed on the source WAL LSN.

Who uses it and how

  • Teams running PostgreSQL, MySQL, or MariaDB as the system of record that need a search index kept current without querying the primary database on every read.
  • Django projects, which can integrate through the django-pgsync package rather than driving the CLI directly.
  • Operators who want a self-contained stack: docker-compose up starts PostgreSQL, Redis, Elasticsearch, Kibana, and PGSync together, with PostgreSQL on port 15432, Elasticsearch on 9201, and Kibana on 5601.
  • OpenSearch deployments, which use docker-compose --profile opensearch up to start PostgreSQL, Redis, OpenSearch, and PGSync, with OpenSearch on port 9400.
  • Evaluation and demos, through the live demo at https://demo.pgsync.com/.

Getting started

Install with pip install pgsync, then run bootstrap --config schema.json once and pgsync --config schema.json -d for ongoing sync; alternatively run the published image with docker run --rm -it toluaina1/pgsync:latest -c schema.json -d -b, setting PG_URL, ELASTICSEARCH_URL, and REDIS_HOST.

How it compares

The facts do not name any paid product that PGSync replaces, and no directly comparable tool is named either. It stands alone in this registry.

When to use it — and when not to

A self-hoster must operate the source database with logical replication enabled, a Redis instance for state, and an Elasticsearch or OpenSearch cluster, which is real operational surface beyond the sync process itself. Teams that cannot run a long-lived worker, or that want only periodic batch exports rather than change data capture, should look elsewhere. The project is actively maintained but carries a substantial open issue count, and the README is task-focused rather than exhaustive on configuration, so expect to read the linked documentation and examples.

project readme (upstream, from github) — read inline

PGSync

Real-time PostgreSQL to Elasticsearch/OpenSearch sync

Keep your relational database as the source of truth while powering lightning-fast search

PyPI version Python versions CI status codecov Downloads License

Docker Code style: black

Live Demo ↗ · Website · Documentation · Examples · Report Bug


What is PGSync?

PGSync is a change data capture tool that syncs data from PostgreSQL, MySQL, or MariaDB to Elasticsearch or OpenSearch in real-time. Define your document structure in JSON, and PGSync handles the rest — no custom code required.

Django integration: Use django-pgsync to integrate PGSync with Django projects.

PGSync Pro: Add real-time semantic and hybrid search to Elasticsearch or OpenSearch, with on-device or hosted embeddings that re-embed only when a document's meaning changes.

ClickHouse connector: Stream PostgreSQL to ClickHouse in real time with pgsync-clickhouse, a change-data-capture sink that mirrors tables or builds denormalized wide tables on ReplacingMergeTree (one-to-one children flattened into typed columns, one-to-many into native Array(Tuple(...))), with exactly-once-on-replay keyed on the source WAL LSN.

%%{init: {'look': 'handDrawn', 'theme': 'neutral'}}%%
flowchart LR
    subgraph Source["🗄️ Source Database"]
        DB[(PostgreSQL<br/>MySQL<br/>MariaDB)]
    end

    subgraph CDC["⚡ Change Data Capture"]
        P[PGSync]
    end

    subgraph Search["🔍 Search Engine"]
        ES[(Elasticsearch<br/>OpenSearch)]
    end

    DB -->|WAL / Binlog| P
    P -->|Bulk Index| ES

Key Features

Feature Description
Real-time sync Changes propagate instantly via logical replication
Zero code Define mappings in JSON — no ETL pipelines to build
Nested documents Automatically denormalize complex relationships
Fault tolerant Resumes from checkpoints after crashes
Transactionally consistent Documents appear in commit order
Minimal overhead Lightweight CDC with negligible database impact

Quick Start

Using Docker (Fastest)

docker run --rm -it \
  -e PG_URL=postgres://user:pass@host/db \
  -e ELASTICSEARCH_URL=http://localhost:9200 \
  -e REDIS_HOST=localhost \
  -v "$(pwd)/schema.json:/app/schema.json" \
  toluaina1/pgsync:latest -c schema.json -d -b

Using pip

pip install pgsync
# Bootstrap (one-time setup)
bootstrap --config schema.json

# Run sync
pgsync --config schema.json -d

Using Docker Compose

Default (Elasticsearch + Kibana):

git clone https://github.com/toluaina/pgsync
cd pgsync
docker-compose up

This starts PostgreSQL, Redis, Elasticsearch, Kibana, and PGSync configured for Elasticsearch.

For OpenSearch:

docker-compose --profile opensearch up

This starts PostgreSQL, Redis, OpenSearch, and PGSync configured for OpenSearch.

Ports:

  • PostgreSQL: 15432
  • Elasticsearch: 9201 (default)
  • Kibana: 5601 (default)
  • OpenSearch: 9400 (OpenSearch profile)

How It Works

1. Define your schema — Map tables to document structure:

{
  "table": "book",
  "columns": ["isbn", "title", "description"],
  "children": [{
    "table": "author",
    "columns": ["name"]
  }]
}

2. PGSync generates optimized queries — Complex JOINs handled automatically:

SELECT JSON_BUILD_OBJECT(
  'isbn', book.isbn,
  'title', book.title,
  'authors', (SELECT JSON_AGG(author.name) FROM author ...)
) FROM book

3. Get denormalized documents — Ready for search:

{
  "isbn": "9785811243570",
  "title": "Charlie and the Chocolate Factory",
  "authors": ["Roald Dahl"]
}

Changes to any related table automatically update the document in Elasticsearch/OpenSearch.


Requirements

Component Version
Python 3.10+
PostgreSQL 9.6+ (or MySQL 5.7.22+ / MariaDB 10.5+)
Elasticsearch 6.3.1+ (or OpenSearch 1.3.7+)
Redis 3.1+ (or Valkey 7.2+) — optional in WAL mode

Database Setup

PostgreSQL

Enable logical decoding in postgresql.conf:

wal_level = logical
max_replication_slots = 1

Optionally limit WAL size:

max_slot_wal_keep_size = 100GB
MySQL / MariaDB

Enable binary logging in my.cnf:

server-id = 1
log_bin = mysql-bin
binlog_row_image = FULL
binlog_expire_logs_seconds = 604800

Create replication user:

CREATE USER 'replicator'@'%' IDENTIFIED WITH mysql_native_password BY 'password';
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'replicator'@'%';
FLUSH PRIVILEGES;

Example

Consider a book library with related authors:

Book
isbn (PK) title description
9785811243570 Charlie and the Chocolate Factory Willy Wonka's famous...
9781471331435 1984 George Orwell's chilling...
Author
id (PK) name
1 Roald Dahl
4 George Orwell

PGSync transforms this into search-ready documents:

[
  {
    "isbn": "9785811243570",
    "title": "Charlie and the Chocolate Factory",
    "authors": ["Roald Dahl"]
  },
  {
    "isbn": "9781471331435",
    "title": "1984",
    "authors": ["George Orwell"]
  }
]

Any change — updating an author's name, adding a new book, deleting a relationship — is automatically synced.


Transforms

PGSync supports built-in transforms to modify field values before indexing. Transforms are applied in order: replace → rename → concat.

Replace

Find and replace substrings within field values:

{
  "table": "product",
  "columns": ["code", "name"],
  "transform": {
    "replace": {
      "code": {
        "-": "/",
        "_": " "
      }
    }
  }
}
Before After
ABC-DEF_GHI ABC/DEF GHI

Rename

Rename fields in the output document:

{
  "table": "book",
  "columns": ["id", "title"],
  "transform": {
    "rename": {
      "id": "book_id",
      "title": "book_title"
    }
  }
}

Concat

Combine multiple fields into a new field:

{
  "table": "user",
  "columns": ["first_name", "last_name"],
  "transform": {
    "concat": {
      "columns": ["first_name", "last_name"],
      "destination": "full_name",
      "delimiter": " "
    }
  }
}

Combined Example

Transforms can be combined and applied to nested children:

{
  "table": "book",
  "columns": ["isbn", "title"],
  "children": [{
    "table": "publisher",
    "columns": ["code", "name"],
    "transform": {
      "replace": { "code": { "-": "." } },
      "rename": { "name": "publisher_name" }
    }
  }],
  "transform": {
    "concat": {
      "columns": ["isbn", "title"],
      "destination": "search_text",
      "delimiter": " - "
    }
  }
}

Why PGSync?

Challenge PGSync Solution
Dual writes are error-prone Captures changes from WAL — single source of truth
Complex JOIN queries Auto-generates optimized SQL from your schema
Nested document updates Detects changes in any related table
Data consistency Transactionally consistent, ordered delivery
Crash recovery Checkpoint-based resumption

Environment Variables

Full list at pgsync.com/env-vars

Variable Description
PG_URL PostgreSQL connection string
ELASTICSEARCH_URL Elasticsearch/OpenSearch URL
REDIS_HOST Redis/Valkey host
REDIS_CHECKPOINT Use Redis for checkpoints (recommended for production)

One-Click Deploy

Deploy to DigitalOcean


Sponsors

DigitalOcean

Contributing

Contributions welcome! See CONTRIBUTING.rst for guidelines.

License

MIT — use it freely in your projects.

Frequently asked questions

Is pgsync free to use?

pgsync is open source under the MIT licence. There is no licence fee and no seat count — you can self-host it or, where the project offers one, pay a vendor for a managed version instead.

What does pgsync do?

Postgres, MySQL, or MariaDB to Elasticsearch/OpenSearch sync

What is pgsync written in?

pgsync is primarily written in Python. Its source is publicly available at https://github.com/toluaina/pgsync, and it has 1,433 GitHub stars.