Skip to content

tapdb: fix postgres sequence desyncs#2047

Draft
jtobin wants to merge 2 commits intolightninglabs:mainfrom
jtobin:mig31-fix
Draft

tapdb: fix postgres sequence desyncs#2047
jtobin wants to merge 2 commits intolightninglabs:mainfrom
jtobin:mig31-fix

Conversation

@jtobin
Copy link
Copy Markdown
Member

@jtobin jtobin commented Apr 2, 2026

Fixes Postgres sequence desyncs caused by a couple of legacy migrations. One is material (in universe_leaves, via migration 31) and the other two are benign (supply commitment-related enum values, via migration 40). Each desync'd sequence is simply set to its respective table's max id.

Also adds a Postgres-only unit test that should catch future desyncs caused by migrations like this in CI; it simply checks that every BIGSERIAL sequence is consistent with its table's max id after all migrations have run.

jtobin added 2 commits April 2, 2026 16:33
Migration 31 copied universe_leaves rows with explicit ids, leaving
the BIGSERIAL sequence behind the actual data. New inserts then hit
duplicate-key errors.

Add migration 55 to reset the universe_leaves sequence via setval().
Also reset supply_commit_states and supply_commit_update_types
(migration 40 inserted explicit ids into these enum tables); these
are benign since no code path auto-increments into them, but fixing
them keeps sequence state consistent. On SQLite the statements are
replaced with no-ops.
Add TestSequenceConsistency, a Postgres-only test that verifies every
BIGSERIAL sequence is consistent with its table's max ID after all
migrations have run. This catches migrations that recreate tables and
copy rows with explicit IDs without advancing the sequence.
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses issues where Postgres sequences become desynchronized from their underlying table data due to legacy migrations that inserted rows with explicit IDs. By explicitly resetting these sequences and implementing a robust automated test to monitor sequence consistency in CI, the changes ensure database integrity for future migrations.

Highlights

  • Postgres Sequence Correction: Added a new migration (55) to reset BIGSERIAL sequences for 'universe_leaves', 'supply_commit_states', and 'supply_commit_update_types' to match their table's maximum ID, resolving desyncs caused by legacy migrations.
  • Regression Testing: Introduced a new Postgres-only unit test, 'TestSequenceConsistency', which verifies that all auto-increment sequences remain consistent with their table's maximum ID after migrations are applied.
  • SQLite Compatibility: Updated 'sqliteSchemaReplacements' to ensure the new Postgres-specific sequence reset commands are treated as no-ops when running on SQLite.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds migration 55 to synchronize Postgres sequences that were desynchronized by previous migrations and introduces a new test to verify sequence consistency. Feedback includes a critical fix for the migration script to prevent failures on empty tables, a reminder to update SQLite replacements if the SQL changes, and suggestions to improve the test logic and SQL safety.

Comment on lines +9 to +11
SELECT setval(pg_get_serial_sequence('universe_leaves', 'id'), COALESCE((SELECT MAX(id) FROM universe_leaves), 0));
SELECT setval(pg_get_serial_sequence('supply_commit_states', 'id'), COALESCE((SELECT MAX(id) FROM supply_commit_states), 0));
SELECT setval(pg_get_serial_sequence('supply_commit_update_types', 'id'), COALESCE((SELECT MAX(id) FROM supply_commit_update_types), 0));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

In PostgreSQL, setval will throw an error if the value provided is less than the sequence's MINVALUE (which is 1 by default for BIGSERIAL columns). Using COALESCE(..., 0) will cause this migration to fail on any database where the universe_leaves table is empty (e.g., a fresh installation or a node that hasn't used the Universe feature). To safely handle empty tables and ensure the next value is 1, you should use the three-argument form of setval with a fallback of 1 and an is_called flag based on whether any rows exist.

Suggested change
SELECT setval(pg_get_serial_sequence('universe_leaves', 'id'), COALESCE((SELECT MAX(id) FROM universe_leaves), 0));
SELECT setval(pg_get_serial_sequence('supply_commit_states', 'id'), COALESCE((SELECT MAX(id) FROM supply_commit_states), 0));
SELECT setval(pg_get_serial_sequence('supply_commit_update_types', 'id'), COALESCE((SELECT MAX(id) FROM supply_commit_update_types), 0));
SELECT setval(pg_get_serial_sequence('universe_leaves', 'id'), COALESCE((SELECT MAX(id) FROM universe_leaves), 1), (SELECT MAX(id) FROM universe_leaves) IS NOT NULL);
SELECT setval(pg_get_serial_sequence('supply_commit_states', 'id'), COALESCE((SELECT MAX(id) FROM supply_commit_states), 1), (SELECT MAX(id) FROM supply_commit_states) IS NOT NULL);
SELECT setval(pg_get_serial_sequence('supply_commit_update_types', 'id'), COALESCE((SELECT MAX(id) FROM supply_commit_update_types), 1), (SELECT MAX(id) FROM supply_commit_update_types) IS NOT NULL);

Comment on lines +44 to +56
"SELECT setval(pg_get_serial_sequence(" +
"'universe_leaves', 'id'), COALESCE((" +
"SELECT MAX(id) FROM universe_leaves" +
"), 0));": "SELECT 1;",
"SELECT setval(pg_get_serial_sequence(" +
"'supply_commit_states', 'id'), " +
"COALESCE((SELECT MAX(id) FROM " +
"supply_commit_states), 0));": "SELECT 1;",
"SELECT setval(pg_get_serial_sequence(" +
"'supply_commit_update_types', 'id'), " +
"COALESCE((SELECT MAX(id) FROM " +
"supply_commit_update_types), 0));": "SELECT 1;",
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The strings in sqliteSchemaReplacements must match the SQL in the migration files exactly for the replacement to work. If the SQL in 000055_fix_universe_leaves_seq.up.sql is updated to fix the setval(..., 0) bug, these keys must be updated accordingly. Failure to do so will cause the replacement to fail, and SQLite will attempt to execute the Postgres-specific SQL, leading to migration errors on SQLite backends.

Comment on lines +1381 to +1399
err := db.QueryRowContext(ctx, fmt.Sprintf(
"SELECT last_value FROM %s", s.seqName,
)).Scan(&lastValue)
require.NoError(t, err)

var maxID int64
err = db.QueryRowContext(ctx, fmt.Sprintf(
"SELECT COALESCE(MAX(%s), 0) FROM %s",
s.column, s.table,
)).Scan(&maxID)
require.NoError(t, err)

require.GreaterOrEqual(
t, lastValue, maxID,
"sequence %s on %s.%s is behind: "+
"last_value=%d, max(%s)=%d",
s.seqName, s.table, s.column,
lastValue, s.column, maxID,
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The check lastValue >= maxID is not sufficient to guarantee that the next auto-incremented ID will not collide with existing data. In PostgreSQL, if last_value is equal to maxID but the is_called flag is false, the next call to nextval() will return maxID, causing a primary key violation. A more robust check would involve verifying that the effective next value (accounting for is_called) is strictly greater than maxID. Additionally, when constructing SQL queries with identifiers dynamically, it is safer to use quoted identifiers (e.g., via %q in fmt.Sprintf) to avoid issues with reserved words or case sensitivity.

@jtobin jtobin marked this pull request as draft April 2, 2026 08:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: 🆕 New

Development

Successfully merging this pull request may close these issues.

1 participant