Docs/Connections/Cloud Databases

YugabyteDB Guide

Use this page when a PostgreSQL connection in DBConvert Streams points at YugabyteDB through its YSQL API.

YugabyteDB is PostgreSQL-compatible at the top and distributed underneath: it reuses the PostgreSQL query layer, so SELECT version() answers with something like PostgreSQL 15.12-YB-2026.1.1.1, and stores rows in its own DocDB tablets rather than in PostgreSQL's files. The standard connection form applies, and everything on this page comes from the places where those two halves disagree.

YSQL listens on port 5433, not 5432.

Measured against a self-managed YugabyteDB 2026.1.1.1. YugabyteDB Aeon, the managed service, has not been tested.

What works

DirectionSupported
YugabyteDB as target, LoadYes
YugabyteDB as target, CDCYes
YugabyteDB as source, LoadYes
YugabyteDB as source, CDCYes - after three prerequisites

A Load run creates the tables together with their primary keys, indexes and foreign keys.

MySQL type mapping

MySQLYugabyteDBNote
YEARsmallint
ENUMtextlabels are kept, the restriction is not
SETtextthe same comma-separated string
BLOBbytea
TINYINT(1)boolean
DECIMALnumeric
TIMESTAMPtimestamp without time zone
smallint unsignedintegerwidened to the next signed type

YugabyteDB as a CDC source

YugabyteDB implements the PostgreSQL replication protocol, and DBConvert Streams reads it with plain pgoutput - no Kafka Connect, no Flink, and not YugabyteDB's own yboutput plugin. Three things have to be true first, and all three are on the YugabyteDB side.

1. Replica identity on every replicated table

YugabyteDB gives a new table the replica identity CHANGE, which does not exist in PostgreSQL and which pgoutput cannot encode. Set a PostgreSQL-compatible identity before the stream is created:

ALTER TABLE public.orders REPLICA IDENTITY DEFAULT;

FULL works too. The order matters: YugabyteDB captures a table's replica identity when the replication slot is created, so changing it afterwards does not affect a stream that already exists.

DBConvert Streams checks this before it creates a publication or a slot and stops with the table name and the statement to run, rather than failing mid-stream.

2. The before-image flag on the tservers

With its default settings YugabyteDB refuses to produce a before-image for an UPDATE or a DELETE and terminates the replication stream:

ERROR: Failed to get the beforeimage for tablet_id: 9e37b58654fa4280b5b5e434f9bbf099

The fix is a tserver flag, cdc_send_null_before_image_if_not_exists, which defaults to false. Set it to true and YugabyteDB sends a null before-image instead of failing:

yb-ts-cli --server_address=<tserver-host>:9100 \
  set_flag cdc_send_null_before_image_if_not_exists true

Add it to the tserver flags of a cluster you start yourself so it survives a restart.

3. The intra-transactional before-image flag

cdc_enable_intra_transactional_before_image also defaults to false. Without it, a row that is inserted and then updated inside the same transaction arrives with the columns that update did not touch set to null. What you see is a run that stops on a NOT NULL column:

ERROR: null value in column "first_name" of relation "actor" violates not-null constraint

On a nullable column there is no error and the stored value is simply replaced, which is worse. A row updated in its own transaction is unaffected either way, so a quick test passes and production does not.

yb-ts-cli --server_address=<tserver-host>:9100 \
  set_flag --force cdc_enable_intra_transactional_before_image true

--force is required: unlike the first flag, this one is not marked safe to change at runtime. On a cluster you start yourself, set it in the tserver flags instead.

With all three in place, a stream delivers the initial snapshot and then inserts, updates and deletes as they happen, including a transaction that does several of them to the same table.

PostgreSQL features YSQL does not have

These are absent or behave differently, so a schema that uses them needs a decision before it moves. All verified on 2026.1.1.1.

FeatureYugabyteDB
GiST indexesNot supported (yugabyte-db#1337)
BRIN indexesNot supported
HASH indexesSilently substituted with YugabyteDB's own lsm access method
GIN indexesSupported
EXCLUDE constraintsNot supported
PostGISExtension not available
ctid, xmin system columnsNot supported
LISTEN / NOTIFYDisabled
UNLOGGED tablesAccepted and ignored
Table inheritanceWorks, marked beta by YugabyteDB

A GiST index on a tsvector column is the common case in a PostgreSQL schema. GIN is the equivalent YSQL accepts, so that index has to be recreated as GIN rather than carried over.

Working as in PostgreSQL: sequences and identity columns, generated stored columns, views and materialized views, PL/pgSQL triggers, deferrable foreign keys, ON DELETE CASCADE, declarative partitioning, partial and expression indexes, COLLATE, ON CONFLICT, FOR UPDATE SKIP LOCKED, savepoints, advisory locks, and the uuid-ossp, pgcrypto, hstore, citext and pg_trgm extensions.

Table and database sizes

YugabyteDB answers pg_relation_size with 0 for every table and pg_database_size with NULL, because rows live in DocDB tablets rather than in relation files. Data Explorer reads the total relation size instead when it is talking to YugabyteDB, so sizes appear as they do for any other connection. An empty table still shows no size, on both engines.

Row counts come from the usual statistics and are accurate; a table that has never been analyzed reports -1 in reltuples, and Data Explorer falls back to an exact count for it.

Diagnosing a slot from SQL does not work here

pg_logical_slot_peek_changes and pg_logical_slot_peek_binary_changes always fail on YugabyteDB:

ERROR: requested WAL segment pg_wal/000000010000000000000000 has already been removed

There is no WAL file behind a YugabyteDB slot for those functions to read. The slot itself is fine - only this way of inspecting it is unavailable. Use a real replication client to check whether a slot streams.