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-alpine
-
Confirm 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.sh
It 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_parent and raise if it is false.
- Log at
warn rather than debug when 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';
Describe the bug
After a clean
./scripts/migrate.shagainst an empty database,enriched_eventsexists and isdeclared
PARTITION BY RANGE, but it is not registered with pg_partman.partman.part_confighas no row for it, and the only child table is
enriched_events_default. Every row therefore landsin the catch-all default partition, and no monthly partitions are ever created or maintained.
migrate.shexits 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:
Confirm the extension is present — it is, version
5.4.0:Run the migration against it:
It exits 0.
Inspect the result:
Expected behavior
partman.create_parentsucceeds,partman.part_configgains a row forpublic.enriched_events, andpg_partman creates monthly partitions from
2024-12-01plus three premade ahead — the parameters themigration itself passes.
Failing that, the migration should raise, so the operator learns about it during deployment rather
than months later.
migrate.shexit codepg_partmaninstalledenriched_eventsisrelkind='p'partman.part_configScreenshots
N/A — server-side, output shown above.
Support
Not a browser issue, so the equivalents:
getlago/api:v1.52.1); also reproduced on v1.52.0getlago/postgres-partman:15.0-alpine— PostgreSQL 15.0, pg_partman 5.4.0Additional context
pg_partman is working correctly. A control test in the same session, issuing the identical
create_parentcall fromdb/migrate/20260109132143_partition_enriched_events.rbagainst a freshlycreated partitioned table, returned
t, wrote thepart_configrow, and built 26 partitions. So theextension functions; the migration's invocation of it does not take effect.
Probable mechanism.
partman.create_parentappears to returnfalserather than raising. After themigration, both
partman.template_public_enriched_eventsandenriched_events_defaultexist —artifacts
create_parentcreates early — but there is nopart_configrow. Becauseexecutesees noexception, the migration is recorded as applied and the script exits 0.
Compounding it, both
20260109092932_setup_partman.rband20260109132143_partition_enriched_events.rbguard on
pg_extension_present?("pg_partman")andreturnquietly if it is absent, logging only atdebug. Between a silent guard and acreate_parentthat fails without raising, there is no signalat all — we confirmed zero lines mentioning
partmanorcreate_parentin 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.
create_parentand raise if it isfalse.warnrather thandebugwhen the partman guard skips.Workaround, safe only while the default partition is still empty: