MP107warningneeds databaseCitus

warn-citus-distributed-ddl

ALTER on a Citus distributed table propagates to every shard on every worker node.

operation
Tables
lock taken
ACCESS EXCLUSIVE
remediation
Manual rewrite
category
Extensions

What triggers it

ctx.tableExtensions.isCitusDistributed true together with an AlterTableStmt. Every such ALTER is reported. The message escalates to a predicted failure when one of the AlterTableCmd entries has subtype AT_AlterColumnType or AT_DropColumn and its name matches citusDistributionColumn case-insensitively, because Citus refuses those rather than propagating them.

What does not

Every run without --database-url: distribution lives only in Citus metadata (requiresDatabaseUrl is set). Anything that is not an AlterTableStmt, so CREATE INDEX on a distributed table is not covered here. Citus reference tables, which are replicated rather than sharded and are deliberately kept out of isCitusDistributed because their cost profile is different. Plain local tables on the coordinator.

Where it applies

Applies to every PostgreSQL version MigrationPilot targets. It only fires on tables managed by Citus. It needs --database-url: without a connection it has nothing to read and stays silent.

Distribution, distribution column and shard count are read from the citus_tables view, falling back to pg_dist_partition and pg_dist_shard on older Citus versions.

The lock, and what it blocks

ACCESS EXCLUSIVE on the coordinator's table, plus the same lock on every shard on every worker, all held until the statement finishes. Because every query for that table passes through the coordinator, one slow worker blocks the whole cluster for that table rather than just its own shards.

Why it matters

A distributed table is a set of shards spread across worker nodes, and Citus propagates DDL to all of them — so one line in the migration becomes a lock on the coordinator plus a lock per shard across the cluster, and the statement is not finished until the slowest worker is. Some forms are refused outright rather than propagated: changing the distribution column answers "cannot execute ALTER TABLE command involving partition column" and the migration stops there.

Unsafe, and safe

Flagged

-- orders is distributed by tenant_id across 32 shards
ALTER TABLE orders ALTER COLUMN tenant_id TYPE bigint;
-- ERROR: cannot execute ALTER TABLE command involving partition column

Safe alternative

-- See what the statement would fan out to before writing it:
SELECT table_name, citus_table_type, distribution_column, shard_count
FROM citus_tables;

What it assumes

Every propagated ALTER gets the same finding: the rule does not separate a metadata-only ADD COLUMN from something that rewrites each shard, and it has no shard sizes and no worker count — citusShardCount counts shards, not the nodes they sit on. The distribution-column check is exact single-column string matching, so a multi-column or expression distribution key, or a subcommand that reaches the column indirectly, is not recognised as blocked. The claim that Citus refuses the statement comes from documented behaviour, not from a version check on the installed extension.

This rule reads live catalogue state, so it says nothing at all without --database-url. That is the trade: no connection, no guess.

What the CLI prints

migrationpilot analyze migration.sql --database-url $DATABASE_URL
⚠ [MP107] WARNING (line 2)
  This ALTER targets "tenant_id", the distribution column of Citus table "orders". Citus refuses ALTER TABLE commands involving the distribution column, so the migration will fail rather than propagate.

  Safe alternative:
  -- See what this statement fans out to:
  SELECT table_name, citus_table_type, distribution_column, shard_count
  FROM citus_tables WHERE table_name::text LIKE '%orders%';
  
  -- If parallel propagation is deadlocking against other work, serialise it:
  BEGIN;
  SET LOCAL citus.multi_shard_modify_mode TO 'sequential';
  SET LOCAL lock_timeout = '5s';
  ALTER TABLE orders ALTER COLUMN tenant_id TYPE bigint
  COMMIT;
  
  -- Changing the distribution column is not an ALTER. It means redistributing the
  -- table (undistribute_table + create_distributed_table, or alter_distributed_table).

  Why: A distributed table is a set of shards spread across worker nodes. Citus propagates DDL to all of them, so the ALTER holds a lock on the coordinator while each worker locks its own shards, and it is only done when the slowest worker is done. Anything that blocks on the coordinator blocks the whole cluster for that table. Some forms are refused outright rather than propagated: changing the distribution column is the common one.
  Docs: https://migrationpilot.dev/rules/mp107

Generated by running the CLI's own formatter over the flagged example above, so it is the text the tool actually produces. A real run also reports the other rules that fire on the same statement; those blocks are left out here. The catalogue figures come from the production context this rule documents.

Turning it off

For one statement, put a comment on the line before it:

-- migrationpilot-disable MP107
ALTER TABLE orders ALTER COLUMN tenant_id TYPE bigint;

For the whole project, in .migrationpilotrc.yml — by name or by id:

.migrationpilotrc.yml
rules:
  MP107: false

# or keep it, and downgrade it
rules:
  MP107:
    severity: warning

Try it

Open this rule's flagged example in the playground. It runs in your browser — edit it and watch the finding appear and disappear.

Run MP107 in the playground

Related rules