Skip to content

Backfill crash-loops when a table's primary key includes an enum column #2149

Description

@Catskan

What happens

I set up a sink with an initial backfill on a table whose primary key includes a Postgres enum column. The backfill never makes progress — the TableReaderServer for that consumer keeps crashing (roughly once a second) and the backfill stays stuck in active.

The crash:

[error] [TableReaderServer] ID fetch task failed backfill_id=...
** (MatchError) no match of right hand side value:
   {:error,
    %Postgrex.Error{
      postgres: %{
        code: :invalid_text_representation,
        message: "invalid input value for enum mood: \"\"",
        routine: "enum_recv",
        where: "unnamed portal parameter $2",
        pg_code: "22P02"
      }
    }}
    (sequin 0.1.0) lib/sequin/runtime/table_reader.ex:177: Sequin.Runtime.TableReader.fetch_batch_pks/4

Running v0.14.6, but the relevant code looks the same on main.

Why

The initial keyset cursor uses an empty string for any column whose type isn't uuid / number / date / time:

# lib/sequin/runtime/keyset_cursor.ex
def min_for_type(_), do: ""

casted_cursor_values/2 passes that through untouched for an enum column (cast_value(_, val), do: val), so the batch query ends up binding $2 = "". Postgres refuses "" as an enum value (enum_recv, SQLSTATE 22P02). And because fetch_batch_pks/4 matches the result with

{:ok, %Postgrex.Result{} = result} = Postgres.query(db_or_conn, sql, params, timeout: timeout)

the {:error, _} isn't handled, the task raises, and it retries forever.

Repro

CREATE TYPE mood AS ENUM ('calm', 'happy', 'sad');
CREATE TABLE t (
  id uuid DEFAULT gen_random_uuid(),
  m  mood,
  PRIMARY KEY (m, id)
);
INSERT INTO t (m) VALUES ('calm'), ('happy'), ('sad');

Point a sink at public.t with an initial backfill and it starts crash-looping right away.

Related

Same root cause, quieter symptom: with a character(n) / bpchar primary key the empty-string cursor doesn't raise, but the backfill finishes having read 0 rows — it ends up completed with rows_processed_count = 0. uuid / text / timestamp keys work fine.

Suggestion

min_for_type/1 returning "" isn't a valid minimum for a lot of column types. For an enum it could use the first label (ORDER BY enumsortorder LIMIT 1); more generally the backfill could seed the cursor from SELECT min(<sort_col>) instead of a synthetic value. And fetch_batch_pks/4 probably shouldn't hard-match the query result — right now any Postgres error there turns into a crash loop.

Can put together a PR if that'd help.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions