Sync Monitoring

Data types

How column values cross between the two databases, and what happens to a column one side does not have.

A captured change carries the row itself, not a description of it. The trigger writes the row's values into the outbox, the agent sends them as JSON, and the other side writes them back. Most columns survive that round trip untouched, and the ones that need help are the subject of this page.

Values JSON cannot carry

Three MySQL types have no natural JSON form, so the agent encodes them on the way out and decodes them on the way in.

TypeCrosses asWhy
DATE, DATETIME, TIMESTAMPThe literal string MySQL returnedConverting to a real date and back would apply the host's timezone at each end and silently shift the value
BLOB, BINARY, VARBINARYA tagged object holding base64Plain base64 would be indistinguishable from a string column that merely looks like base64
BIGINT and other large numbersA stringValues beyond JavaScript's safe integer range would otherwise lose precision

Binary is decoded back to bytes before the write, so the receiving row holds the same value the source did — the tag exists only in transit.

JSON columns cross as the JSON value itself and are written back as JSON text. This assumes the column is JSON on both sides: if the receiving column is JSON but the source's is TEXT, the source's text arrives as a JSON string rather than the object it spells out.

Datetimes are copied, not translated

Both databases receive the exact string the source returned. If the two hosts run different timezones, the same instant is stored as two different wall-clock values on each side. That is a deployment decision the agent deliberately does not make for you.

Zero dates

Older applications often store 0000-00-00 or 0000-00-00 00:00:00 in place of NULL. The application gets away with it because its own connection turns MySQL's zero-date checks off — Laravel does this with 'strict' => false. The agent's connection would otherwise use the server's default mode, which on MySQL 8 refuses zero dates:

Incorrect datetime value: '0000-00-00 00:00:00' for column 'deleteddatetime'

So the agent turns off NO_ZERO_DATE and NO_ZERO_IN_DATE for its own session, and nothing else. A zero date copies across like any other value. A genuinely invalid date, such as 2026-13-45, is still refused when the server runs in strict mode.

Columns the other side does not have

Before writing a row, the agent reads the receiving table's column list from INFORMATION_SCHEMA and drops any payload key that is not in it.

A column that exists only on the source is therefore ignored, and the row still applies without it. Nothing fails, nothing is logged, and the two rows end up holding different data. This is the failure mode most worth knowing about, because it is invisible from the run history — the run is a success.

The Schema tab is what surfaces it. Scanning a table on both sides lists the columns each one defines, so a column present on one side and missing on the other shows up as a difference rather than as silently absent data.

Generated columns (VIRTUAL or STORED) are dropped the same way, because MySQL refuses a value written to one. The receiving side computes its own from the rest of the row.

If the table is missing entirely, the agent throws Table "..." does not exist in this database and the queue stops there. That is loud by design: a missing table is a misconfiguration, and skipping past it would leave a permanent gap.

Rows are matched by the configured key

The receiving side finds the row to update using the primary key column set on the sync table, not MySQL's own key definition. If the two sides disagree about what identifies a row, they will disagree about which row to write.

Two independently created rows sharing a key are a conflict, and the agent holds them rather than guessing.

What never crosses

Schema changes are not captured. Triggers fire on INSERT, UPDATE and DELETE, so adding a column, widening a type, adding an index or dropping a table produce nothing in the outbox.

Both databases have to be migrated separately, and doing one and not the other is exactly how the silent column-dropping above starts. Migrate both sides, then scan the table to confirm they agree.

On this page