Finding and Fixing INT-Based Sequences in YugabyteDB Before They Overflow

Why This Matters

In YugabyteDB, sequences are distributed objects. High CACHE settings and session-level caching can lead to larger gaps in generated IDs. Normally, gaps are harmlessm but if the sequence feeds an INT or SMALLINT column, you can hit the column’s hard limit much sooner than expected:

  • SMALLINT max: 32,767

  • INT max: 2,147,483,647

Once the sequence exceeds that limit, inserts fail with “integer out of range” errors.

Worse, in YugabyteDB changing a column from INTBIGINT requires a full table rewrite, which is painful on large production tables.

That’s why it’s critical to find and fix risky columns early.

Three Common Risk Patterns

These all create sequence-backed integer columns that can eventually overflow in a busy distributed cluster:

				
					-- SERIAL (implicit INT sequence)
CREATE TABLE orders (
    order_id SERIAL PRIMARY KEY,
    order_date date NOT NULL
);

-- IDENTITY column (explicit INT sequence)
CREATE TABLE customers (
    customer_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    name text
);

-- Manual sequence in DEFAULT expression
CREATE SEQUENCE emp_seq;

CREATE TABLE emp (
    emp_id INT DEFAULT nextval('emp_seq') PRIMARY KEY,
    emp_name TEXT
);
				
			
The Detection Query

The query below will find all SMALLINT/INT columns linked to sequences — covering:

  • • SERIAL (owned sequence, deptype = 'a')

  • • IDENTITY (internal/owned, deptype = 'i')

  • • Manual nextval() default (dependency from pg_attrdef to sequence)

  • • Fallback regex parsing if dependencies are missing

It also:

  • • Shows sequence settings (last_value, min_value, max_value, cache_size)

  • • Calculates approx. remaining values until column overflow

  • • Flags mismatches where a column’s type max is less than the sequence’s max_value

				
					WITH base_cols AS (
  SELECT
      ns.nspname                AS schema_name,
      t.relname                 AS table_name,
      a.attnum                  AS attnum,
      a.attname                 AS column_name,
      a.atttypid::regtype::text AS column_type,
      (a.attidentity <> '')     AS is_identity,
      format('%I.%I', ns.nspname, t.relname) AS fq_table_name,
      t.oid                     AS table_oid
  FROM pg_class t
  JOIN pg_namespace ns ON ns.oid = t.relnamespace
  JOIN pg_attribute a  ON a.attrelid = t.oid
                       AND a.attnum > 0
                       AND NOT a.attisdropped
  WHERE t.relkind IN ('r','p')
    AND a.atttypid IN ('int2'::regtype, 'int4'::regtype)
),
col_defaults AS (
  SELECT
    b.*, ad.oid AS ad_oid,
    pg_get_expr(ad.adbin, ad.adrelid) AS def_expr
  FROM base_cols b
  LEFT JOIN pg_attrdef ad
    ON ad.adrelid = b.table_oid
   AND ad.adnum   = b.attnum
),
by_func AS (
  SELECT c.*, pg_get_serial_sequence(c.fq_table_name, c.column_name) AS seq_from_func
  FROM col_defaults c
),
by_depend_owned AS (
  SELECT c.schema_name, c.table_name, c.attnum, c.column_name, c.column_type, c.is_identity,
         sn.nspname || '.' || s.relname AS seq_from_owned
  FROM col_defaults c
  JOIN pg_depend d
    ON d.refobjid = c.table_oid
   AND d.refobjsubid = c.attnum
   AND d.deptype IN ('a','i')
  JOIN pg_class s ON s.oid = d.objid AND s.relkind = 'S'
  JOIN pg_namespace sn ON sn.oid = s.relnamespace
),
by_depend_default AS (
  SELECT c.schema_name, c.table_name, c.attnum, c.column_name, c.column_type, c.is_identity,
         sn.nspname || '.' || s.relname AS seq_from_default
  FROM col_defaults c
  JOIN pg_depend d
    ON d.refobjid = c.ad_oid
   AND d.deptype = 'n'
  JOIN pg_class s ON s.oid = d.objid AND s.relkind = 'S'
  JOIN pg_namespace sn ON sn.oid = s.relnamespace
),
by_regex AS (
  SELECT c.schema_name, c.table_name, c.attnum, c.column_name, c.column_type, c.is_identity,
         CASE
           WHEN c.def_expr ~* $$nextval\s*\('([^']+)'\s*::\s*regclass\)$$ THEN
             (SELECT n.nspname || '.' || cls.relname
              FROM pg_class cls
              JOIN pg_namespace n ON n.oid = cls.relnamespace
              WHERE cls.oid = to_regclass(
                regexp_replace(c.def_expr,
                               $$.*nextval\s*\('([^']+)'\s*::\s*regclass\).*$$,
                               '\1')
              ))
         END AS seq_from_regex
  FROM col_defaults c
),
risky AS (
  SELECT DISTINCT
    f.schema_name, f.table_name, f.column_name, f.column_type, f.is_identity,
    COALESCE(f.seq_from_func, o.seq_from_owned, d.seq_from_default, r.seq_from_regex) AS sequence_name
  FROM by_func f
  LEFT JOIN by_depend_owned   o USING (schema_name, table_name, attnum)
  LEFT JOIN by_depend_default d USING (schema_name, table_name, attnum)
  LEFT JOIN by_regex          r USING (schema_name, table_name, attnum)
  WHERE COALESCE(f.seq_from_func, o.seq_from_owned, d.seq_from_default, r.seq_from_regex) IS NOT NULL
),
seqs AS (
  SELECT schemaname, sequencename, last_value, min_value, max_value, increment_by, cache_size
  FROM pg_sequences
)
SELECT
  r.schema_name, r.table_name, r.column_name, r.column_type, r.is_identity,
  r.sequence_name,
  s.last_value, s.min_value, s.max_value, s.increment_by, s.cache_size,
  CASE
    WHEN r.column_type = 'smallint' THEN (32767      - COALESCE(s.last_value, 0))::bigint
    WHEN r.column_type = 'integer'  THEN (2147483647 - COALESCE(s.last_value, 0))::bigint
  END AS approx_remaining_until_overflow,
  CASE
    WHEN r.column_type = 'smallint' AND s.max_value > 32767 THEN '⚠ mismatch: seq max > SMALLINT max'
    WHEN r.column_type = 'integer'  AND s.max_value > 2147483647 THEN '⚠ mismatch: seq max > INT max'
  END AS mismatch_warning
FROM risky r
LEFT JOIN seqs s
  ON s.schemaname   = split_part(r.sequence_name, '.', 1)
 AND s.sequencename = split_part(r.sequence_name, '.', 2)
ORDER BY r.schema_name, r.table_name, r.column_name;

				
			
Example Output

For our sample tables:

				
					 schema_name | table_name | column_name | column_type | is_identity |          sequence_name           | last_value | min_value |      max_value      | increment_by | cache_size | approx_remaining_until_overflow |       mismatch_warning
-------------+------------+-------------+-------------+-------------+----------------------------------+------------+-----------+---------------------+--------------+------------+---------------------------------+-------------------------------
 public      | customers  | customer_id | integer     | t           | public.customers_customer_id_seq |            |         1 |          2147483647 |            1 |        100 |                      2147483647 |
 public      | emp        | emp_id      | integer     | f           | public.emp_seq                   |            |         1 | 9223372036854775807 |            1 |        100 |                      2147483647 | ⚠ mismatch: seq max > INT max
 public      | orders     | order_id    | integer     | f           | public.orders_order_id_seq       |            |         1 |          2147483647 |            1 |        100 |                      2147483647 |
				
			
Takeaways
  • • Always start with BIGSERIAL or BIGINT GENERATED ... AS IDENTITY in distributed systems.

  • • Audit regularly for:

    • ◦ INT/SMALLINT columns with sequences

    • ◦ Mismatched sequence max values

  • • Fix early to avoid disruptive rewrites.

Have Fun!

Chip, our deck-crashing chipmunk, now shows up daily for his food. If you’ve been following my tips, you know I still miss Lucy, our dog, but Chip’s a decent stand-in… at least until the next dog comes along and demotes him back to “yard squirrel.” 🐿️