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
| Direction | Supported |
|---|---|
| YugabyteDB as target, Load | Yes |
| YugabyteDB as target, CDC | Yes |
| YugabyteDB as source, Load | Yes |
| YugabyteDB as source, CDC | Yes - after three prerequisites |
A Load run creates the tables together with their primary keys, indexes and foreign keys.
MySQL type mapping
| MySQL | YugabyteDB | Note |
|---|---|---|
YEAR | smallint | |
ENUM | text | labels are kept, the restriction is not |
SET | text | the same comma-separated string |
BLOB | bytea | |
TINYINT(1) | boolean | |
DECIMAL | numeric | |
TIMESTAMP | timestamp without time zone | |
smallint unsigned | integer | widened to the next signed type |
A MySQL ENUM column arrives as text holding the same labels, and a SET column as the
same comma-separated string. Which values are allowed is not carried over. Add a CHECK
constraint on the target if you need it enforced.
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.
This is not specific to DBConvert Streams. PostgreSQL's own pg_recvlogical fails the same
way on the same slot, with REPLICA IDENTITY DEFAULT and with FULL. Until the flag is
set, nothing can read updates or deletes out of that cluster over the PostgreSQL protocol.
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.
| Feature | YugabyteDB |
|---|---|
| GiST indexes | Not supported (yugabyte-db#1337) |
| BRIN indexes | Not supported |
| HASH indexes | Silently substituted with YugabyteDB's own lsm access method |
| GIN indexes | Supported |
EXCLUDE constraints | Not supported |
| PostGIS | Extension not available |
ctid, xmin system columns | Not supported |
LISTEN / NOTIFY | Disabled |
UNLOGGED tables | Accepted and ignored |
| Table inheritance | Works, 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.