[BUG]: `enriched_events` is left partitioned but never registered with pg_partman, and `migrate.sh` exits 0
Describe the bug
After a clean ./scripts/migrate.sh against an empty database, enriched_events exists and is
declared PARTITION BY RANGE, but it is not registered with pg_partman. partman.part_config
has no row for it, and the only child table is enriched_events_default. Every row therefore lands
in the catch-all default partition, and no monthly partitions are ever created or maintained.
migrate.sh exits 0. There is no error, no warning, and nothing in the log at default level.
This is not a pg_partman problem and not a managed-database problem — it reproduces on
getlago/postgres-partman:15.0-alpine, the image this project ships.
To Reproduce
Start the reference database image:
docker network create lagorepro docker run -d --name pgpartman --network lagorepro \ -e POSTGRES_PASSWORD=repro -e POSTGRES_USER=lago -e POSTGRES_DB=lago \ getlago/postgres-partman:15.0-alpineConfirm the extension is present — it is, version
5.4.0:docker exec pgpartman psql -U lago -d lago -tAc \ "select extversion from pg_extension where extname='pg_partman'"Run the migration against it:
docker run --rm --network lagorepro \ -e RAILS_ENV=production \ -e DATABASE_URL=postgresql://lago:repro@pgpartman:5432/lago \ -e SECRET_KEY_BASE=... -e LAGO_RSA_PRIVATE_KEY=... \ -e LAGO_ENCRYPTION_PRIMARY_KEY=... -e LAGO_ENCRYPTION_DETERMINISTIC_KEY=... \ -e LAGO_ENCRYPTION_KEY_DERIVATION_SALT=... \ getlago/api:v1.52.1 ./scripts/migrate.shIt exits 0.
Inspect the result:
SELECT relkind FROM pg_class WHERE relname='enriched_events' AND relnamespace='public'::regnamespace; -- p (partitioned, as expected) SELECT count(*) FROM partman.part_config WHERE parent_table='public.enriched_events'; -- 0 (expected: 1) SELECT c.relname FROM pg_inherits i JOIN pg_class c ON c.oid=i.inhrelid WHERE i.inhparent='public.enriched_events'::regclass; -- enriched_events_default (and nothing else)
Expected behavior
partman.create_parent succeeds, partman.part_config gains a row for public.enriched_events, and
pg_partman creates monthly partitions from 2024-12-01 plus three premade ahead — the parameters the
migration itself passes.
Failing that, the migration should raise, so the operator learns about it during deployment rather than months later.
| Check | Expected | Actual |
|---|---|---|
migrate.sh exit code |
0 | 0 |
pg_partman installed |
yes | yes (5.4.0) |
enriched_events is relkind='p' |
yes | yes |
Row in partman.part_config |
1 | 0 |
| Monthly partitions | ~25 | 0 — only the default |
| Warning or error anywhere | — | none |
Screenshots
N/A — server-side, output shown above.
Support
Not a browser issue, so the equivalents:
- Deployment: Docker, self-hosted
- Lago version: v1.52.1 (
getlago/api:v1.52.1); also reproduced on v1.52.0 - Database:
getlago/postgres-partman:15.0-alpine— PostgreSQL 15.0, pg_partman 5.4.0 - OS: Linux x86_64
Additional context
pg_partman is working correctly. A control test in the same session, issuing the identical
create_parent call from db/migrate/20260109132143_partition_enriched_events.rb against a freshly
created partitioned table, returned t, wrote the part_config row, and built 26 partitions. So the
extension functions; the migration's invocation of it does not take effect.
Probable mechanism. partman.create_parent appears to return false rather than raising. After the
migration, both partman.template_public_enriched_events and enriched_events_default exist —
artifacts create_parent creates early — but there is no part_config row. Because execute sees no
exception, the migration is recorded as applied and the script exits 0.
Compounding it, both 20260109092932_setup_partman.rb and 20260109132143_partition_enriched_events.rb
guard on pg_extension_present?("pg_partman") and return quietly if it is absent, logging only at
debug. Between a silent guard and a create_parent that fails without raising, there is no signal
at all — we confirmed zero lines mentioning partman or create_parent in the full migration output.
Why this matters. The failure is silent and the consequence is delayed. An operator has no reason to suspect anything is wrong until the table misbehaves months later, and by then every row is in an unbounded default partition — which is worse than no partitioning, since it carries all of the overhead and none of the benefit, and makes attaching real partitions progressively more expensive.
Suggested fix.
- Check the boolean returned by
create_parentand raise if it isfalse. - Log at
warnrather thandebugwhen the partman guard skips.
Workaround, safe only while the default partition is still empty:
ALTER TABLE public.enriched_events DETACH PARTITION public.enriched_events_default;
DROP TABLE public.enriched_events_default;
SELECT partman.create_parent(
p_parent_table := 'public.enriched_events',
p_control := 'timestamp',
p_interval := '1 month',
p_type := 'range',
p_premake := 3,
p_start_partition := '2024-12-01'
);
UPDATE partman.part_config
SET infinite_time_partitions = true,
retention = '14 months',
retention_keep_table = true
WHERE parent_table = 'public.enriched_events';Source: getlago/lago