Sync Monitoring

Conflicts

When both databases claim the same row, and how to decide which version survives.

A conflict is one specific situation: a row was created on one side, and by the time it reached the other database that key was already taken by a different row. Both sides minted the same id independently, so neither version is a copy of the other and writing the incoming one would destroy real data.

The agent refuses the insert and records both versions. Nothing is overwritten until someone decides.

Only inserts are checked this way. An update or a delete arriving for a row whose values differ is not a conflict — that is ordinary sync doing its job. And if the key is already held by a row with the same values, the change is treated as already applied and skipped rather than disputed.

Why the whole key is held

Detection catches the insert that first hit the taken key. From that moment every change to that key is held as well — including the updates and deletes that would otherwise be applied without question.

That matters because the dispute is about identity. If row 4021 is a different record on each side, a later UPDATE to 4021 is an update to the other database's record, and a DELETE would quietly remove the row that was being protected. Holding the key until the dispute is settled is the only safe reading.

Held changes appear in the sync history with a CONFLICT result. The cursor still advances past them, so a single disputed row never blocks the rest of the queue.

A held edit is not thrown away, though. It replaces the version shown for the side that made it, so the comparison you choose from, and the row a resolution writes, is that side's row as it stands now rather than as it was when the clash was found. Once a version has been chosen, only the winning side's version keeps moving; the losing side's stays fixed, because that is what the drift check compares against. A held DELETE has no row to show, so it still waits for the resolution.

Only the side that applied it records the clash

A conflict is detected where a change is applied, so usually only one side has a record of it. Both databases still hold the row until it is resolved — the server tells each agent which keys to hold regardless of which one reported the problem.

Reading the list

The Conflicts tab on a school lists every dispute, newest first, with unresolved ones kept at the top.

ColumnShows
DetectedWhen an agent first refused the row
TableThe table and the disputed key
Refused byLocal or Online — the side that already held a row
DirectionUP or DOWN, the direction the change was travelling
DifferencesThe columns whose values disagree
ActionResolve, or the current state if a choice was already made

Opening a row shows the two versions side by side, one column per line, so you can see exactly what disagrees before choosing.

Choosing a winner

Resolve offers three choices.

ChoiceWhat happens
Keep the local rowThe online database is overwritten with the local values
Keep the online rowThe local database is overwritten with the online values
I fixed it myselfNothing is written. The hold is cleared and syncing resumes

The first two are carried out by an agent, not by the dashboard. The choice is recorded, the losing side picks it up on its next cycle, writes the winning values, and reports back. Only then is the conflict closed.

Choosing applies to the whole key, not to one record. If both sides reported the same clash, one decision settles both — otherwise one agent would keep holding the row.

Two of these overwrite a real row

Keeping one side means the other side's values are gone. The dashboard shows both versions before you commit precisely because this is not reversible from here.

When a resolution comes back unapplied

Before writing, the agent re-reads the row and compares it against the version you were shown. If it has changed in the meantime, the write is abandoned and the conflict reopens with a note explaining why.

This is deliberate. A choice made against a stale view could silently discard an edit made after you looked. When this happens the diff is recomputed from what the row actually holds now, so reopening and choosing again is safe.

A resolution comes back the same way if the write fails outright, or if the table has since been removed from the school's sync configuration. The note on the reopened conflict says which of these it was.

After it is resolved

Resolved conflicts stay in the list as history and are removed by the retention sweep once they pass the history window. Unresolved ones are never removed at any age — the record is what keeps the hold in place.

Avoiding them

Conflicts of this kind almost always mean both databases minted the same primary key independently, which happens when both sides auto-increment from the same starting point. Giving each side its own id range removes the cause rather than managing the symptom.

A conflict between genuinely concurrent edits to the same record is the case this tool is meant for. A steady stream of them on newly created rows is a numbering problem.

On this page