0

We noticed a data inconsistency in a 3-node asynchronous replication Postgres setup. A single JDBC transaction inserted/updated rows in two tables but we are noticing that one table did not have a few rows inserted. A few hours after the transaction the primary node went down and standby node became master.

Environment

  • PostgreSQL 13.x (3 nodes, asynchronous streaming replication)
  • JDBC (PostgreSQL driver)
  • BoneCP connection pool
  • connection.setAutoCommit(false)
  • Isolation: REPEATABLE READ
  • Explicit connection.commit() after multiple executeBatch() calls
  • queryTimeout = 600s (long transaction)
  • Application logs show no rollback, no exceptions
  • Updates made in a transaction have the same value for column version
  • Entries are written to postgres under a global lock
  • No entries have been deleted from the table
  • data‑checksums were not enabled

Code for inserting:

try (Connection connection = dataSource.getConnection()) {
    connection.setAutoCommit(false);
    connection.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
    // 1. Insert into main table (table_a)
    // size of updated is around 400000
    if (!updated.isEmpty()) {
        try (PreparedStatement ps = connection.prepareStatement(
                "INSERT INTO table_a (id, version, content) VALUES (?, ?, ?)")) {
            for (Storable s : updated.values()) {
                ps.setString(1, s.getId());
                ps.setLong(2, s.getVersion());
                ps.setBytes(3, s.toBytes());
                ps.addBatch();
            }
            int[] updates = ps.executeBatch();
            int updateCount = IntStream.of(updates).sum();
            if (updateCount != updated.size()) {
                throw new SQLException("Unexpected updateCount=" + updateCount);
            }
        }
    }

    // 2. Update index table (table_b)
    if (!updated.isEmpty()) {
        try (PreparedStatement ps = connection.prepareStatement(
                "UPDATE table_b SET id=?, name=?, version=? WHERE id=?")) {
            for (Storable s : updated.values()) {
                ps.setString(1, s.getId());
                ps.setString(2, s.getName());
                ps.setLong(3, s.getVersion());
                ps.setString(4, s.getId());
                ps.addBatch();
            }
            int[] updates = ps.executeBatch();
            int updateCount = IntStream.of(updates).sum();
            if (updateCount != updated.size()) {
                throw new SQLException("Unexpected updateCount=" + updateCount);
            }
        }
    }

    connection.commit(); // <- Commit succeeds without errors
} catch (SQLException e) {
   connection.rollback();
   ...
}

Observed Behavior

  • The transaction completed successfully (no rollback or SQL exception).

  • After a failover (a few hours later):

    • The new primary had the updated entries in table_b

    • But for a few IDs(which are present in table_b), table_a had no new rows for that version. None of the postgres nodes have these rows in table_a.

  • For most IDs, both tables were updated correctly.

  • The tables were first queried after failover, so it’s unclear if the inconsistency existed before failover or appeared later.

  • PostgreSQL logs showed no errors during the transaction or failover.

Questions:

  • Could this be caused by disk or memory corruption (given data_checksums were disabled)?
  • Could it be caused due to an issue with async replication? (Logs for successful syncing of wal are present)
  • What specific PostgreSQL or system logs should be checked to confirm whether the WAL for this transaction was fully written and applied?
5
  • 1
    I cannot imagine a way in which only part of a transaction can be replicated. So I'd assume that something was wrong with the original transaction. Commented Nov 9 at 5:56
  • are you saying that, out of the three nodes you have, only this one has rows missing? Commented Nov 9 at 7:27
  • @mustaccio no, none of the nodes have those rows. Will edit the post to remove the ambiguity Commented Nov 12 at 5:32
  • @LaurenzAlbe are there any scenarios where a transaction does not get applied completely or maybe some parts of it get corrupted? I forgot to mention in the post but in this particular transaction the size of updated list was 400k. Just the writes to table_a took 8 minutes (this could primarily be due to inefficient way of writing rows in postgres) Commented Nov 12 at 5:40
  • No, that is not possible, exept if there were a surprising software bug in PostgreSQL. Data can of course be corrupted after the transaction, but that corruption wouldn't restore the data from before the transaction. I bet your transaction boundaries were not what you thought they are. Commented Nov 12 at 8:22

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.