partition_gardener 0.3.6 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +1 -1
- data/docs/THIRD_PARTY_LICENSE_MANIFEST.tsv +19 -19
- data/docs/changelogs/20260709120900_partition-engines-guide.md +1 -1
- data/docs/changelogs/20260813142300_rebalance-before-archive-attach.md +1 -1
- data/docs/changelogs/20260813144516_relock-appraisal-advisory-json.md +1 -1
- data/docs/changelogs/20260902114500_align-child-columns-before-attach.md +1 -1
- data/docs/changelogs/20260902124500_cut-patch-release-0-3-4.md +1 -1
- data/docs/changelogs/20260909143500_occupied-window-current-origin.md +1 -1
- data/docs/changelogs/20260909154900_tail-slot-rfc.md +1 -1
- data/docs/changelogs/20260909162554_high-end-future-occupancy.md +1 -1
- data/docs/changelogs/20260909164000_shipped-contract-rfcs.md +1 -1
- data/docs/changelogs/20260909165500_cut-patch-release-0-3-5.md +1 -1
- data/docs/changelogs/20260916144600_public-identifiers-contract.md +1 -1
- data/docs/changelogs/20261005135700_checkpoint-table-name.md +1 -1
- data/docs/changelogs/20261005141621_cut-patch-release-0-3-6.md +1 -1
- data/docs/changelogs/20261021220000_cut-patch-release-0-3-7.md +24 -0
- data/docs/issues/20260909143500_occupied-window-current-origin.md +1 -1
- data/docs/issues/20260909164000_shipped-contract-rfcs.md +1 -1
- data/docs/issues/20260916113300_opaque-composite-public-ids.md +1 -1
- data/docs/issues/20260917123253_ci-drop-automatic-dependency-audit.md +1 -1
- data/docs/reviews/20261021220000_engineering-audit-0-3-7.md +30 -0
- data/docs/reviews/20261028075216_engineering-audit-release-preparation.md +35 -0
- data/docs/reviews/20261029150855_engineering-audit-release-preparation.md +43 -0
- data/docs/schemas/partition_garden.schema.json +16 -0
- data/lib/partition_gardener/archive_retention.rb +7 -3
- data/lib/partition_gardener/config_document.rb +4 -0
- data/lib/partition_gardener/date_calendar.rb +2 -5
- data/lib/partition_gardener/gap_detection.rb +1 -1
- data/lib/partition_gardener/migration/hot_switch_concern.rb +24 -15
- data/lib/partition_gardener/plan_applier.rb +17 -3
- data/lib/partition_gardener/version.rb +1 -1
- data/sig/partition_gardener.rbs +2 -2
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e5f9084c2b05bdad581f87e5b137cc7321106f2a4a39e3d52def523acd74f0b7
|
|
4
|
+
data.tar.gz: 86ba83786f0cf06729e041e758e8034253e549677237160bb3dc0c09716ec071
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c3c13183c16a47fc4b479a7c4311e689a2081e9dddc0c60f8a590c5e4cdffa4e56db1f8e270e7cf4119772398f1f286baaa4ab985b20ef743b8fa2854a38b47e
|
|
7
|
+
data.tar.gz: 702c2a0f9d29d5c0a63894172ced8ab62cf47f70ab84f0fbcd02a9e648455cfed78927d187ca41538612140c7271b51363937fa60a1be501a24e4c3bb3ac42a5
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.3.7 (2026-10-29)
|
|
6
|
+
|
|
7
|
+
- Fix rebalance resume so phase `detach` still runs detach and drain until the record advances to `segments`
|
|
8
|
+
- Refuse `CREATE TABLE IF NOT EXISTS … PARTITION OF` when a detached table already owns the planned child name
|
|
9
|
+
- Drop detached remove candidates only when they hold zero rows
|
|
10
|
+
- Rename hot-switch children when the live table name is a suffix of the shadow prefix (for example `p_events` → `events`)
|
|
11
|
+
- Repoint serial sequences after hot-switch by setting `nextval` default and `setval` on the live table
|
|
12
|
+
- Fail closed in `sync_delta_data` when `sync_stale_column` is missing from source or target
|
|
13
|
+
- Sort gap detection bounds with native `Comparable` order so integer windows are not lexicographic
|
|
14
|
+
- Advance `DateCalendar.add_years` through leap days via `Date#>>`
|
|
15
|
+
- Notify detach versus drop for archive retention when `retention_keep_table` is set
|
|
16
|
+
- Export and accept `active_id_lo`, `active_id_width`, `current_band_size`, and `archive_band_size` in JSON registry documents
|
|
17
|
+
- Align README Ruby requirement and RBS `statement_timeout` types with the gemspec
|
|
18
|
+
|
|
5
19
|
## 0.3.6 (2026-10-05)
|
|
6
20
|
|
|
7
21
|
- Store rebalance checkpoints in `partition_gardener_checkpoints` by default (previously `partition_gardener_run_records`)
|
data/README.md
CHANGED
|
@@ -6,7 +6,7 @@ PostgreSQL partition lifecycle: archive, current, and future zones, heat-driven
|
|
|
6
6
|
|
|
7
7
|
## Requirements
|
|
8
8
|
|
|
9
|
-
- Ruby >= 3.
|
|
9
|
+
- Ruby >= 3.4
|
|
10
10
|
- PostgreSQL with declarative partitioning
|
|
11
11
|
- `pg` gem (runtime dependency)
|
|
12
12
|
- Rails >= 7.1 optional — railtie loads when Rails is present and sets Active Record connection plus `Time.zone.today`
|
|
@@ -1,60 +1,60 @@
|
|
|
1
1
|
bundle gem version licenses
|
|
2
|
-
root activemodel 8.1.
|
|
3
|
-
root activerecord 8.1.
|
|
4
|
-
root activesupport 8.1.
|
|
2
|
+
root activemodel 8.1.4 MIT
|
|
3
|
+
root activerecord 8.1.4 MIT
|
|
4
|
+
root activesupport 8.1.4 MIT
|
|
5
5
|
root appraisal 2.5.0 MIT
|
|
6
6
|
root ast 2.4.3 MIT
|
|
7
7
|
root base64 0.3.0 Ruby|BSD-2-Clause
|
|
8
|
-
root bigdecimal 4.1.
|
|
8
|
+
root bigdecimal 4.1.3 Ruby|BSD-2-Clause
|
|
9
9
|
root bundler 4.0.16 MIT
|
|
10
10
|
root concurrent-ruby 1.3.8 MIT
|
|
11
11
|
root connection_pool 3.0.2 MIT
|
|
12
12
|
root diff-lcs 1.6.2 MIT|Artistic-1.0-Perl|GPL-2.0-or-later
|
|
13
13
|
root drb 2.2.3 Ruby|BSD-2-Clause
|
|
14
14
|
root i18n 1.15.2 MIT
|
|
15
|
-
root json
|
|
15
|
+
root json 3.1.0 Ruby
|
|
16
16
|
root language_server-protocol 3.17.0.6 MIT
|
|
17
17
|
root lint_roller 1.1.0 MIT
|
|
18
18
|
root logger 1.7.0 Ruby|BSD-2-Clause
|
|
19
19
|
root minitest 6.0.6 MIT
|
|
20
|
-
root parallel 2.
|
|
21
|
-
root parser 3.3.
|
|
22
|
-
root partition_gardener 0.3.
|
|
20
|
+
root parallel 2.3.0 MIT
|
|
21
|
+
root parser 3.3.13.0 MIT
|
|
22
|
+
root partition_gardener 0.3.7 MIT
|
|
23
23
|
root pg 1.6.3 BSD-2-Clause
|
|
24
|
-
root polyrun 2.2.
|
|
24
|
+
root polyrun 2.2.5 MIT
|
|
25
25
|
root prism 1.9.0 MIT
|
|
26
26
|
root prosopite 2.2.0 Apache-2.0
|
|
27
27
|
root racc 1.8.1 Ruby|BSD-2-Clause
|
|
28
|
-
root rack 3.2.
|
|
28
|
+
root rack 3.2.7 MIT
|
|
29
29
|
root rainbow 3.1.1 MIT
|
|
30
30
|
root rake 13.4.2 MIT
|
|
31
31
|
root rbs 4.2.0 BSD-2-Clause|Ruby
|
|
32
|
-
root regexp_parser 2.
|
|
32
|
+
root regexp_parser 2.13.1 MIT
|
|
33
33
|
root rspec 3.13.2 MIT
|
|
34
34
|
root rspec-core 3.13.6 MIT
|
|
35
35
|
root rspec-expectations 3.13.5 MIT
|
|
36
36
|
root rspec-mocks 3.13.8 MIT
|
|
37
37
|
root rspec-support 3.13.7 MIT
|
|
38
|
-
root rubocop 1.
|
|
38
|
+
root rubocop 1.91.0 MIT
|
|
39
39
|
root rubocop-ast 1.50.0 MIT
|
|
40
40
|
root rubocop-capybara 2.23.0 MIT
|
|
41
41
|
root rubocop-factory_bot 2.28.0 MIT
|
|
42
42
|
root rubocop-performance 1.26.1 MIT
|
|
43
43
|
root rubocop-rails 2.34.3 MIT
|
|
44
44
|
root rubocop-rspec 3.10.2 MIT
|
|
45
|
-
root rubocop-rspec_rails 2.
|
|
46
|
-
root rubocop-thread_safety 0.
|
|
45
|
+
root rubocop-rspec_rails 2.33.0 MIT
|
|
46
|
+
root rubocop-thread_safety 0.8.0 MIT
|
|
47
47
|
root ruby-progressbar 1.13.0 MIT
|
|
48
48
|
root securerandom 0.4.1 Ruby|BSD-2-Clause
|
|
49
|
-
root standard 1.
|
|
49
|
+
root standard 1.57.0
|
|
50
50
|
root standard-custom 1.0.2 MIT
|
|
51
51
|
root standard-performance 1.9.0 MIT
|
|
52
52
|
root standard-rails 1.6.0 MIT
|
|
53
|
-
root standard-rspec 0.5.
|
|
53
|
+
root standard-rspec 0.5.1 MIT
|
|
54
54
|
root thor 1.5.0 MIT
|
|
55
55
|
root timeout 0.6.1 Ruby|BSD-2-Clause
|
|
56
56
|
root tsort 0.2.0 Ruby|BSD-2-Clause
|
|
57
57
|
root tzinfo 2.0.6 MIT
|
|
58
|
-
root unicode-display_width 3.
|
|
59
|
-
root unicode-emoji 4.
|
|
60
|
-
root uri 1.1.
|
|
58
|
+
root unicode-display_width 3.3.0 MIT
|
|
59
|
+
root unicode-emoji 4.3.0 MIT
|
|
60
|
+
root uri 1.1.2 Ruby|BSD-2-Clause
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Cut patch release 0.3.7
|
|
2
|
+
|
|
3
|
+
## Participants
|
|
4
|
+
|
|
5
|
+
Andrei Makarov (@amkisko)
|
|
6
|
+
|
|
7
|
+
## Decisions
|
|
8
|
+
|
|
9
|
+
- Bump `PartitionGardener::VERSION` to `0.3.7`.
|
|
10
|
+
- Record user-visible correctness and contract fixes in `CHANGELOG.md` under `## 0.3.7 (2026-10-29)`.
|
|
11
|
+
|
|
12
|
+
## Effects
|
|
13
|
+
|
|
14
|
+
- Resume, hot-switch, gap detection, calendar leap day, retention notify, and integer-window JSON registry fixes landed on `patch/engineering-audit-0-3-7`.
|
|
15
|
+
- Release script now uses `git status --porcelain` and loads `PartitionGardener::VERSION` directly.
|
|
16
|
+
|
|
17
|
+
## Next
|
|
18
|
+
|
|
19
|
+
- Commit the prepared tree on `main`, tag `0.3.7`, push, and run `make release` (gem push, tag push, GitHub release). See `docs/reviews/20261029150855_engineering-audit-release-preparation.md`.
|
|
20
|
+
|
|
21
|
+
## Source
|
|
22
|
+
|
|
23
|
+
- Review: `docs/reviews/20261021220000_engineering-audit-0-3-7.md`
|
|
24
|
+
- `CHANGELOG.md`, `lib/partition_gardener/version.rb`
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Engineering audit and 0.3.7 fixes
|
|
2
|
+
|
|
3
|
+
## Participants
|
|
4
|
+
|
|
5
|
+
Andrei Makarov (@amkisko)
|
|
6
|
+
|
|
7
|
+
## Decisions
|
|
8
|
+
|
|
9
|
+
- Run a multi-model engineering audit on `partition_gardener` (correctness, tests, hot-path, contracts).
|
|
10
|
+
- Fact-check findings in source before shipping.
|
|
11
|
+
- Ship high-confidence correctness and contract fixes as patch `0.3.7`.
|
|
12
|
+
- Defer structural refactors (PlanApplier phase machine collapse, HotSwitchConcern split, strategy mixin extraction, default statement_timeout wrapper) to later work.
|
|
13
|
+
|
|
14
|
+
## Effects
|
|
15
|
+
|
|
16
|
+
- Confirmed and fixed: resume skips detach while phase is `detach`; hot-switch child rename skip on `p_` prefix; serial sequence default left on `_old`; unbounded `sync_delta_data` when stale column missing; `CREATE IF NOT EXISTS … PARTITION OF` no-op on detached name; lexicographic integer gap sort; leap-day `add_years`; retention notify says drop when table kept; JSON registry omits integer window keys.
|
|
17
|
+
- Unit specs: 151 examples, 0 failures (`bundle exec rspec` on non-integration paths from `spec/spec_paths.txt`).
|
|
18
|
+
- Integration resume examples extended for row-count preservation and started-but-not-detached phase; not run in this session (needs `INTEGRATION=1` and Postgres).
|
|
19
|
+
|
|
20
|
+
## Next
|
|
21
|
+
|
|
22
|
+
- Run `INTEGRATION=1` resume and hot-switch specs before cutting the gem.
|
|
23
|
+
- Run `ruby usr/bin/release.rb` when the working tree is clean and ready to push to RubyGems.
|
|
24
|
+
- Follow-up: session advisory lock default for long copies; statement_timeout wrapper; RFC 0002 status; dead `SCHEMA_PATH` wiring or removal.
|
|
25
|
+
|
|
26
|
+
## Source
|
|
27
|
+
|
|
28
|
+
- Audit agents: correctness, tests/coverage, hot-path/resource, org/contracts.
|
|
29
|
+
- Branch: `patch/engineering-audit-0-3-7`
|
|
30
|
+
- Related changelog: `docs/changelogs/20261021220000_cut-patch-release-0-3-7.md`
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Engineering audit and release preparation
|
|
2
|
+
|
|
3
|
+
## Decisions
|
|
4
|
+
|
|
5
|
+
- Continue on main while preserving the pre-existing uncommitted changes. Do not commit, tag, push, or publish.
|
|
6
|
+
- Retain release version 0.3.7. Reconcile root and appraisal PATH metadata without updating third-party dependency versions.
|
|
7
|
+
- Review correctness, executable coverage, workflow triggers and permissions, package boundaries, resource cost, and trace/identity boundaries. Re-audit the changed paths after corrections.
|
|
8
|
+
|
|
9
|
+
## Effects
|
|
10
|
+
|
|
11
|
+
- Correct the resume integration fixture's initial expectation: the source rows initially belong to the attached current partition, and the hot monthly partition exists after rebalance.
|
|
12
|
+
- Restore the required lint gate by combining adjacent boundary stubs. Verify all integration examples against an isolated PostgreSQL server, including the existing rebalance and hot-switch changes.
|
|
13
|
+
- Synchronize the own-gem PATH version in the root and appraisal lockfiles to 0.3.7, enable pull-request tests, and restrict workflow permissions to contents: read.
|
|
14
|
+
- Fix the release confirmation guard in usr/bin/release.rb so EOF refuses publication. An explicit Enter still selects the displayed default. The process-boundary regression test executes the release script with command execution replaced at the operating-system boundary; no publication command runs against a real service. The shared regression was observed first in Favorites: 2 examples, 1 failure before correction and 2 examples, 0 failures after correction. The release specs pass in every repository.
|
|
15
|
+
- Final serial JSON report: 188 examples, 0 failures with INTEGRATION=1 and PostgreSQL; no pending integration examples.
|
|
16
|
+
- Required project checks passed: DATABASE_URL=postgres://postgres@127.0.0.1:55432/partition_gardener_test make test. Environment: Ruby 4.0.2, BUNDLE_PATH=vendor/bundle; the installed native extensions require that Ruby build.
|
|
17
|
+
- Build the gem, verify it with Gem::Package#verify, and check that the changelog heading matches the gem version. Final package measurements are recorded in the session output; this gem includes the review note itself.
|
|
18
|
+
- Cold process measurement on macOS arm64 with /usr/bin/time -l and bundle exec ruby -Ilib: 0.35 CPU seconds, 0.7 elapsed seconds, 31817728 peak RSS bytes. Favorites loads Rails before the engine. Local smoke ceilings: 1 MiB compressed gem, 128 MiB RSS, 2 CPU seconds. All measured values fit those ceilings. CPU seconds are the energy proxy; electricity use was not measured.
|
|
19
|
+
- Parse every workflow YAML, verify lockfile own-gem versions, and run git diff --check. All passed. All six assigned repositories remain on main.
|
|
20
|
+
|
|
21
|
+
## Next
|
|
22
|
+
|
|
23
|
+
- No confirmed correction remains in the reviewed changes. Hosted GitHub Actions and the complete supported Ruby/framework matrix were not executed locally.
|
|
24
|
+
- Scope limits: production traffic, production database size and query plans, external service retention, and co-located resource interference were not measured. The local byte/RSS/CPU results do not establish those properties.
|
|
25
|
+
- Trace review follows local file/log and protocol emissions. No live customer identities or credentials were used. Data retention and deletion in embedding applications remain host contracts.
|
|
26
|
+
- Learned-system training/evaluation and cloud deployment modes do not apply to these library trees. Native memory-boundary mode applies to Polyrun's extension; browser presentation applies to Favorites; HTTP/identity and message companions apply only to repositories that expose those boundaries. Libraries have no independent production health endpoint to assess.
|
|
27
|
+
|
|
28
|
+
## Source
|
|
29
|
+
|
|
30
|
+
- AGENTS.md
|
|
31
|
+
- .agents/skills/engineering-audit/SKILL.md
|
|
32
|
+
- .agents/skills/changelog-update/SKILL.md
|
|
33
|
+
- CHANGELOG.md
|
|
34
|
+
- Makefile
|
|
35
|
+
- spec/README.md
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Engineering audit and release preparation
|
|
2
|
+
|
|
3
|
+
## Participants
|
|
4
|
+
|
|
5
|
+
Andrei Makarov (@amkisko)
|
|
6
|
+
|
|
7
|
+
## Decisions
|
|
8
|
+
|
|
9
|
+
- Continue on main while preserving uncommitted work. Do not commit, tag, push, or publish.
|
|
10
|
+
- Keep release version 0.3.7. Set the CHANGELOG heading date to 2026-10-29. RubyGems still lists 0.3.6 as the latest published version.
|
|
11
|
+
- Re-check correctness, executable coverage, workflow triggers and permissions, package boundaries, resource cost, and database-boundary behavior after the prior release-prep pass.
|
|
12
|
+
|
|
13
|
+
## Effects
|
|
14
|
+
|
|
15
|
+
- Enable `pull_request` on the test workflow, keep token permissions at `contents: read`, and cancel superseded runs.
|
|
16
|
+
- Set the `CHANGELOG.md` heading for 0.3.7 to 2026-10-29 and align the cut note under `docs/changelogs/`.
|
|
17
|
+
- Final serial JSON report: 188 examples, 0 failures with `INTEGRATION=1` and PostgreSQL on port 5432; no pending examples. Parallel polyrun with five workers: exit 0.
|
|
18
|
+
- Required project checks passed: rubocop (116 files, no offenses), rbs validate, `INTEGRATION=1` polyrun parallel-rspec. Environment: Ruby 4.0.2, `BUNDLE_PATH=vendor/bundle`.
|
|
19
|
+
- `release_spec`: 2 examples, 0 failures (EOF refuses gem push; Enter accepts the displayed default with system execution replaced).
|
|
20
|
+
- Build the gem, verify it with `Gem::Package#verify`, and confirm the changelog heading matches version 0.3.7. Compressed package: 117760 bytes; source payload: 495104 bytes across 104 packaged paths. These are compressed versus uncompressed sizes, not interchangeable quantities.
|
|
21
|
+
- Cold process measurement on macOS arm64 with `/usr/bin/time -p` and `bundle exec ruby -Ilib`: 0.17 CPU seconds, 0.26 elapsed seconds. Peak RSS via `/usr/bin/time -l` was unavailable in this session (`sysctl kern.clockrate` refused). Prior release-prep peak RSS on the same require path was 31817728 bytes. Local smoke ceilings: 1 MiB compressed gem, 128 MiB RSS, 2 CPU seconds. Measured gem size and CPU fit those ceilings. CPU seconds are the energy proxy; electricity use was not measured.
|
|
22
|
+
- Parse every workflow YAML, verify lockfile own-gem versions at 0.3.7, and run `git diff --check`. All passed.
|
|
23
|
+
|
|
24
|
+
## Next
|
|
25
|
+
|
|
26
|
+
- Commit, tag 0.3.7, push, and publish remain operator steps; this session did not run them. When the tree is clean, `make release` runs gem push, tag, and GitHub release.
|
|
27
|
+
- Hosted GitHub Actions and the complete supported Ruby/framework matrix were not executed locally beyond the default Gemfile path used by `make test`.
|
|
28
|
+
- Re-measure peak RSS with `/usr/bin/time -l` outside the restricted session if a fresh number is required before publish.
|
|
29
|
+
- Scope limits: production traffic, production database size and query plans, external service retention, and co-located resource interference were not measured. The local byte/CPU results do not establish those properties.
|
|
30
|
+
- Trace review follows local file/log and protocol emissions. No live customer identities or credentials were used. Data retention and deletion in embedding applications remain host contracts.
|
|
31
|
+
- Learned-system training/evaluation and cloud deployment modes do not apply to this library tree. Database-boundary mode applies to PostgreSQL maintenance DDL and checkpoint resume. The library has no independent production health endpoint to assess.
|
|
32
|
+
|
|
33
|
+
## Source
|
|
34
|
+
|
|
35
|
+
- AGENTS.md
|
|
36
|
+
- .agents/skills/engineering-audit/SKILL.md
|
|
37
|
+
- .agents/skills/changelog-update/SKILL.md
|
|
38
|
+
- CHANGELOG.md
|
|
39
|
+
- Makefile
|
|
40
|
+
- spec/README.md
|
|
41
|
+
- docs/reviews/20261028075216_engineering-audit-release-preparation.md
|
|
42
|
+
- docs/reviews/20261021220000_engineering-audit-0-3-7.md
|
|
43
|
+
- docs/changelogs/20261021220000_cut-patch-release-0-3-7.md
|
|
@@ -60,6 +60,22 @@
|
|
|
60
60
|
"minimum": 1,
|
|
61
61
|
"maximum": 10
|
|
62
62
|
},
|
|
63
|
+
"active_id_lo": {
|
|
64
|
+
"type": "integer",
|
|
65
|
+
"minimum": 0
|
|
66
|
+
},
|
|
67
|
+
"active_id_width": {
|
|
68
|
+
"type": "integer",
|
|
69
|
+
"minimum": 1
|
|
70
|
+
},
|
|
71
|
+
"current_band_size": {
|
|
72
|
+
"type": "integer",
|
|
73
|
+
"minimum": 1
|
|
74
|
+
},
|
|
75
|
+
"archive_band_size": {
|
|
76
|
+
"type": "integer",
|
|
77
|
+
"minimum": 1
|
|
78
|
+
},
|
|
63
79
|
"premake_months": {
|
|
64
80
|
"type": "integer",
|
|
65
81
|
"minimum": 0,
|
|
@@ -35,15 +35,18 @@ module PartitionGardener
|
|
|
35
35
|
next if bucket >= DateCalendar.beginning_of_month(cutoff)
|
|
36
36
|
|
|
37
37
|
if apply_retention
|
|
38
|
+
keep_table = @config.fetch(:retention_keep_table, false)
|
|
38
39
|
@executor.detach_partition(table_name, partition.name, concurrently: detach_concurrently?)
|
|
39
|
-
@executor.drop_table(partition.name) unless
|
|
40
|
+
@executor.drop_table(partition.name) unless keep_table
|
|
40
41
|
|
|
42
|
+
action = keep_table ? "Detached" : "Dropped"
|
|
41
43
|
PartitionGardener.configuration.notify(
|
|
42
|
-
"[PartitionGardener]
|
|
44
|
+
"[PartitionGardener] #{action} archive partition #{partition.name} (retention #{retention_months} months)",
|
|
43
45
|
context: {
|
|
44
46
|
table_name: table_name,
|
|
45
47
|
partition_name: partition.name,
|
|
46
48
|
retention_months: retention_months,
|
|
49
|
+
retention_keep_table: keep_table,
|
|
47
50
|
job: @job_class_name
|
|
48
51
|
}
|
|
49
52
|
)
|
|
@@ -51,8 +54,9 @@ module PartitionGardener
|
|
|
51
54
|
next
|
|
52
55
|
end
|
|
53
56
|
|
|
57
|
+
preview_action = @config.fetch(:retention_keep_table, false) ? "detach" : "drop"
|
|
54
58
|
PartitionGardener.configuration.notify(
|
|
55
|
-
"[PartitionGardener] Would
|
|
59
|
+
"[PartitionGardener] Would #{preview_action} archive partition #{partition.name} (bucket #{bucket}, retention #{retention_months} months)",
|
|
56
60
|
context: {
|
|
57
61
|
table_name: table_name,
|
|
58
62
|
partition_name: partition.name,
|
|
@@ -52,14 +52,11 @@ module PartitionGardener
|
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
def add_years(date, count)
|
|
55
|
-
|
|
56
|
-
Date.new(value.year + count, value.month, value.day)
|
|
55
|
+
to_date(date) >> (count * 12)
|
|
57
56
|
end
|
|
58
57
|
|
|
59
58
|
def add_months(date, count)
|
|
60
|
-
|
|
61
|
-
month_index = (base.year * 12) + (base.month - 1) + count
|
|
62
|
-
Date.new(month_index / 12, (month_index % 12) + 1, 1)
|
|
59
|
+
beginning_of_month(date) >> count
|
|
63
60
|
end
|
|
64
61
|
end
|
|
65
62
|
end
|
|
@@ -20,7 +20,7 @@ module PartitionGardener
|
|
|
20
20
|
segments = strategy.attached_tail_segments
|
|
21
21
|
return [] if segments.empty?
|
|
22
22
|
|
|
23
|
-
sorted = segments.sort_by { |segment| [segment.range_start
|
|
23
|
+
sorted = segments.sort_by { |segment| [segment.range_start, segment.name] }
|
|
24
24
|
gaps = []
|
|
25
25
|
|
|
26
26
|
sorted.each_cons(2) do |left, right|
|
|
@@ -175,11 +175,13 @@ module PartitionGardener
|
|
|
175
175
|
conflict_match = conflict_key.map { |column| "d.#{connection.quote_column_name(column)} = s.#{connection.quote_column_name(column)}" }.join(" AND ")
|
|
176
176
|
update_columns = insert_columns - conflict_key
|
|
177
177
|
update_clause = update_columns.map { |column| "#{connection.quote_column_name(column)} = EXCLUDED.#{connection.quote_column_name(column)}" }.join(", ")
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
178
|
+
unless current_columns.include?(stale_column) && partitioned_columns.include?(stale_column)
|
|
179
|
+
raise ArgumentError,
|
|
180
|
+
"sync_stale_column #{stale_column.inspect} is missing from source or target; " \
|
|
181
|
+
"refusing unbounded delta sync"
|
|
182
182
|
end
|
|
183
|
+
|
|
184
|
+
stale_check = "d.#{connection.quote_column_name(stale_column)} >= s.#{connection.quote_column_name(stale_column)}"
|
|
183
185
|
window_predicate = [
|
|
184
186
|
"#{current_partition_key_expression} >= #{start_date_sql}",
|
|
185
187
|
"AND #{current_partition_key_expression} < #{end_date_sql}",
|
|
@@ -209,16 +211,11 @@ module PartitionGardener
|
|
|
209
211
|
<<~SQL.chomp
|
|
210
212
|
ON CONFLICT (#{conflict_key_str}) DO NOTHING
|
|
211
213
|
SQL
|
|
212
|
-
elsif partitioned_columns.include?(stale_column)
|
|
213
|
-
<<~SQL.chomp
|
|
214
|
-
ON CONFLICT (#{conflict_key_str}) DO UPDATE SET
|
|
215
|
-
#{update_clause}
|
|
216
|
-
WHERE #{quoted_table(target_table)}.#{connection.quote_column_name(stale_column)} < EXCLUDED.#{connection.quote_column_name(stale_column)}
|
|
217
|
-
SQL
|
|
218
214
|
else
|
|
219
215
|
<<~SQL.chomp
|
|
220
216
|
ON CONFLICT (#{conflict_key_str}) DO UPDATE SET
|
|
221
217
|
#{update_clause}
|
|
218
|
+
WHERE #{quoted_table(target_table)}.#{connection.quote_column_name(stale_column)} < EXCLUDED.#{connection.quote_column_name(stale_column)}
|
|
222
219
|
SQL
|
|
223
220
|
end
|
|
224
221
|
|
|
@@ -406,17 +403,29 @@ module PartitionGardener
|
|
|
406
403
|
def rename_partition_children!(parent_table, from_prefix, to_prefix)
|
|
407
404
|
fetch_partitions(parent_table).each do |partition|
|
|
408
405
|
partition_name = partition.to_s
|
|
409
|
-
|
|
406
|
+
bare_name = partition_name.delete('"').split(".").last
|
|
407
|
+
next unless bare_name.include?(from_prefix)
|
|
410
408
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
409
|
+
new_bare_name = bare_name.sub(from_prefix, to_prefix)
|
|
410
|
+
next if new_bare_name == bare_name
|
|
411
|
+
|
|
412
|
+
run_sql "ALTER TABLE #{partition} RENAME TO #{connection.quote_column_name(new_bare_name)}"
|
|
413
|
+
say "Renamed #{partition} to #{new_bare_name}"
|
|
414
414
|
end
|
|
415
415
|
end
|
|
416
416
|
|
|
417
417
|
def repoint_serial_sequences!(table_name, sequence_pairs)
|
|
418
418
|
sequence_pairs.each do |column_name, sequence_name|
|
|
419
|
-
|
|
419
|
+
quoted_column = connection.quote_column_name(column_name)
|
|
420
|
+
live_table = quoted_table(table_name)
|
|
421
|
+
run_sql <<~SQL
|
|
422
|
+
ALTER TABLE #{live_table}
|
|
423
|
+
ALTER COLUMN #{quoted_column}
|
|
424
|
+
SET DEFAULT nextval(#{connection.quote(sequence_name)}::regclass)
|
|
425
|
+
SQL
|
|
426
|
+
run_sql "ALTER SEQUENCE #{sequence_name} OWNED BY #{live_table}.#{quoted_column}"
|
|
427
|
+
max_id = run_sql("SELECT COALESCE(MAX(#{quoted_column}), 1) AS max_id FROM #{live_table}").first["max_id"].to_i
|
|
428
|
+
run_sql "SELECT setval(#{connection.quote(sequence_name)}, #{[max_id, 1].max})"
|
|
420
429
|
say "Repointed #{sequence_name} to #{table_name}.#{column_name}"
|
|
421
430
|
end
|
|
422
431
|
end
|
|
@@ -139,8 +139,10 @@ module PartitionGardener
|
|
|
139
139
|
end
|
|
140
140
|
|
|
141
141
|
def apply_resuming!(plan, operations, staging_name, record)
|
|
142
|
-
|
|
143
|
-
|
|
142
|
+
# RunRecord.start persists phase "detach" before work. Treat detach as done only once
|
|
143
|
+
# the record has advanced to "segments" or later.
|
|
144
|
+
unless record.phase_at_least?("segments")
|
|
145
|
+
prepare_staging!(staging_name) unless Connection.partition_exists?(staging_name)
|
|
144
146
|
detach_managed_tail_partitions!(staging_name, skip_names: keep_names_from(operations))
|
|
145
147
|
drain_default_tail_rows_into_staging!(staging_name)
|
|
146
148
|
record = record.advance!("detach", staging_row_count: staging_row_count(staging_name))
|
|
@@ -185,11 +187,21 @@ module PartitionGardener
|
|
|
185
187
|
segments.each do |segment|
|
|
186
188
|
next if Connection.partition_attached?(table_name, segment.name)
|
|
187
189
|
|
|
190
|
+
if Connection.partition_exists?(segment.name)
|
|
191
|
+
raise StandardError,
|
|
192
|
+
"partition #{segment.name} exists but is not attached to #{table_name}; " \
|
|
193
|
+
"CREATE TABLE IF NOT EXISTS … PARTITION OF would no-op and leave rows stranded"
|
|
194
|
+
end
|
|
195
|
+
|
|
188
196
|
@executor.create_partition(
|
|
189
197
|
table_name,
|
|
190
198
|
segment.name,
|
|
191
199
|
segment.for_values_clause(strategy)
|
|
192
200
|
)
|
|
201
|
+
|
|
202
|
+
unless Connection.partition_attached?(table_name, segment.name)
|
|
203
|
+
raise StandardError, "failed to attach planned partition #{segment.name} to #{table_name}"
|
|
204
|
+
end
|
|
193
205
|
end
|
|
194
206
|
end
|
|
195
207
|
|
|
@@ -205,8 +217,10 @@ module PartitionGardener
|
|
|
205
217
|
operations.select { |operation| operation.action == :drop }.each do |operation|
|
|
206
218
|
partition_name = operation.attached_segment.name
|
|
207
219
|
next if Connection.partition_attached?(@config[:table_name], partition_name)
|
|
220
|
+
next unless Connection.partition_exists?(partition_name)
|
|
221
|
+
next unless Connection.count_rows_in_partition_table(partition_name).zero?
|
|
208
222
|
|
|
209
|
-
@executor.drop_table(partition_name)
|
|
223
|
+
@executor.drop_table(partition_name)
|
|
210
224
|
end
|
|
211
225
|
end
|
|
212
226
|
|
data/sig/partition_gardener.rbs
CHANGED
|
@@ -3,10 +3,10 @@ module PartitionGardener
|
|
|
3
3
|
|
|
4
4
|
MOVE_BATCH_SIZE: Integer
|
|
5
5
|
FUTURE_MONTH_PARTITION_ROW_THRESHOLD: Integer
|
|
6
|
-
DEFAULT_STATEMENT_TIMEOUT:
|
|
6
|
+
DEFAULT_STATEMENT_TIMEOUT: Integer
|
|
7
7
|
|
|
8
8
|
def self.run!: (
|
|
9
|
-
?statement_timeout:
|
|
9
|
+
?statement_timeout: Integer | Numeric,
|
|
10
10
|
?job_class_name: String,
|
|
11
11
|
?continue_on_error: bool,
|
|
12
12
|
?dry_run: bool,
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: partition_gardener
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrei Makarov
|
|
@@ -278,6 +278,7 @@ files:
|
|
|
278
278
|
- docs/changelogs/20260916144600_public-identifiers-contract.md
|
|
279
279
|
- docs/changelogs/20261005135700_checkpoint-table-name.md
|
|
280
280
|
- docs/changelogs/20261005141621_cut-patch-release-0-3-6.md
|
|
281
|
+
- docs/changelogs/20261021220000_cut-patch-release-0-3-7.md
|
|
281
282
|
- docs/cli.md
|
|
282
283
|
- docs/configuration.md
|
|
283
284
|
- docs/cutover.md
|
|
@@ -296,6 +297,9 @@ files:
|
|
|
296
297
|
- docs/pg_party_recipe.md
|
|
297
298
|
- docs/related_postgres_tooling.md
|
|
298
299
|
- docs/retention.md
|
|
300
|
+
- docs/reviews/20261021220000_engineering-audit-0-3-7.md
|
|
301
|
+
- docs/reviews/20261028075216_engineering-audit-release-preparation.md
|
|
302
|
+
- docs/reviews/20261029150855_engineering-audit-release-preparation.md
|
|
299
303
|
- docs/schemas/partition_garden.schema.json
|
|
300
304
|
- docs/schemas/plan_report.schema.json
|
|
301
305
|
- docs/tooling_split.md
|