Skip to content

Commit 1e2aca1

Browse files
committed
feat: unify workload orchestration and profiles
1 parent 429d207 commit 1e2aca1

24 files changed

Lines changed: 437 additions & 95 deletions

‎.github/workflows/publish.yml‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ concurrency:
1515
jobs:
1616
build:
1717
name: Test and build distributions
18-
runs-on: ubuntu-latest
18+
runs-on: ubuntu-24.04
1919
timeout-minutes: 45
2020

2121
steps:
@@ -28,7 +28,7 @@ jobs:
2828
python-version: "3.10"
2929

3030
- name: Install test and build dependencies
31-
run: python -m pip install --upgrade pip ".[dev]"
31+
run: python -m pip install --upgrade 'pip>=24.3,<26' ".[dev]"
3232

3333
- name: Verify tag matches package version
3434
shell: bash
@@ -91,7 +91,7 @@ jobs:
9191
publish:
9292
name: Publish distributions to PyPI
9393
needs: build
94-
runs-on: ubuntu-latest
94+
runs-on: ubuntu-24.04
9595
timeout-minutes: 15
9696
environment:
9797
name: pypi

‎.github/workflows/tests.yml‎

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,21 @@ name: tests
22

33
on:
44
push:
5+
branches:
6+
- "**"
57
pull_request:
8+
workflow_dispatch:
69

710
permissions:
811
contents: read
912

13+
concurrency:
14+
group: tests-${{ github.workflow }}-${{ github.ref }}
15+
cancel-in-progress: true
16+
1017
jobs:
1118
unit:
12-
runs-on: ubuntu-latest
19+
runs-on: ubuntu-24.04
1320
strategy:
1421
matrix:
1522
python-version: ["3.10", "3.12"]
@@ -24,14 +31,14 @@ jobs:
2431
- run: pytest -q
2532

2633
package:
27-
runs-on: ubuntu-latest
34+
runs-on: ubuntu-24.04
2835
needs: unit
2936
steps:
3037
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
3138
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
3239
with:
3340
python-version: "3.12"
34-
- run: python -m pip install build twine
41+
- run: python -m pip install 'build>=1.2,<2' 'twine>=6,<7'
3542
- run: python -m build
3643
- run: python -m twine check dist/*
3744
- name: Smoke-test installed wheel and bundled profiles
@@ -50,7 +57,7 @@ jobs:
5057
'import os; from pathlib import Path; root = Path(os.environ["RUNNER_TEMP"]) / "pg-workload-project/data"; assert len(list(root.glob("*/profile.yml"))) >= 7; assert not any(root.rglob("*.csv")); assert not any(root.rglob("*.tar.gz"))'
5158
5259
postgres-18:
53-
runs-on: ubuntu-latest
60+
runs-on: ubuntu-24.04
5461
needs: unit
5562
timeout-minutes: 30
5663
steps:

‎README.md‎

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,8 @@ instead of `--passfile`; `PGPASSWORD` is accepted only as an admin-password comp
133133

134134
`prepare-db --recreate` drops only the selected workload database after terminating its sessions;
135135
it does not drop the workload role.
136+
Unless `--extensions` is supplied, it creates both `pg_stat_statements` and
137+
`pg_buffercache`, matching the default pg_diag metric set used by pg_play.
136138

137139
## Targets
138140

@@ -167,9 +169,9 @@ Use `--bin-dir /path/to/postgresql/bin` when client binaries are outside
167169
| `simple_stock_spec_symbols` | Identifier, Unicode, and parser edge cases | synthetic Python generator |
168170
| `imdb` | Movie-domain analytical joins and skew | synthetic Python generator |
169171
| `pagila` | Mixed Pagila OLTP | synthetic Python generator |
170-
| `many_objects` | Metadata-heavy schemas and partitions | SQL object creation only |
172+
| `many_objects` | Metadata-heavy schemas and partitions | scale-aware SQL object creation |
171173
| `emulate_errors` | Intentional SQL errors | SQL seed rows only |
172-
| `pss_overflow` | `pg_stat_statements` churn | SQL object creation; extension/preload required |
174+
| `pss_overflow` | `pg_stat_statements` churn | scaled Python generator; extension/preload required |
173175

174176
No profile requires a dump, CSV file, or network download. The Pagila schema is redistributed
175177
under its upstream license; see `THIRD_PARTY_NOTICES.md`. Its rows are generated locally. The
@@ -284,8 +286,10 @@ For example, `--scale 2` approximately doubles scalable tables. Very small value
284286
profile-specific minimum so foreign-key structure and query selectivity remain meaningful in CI.
285287
Generators use deterministic seeds, preserve foreign-key relationships, and introduce skewed
286288
random distributions for hot entities, popularity, money, ratings, inventory, and customers.
287-
Profiles that model object count or failures (`many_objects`, `pss_overflow`, `emulate_errors`)
288-
have no Python generator and ignore `--scale` during preparation.
289+
`many_objects` scales its committed schema batches with `scale_repeat`; this
290+
also bounds DDL locks on small PostgreSQL stands. `pss_overflow` scales its
291+
generated table count (with a 50-table minimum). `emulate_errors` alone ignores
292+
`--scale` because it keeps a fixed, tiny seed.
289293

290294
Installing a generated profile recreates its schema before filling it, so changing `--scale` and
291295
installing again produces a clean dataset rather than appending rows. Scheduler recovery reuses
@@ -315,6 +319,11 @@ pg-workload scheduler \
315319
--run-immediately
316320
```
317321

322+
`scheduler`, `start`, and their reviewed `plan --operation=scheduler` accept
323+
`--job-interval-seconds N` to apply one bounded interval to every selected
324+
profile. This is intended for short orchestrated observation windows; normal
325+
long-running installations should keep profile-specific intervals.
326+
318327
Changes made by `enable`, `disable`, and `set-interval` are picked up without restarting the
319328
scheduler:
320329

‎docs/pg_play-integration.md‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,15 @@ pg-workload --machine --request-id workload-003 plan \
1212
--root workload --operation=install --profile pagila
1313
```
1414

15+
The capability document uses `pg_play/capabilities/v1`. Every command declares
16+
the common boolean fields `mutates_target`, `machine_output`, and
17+
`accepts_plan_hash`.
18+
Its `machine_interface` object records the canonical machine, request-id, and
19+
capability option names.
20+
1521
`plan` hashes every selected profile file, selected jobs, scale, connection
1622
metadata without secrets, requested database changes, and scheduler state.
17-
Mutating `prepare-db`, `install`, `run`, `scheduler`, and `start` operations can
23+
Mutating `prepare-db`, `install`, `run`, and `start` operations can
1824
be guarded with the returned component plan hash.
1925

2026
`start`, `status`, and `stop` provide a bounded background lifecycle useful to
@@ -25,6 +31,10 @@ For an orchestrated run, the hidden `--enable-selected` option is supplied to
2531
both `plan --operation=scheduler` and `start`. The plan hashes the prospective
2632
desired state; `start` verifies that hash, writes all selected enables under
2733
one state lock, and only then launches the scheduler.
34+
`--job-interval-seconds` is included in that same desired state, so a reviewed
35+
short diagnostic cadence cannot drift between plan and execution. Database
36+
preparation creates the default pg_diag extensions (`pg_stat_statements` and
37+
`pg_buffercache`) unless the caller explicitly supplies another extension set.
2838

2939
Passwords are accepted only through the documented environment/passfile
3040
mechanisms. They are never accepted in the machine envelope or emitted to

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "pg-workload"
7-
version = "0.2.0"
7+
version = "0.3.0"
88
description = "PostgreSQL backend activity emulator for diverse plans, logs, and runtime statistics."
99
readme = "README.md"
1010
requires-python = ">=3.10"

‎src/pg_workload/__init__.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""PostgreSQL workload generation and scheduling tools."""
22

3-
__version__ = "0.2.0"
3+
__version__ = "0.3.0"
44

55
from pg_workload.assets import bundled_profiles_root, initialize_project
66
from pg_workload.cli import build_parser

‎src/pg_workload/bundled/profiles/imdb/generator.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,28 +71,28 @@ def main() -> None:
7171
7272
INSERT INTO cast_info (title_id, person_id, role_type, billing_order)
7373
SELECT
74-
1 + ((g * 104729 - 1) % {titles}),
74+
1 + ((g::bigint * 104729 - 1) % {titles}),
7575
1 + floor(power(random(), 1.8) * {people})::bigint,
7676
(ARRAY['actor', 'actress', 'director', 'writer', 'producer'])[1 + (g * 3) % 5],
7777
1 + (g % 20)
7878
FROM generate_series(1, {cast_rows}) AS g;
7979
8080
INSERT INTO movie_keyword (title_id, keyword_id)
8181
SELECT
82-
1 + ((g * 65537 - 1) % {titles}),
82+
1 + ((g::bigint * 65537 - 1) % {titles}),
8383
1 + floor(power(random(), 2.3) * {keywords})::bigint
8484
FROM generate_series(1, {keyword_rows}) AS g;
8585
8686
INSERT INTO movie_company (title_id, company_id, company_type)
8787
SELECT
88-
1 + ((g * 32771 - 1) % {titles}),
88+
1 + ((g::bigint * 32771 - 1) % {titles}),
8989
1 + floor(power(random(), 2.0) * {companies})::bigint,
9090
(ARRAY['production', 'distributor', 'effects', 'post-production'])[1 + (g % 4)]
9191
FROM generate_series(1, {company_rows}) AS g;
9292
9393
INSERT INTO movie_info (title_id, info_type, info)
9494
SELECT
95-
1 + ((g * 8191 - 1) % {titles}),
95+
1 + ((g::bigint * 8191 - 1) % {titles}),
9696
(ARRAY['budget', 'gross', 'language', 'location'])[1 + (g % 4)],
9797
CASE g % 4
9898
WHEN 0 THEN '$' || (100000 + (g::bigint * 7919) % 200000000)

‎src/pg_workload/bundled/profiles/many_objects/profile.yml‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ prepare:
99
path: sql/prepare_schema_01.sql
1010
- type: sql
1111
path: sql/prepare_schema_02.sql
12-
repeat: 5
12+
repeat: 25
13+
scale_repeat: true
1314
jobs:
1415
- name: metadata_scan
1516
type: pgbench

‎src/pg_workload/bundled/profiles/many_objects/sql/prepare_schema_02.sql‎

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ BEGIN
1515
FROM pg_namespace
1616
WHERE nspname LIKE 'many_objects_%';
1717

18-
FOR i IN (last_schema_index + 1)..(last_schema_index + 5) LOOP
18+
-- One schema per invocation keeps DDL locks bounded. The profile runner
19+
-- scales the number of committed invocations with --scale.
20+
FOR i IN (last_schema_index + 1)..(last_schema_index + 1) LOOP
1921
schema_name := 'many_objects_' || i;
2022

2123
-- Create the schema
@@ -55,12 +57,35 @@ BEGIN
5557
END LOOP;
5658
END LOOP;
5759

58-
-- Create indexes on the partitioned table
59-
EXECUTE format('
60-
CREATE INDEX ON %I.%I (created_at);
61-
CREATE INDEX ON %I.%I (data);
62-
', schema_name, table_name, schema_name, table_name);
60+
-- PostgreSQL 11+ propagates parent indexes to every leaf partition.
61+
-- PostgreSQL 10 rejects CREATE INDEX on a partitioned table; leaf
62+
-- indexes are created below, after this DO block commits its DDL.
63+
IF current_setting('server_version_num')::integer >= 110000 THEN
64+
EXECUTE format('
65+
CREATE INDEX ON %I.%I (created_at);
66+
CREATE INDEX ON %I.%I (data);
67+
', schema_name, table_name, schema_name, table_name);
68+
END IF;
6369

6470
RAISE NOTICE 'Created schema % and partitioned table with partitions and subpartitions.', schema_name;
6571
END LOOP;
6672
END $$;
73+
74+
-- psql \gexec runs every generated row as a separately committed command.
75+
-- This keeps the PostgreSQL 10 lock footprint bounded while preserving the
76+
-- indexed metadata shape used by the profile on newer releases.
77+
SELECT format(
78+
'CREATE INDEX ON %I.%I (created_at); CREATE INDEX ON %I.%I (data);',
79+
n.nspname,
80+
c.relname,
81+
n.nspname,
82+
c.relname
83+
)
84+
FROM pg_catalog.pg_class c
85+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
86+
WHERE current_setting('server_version_num')::integer < 110000
87+
AND n.nspname LIKE 'many_objects_%'
88+
AND c.relkind = 'r'
89+
AND c.relname LIKE 'partitioned_table_partition_%_subpartition_%'
90+
ORDER BY n.nspname, c.relname
91+
\gexec

0 commit comments

Comments
 (0)