Skip to content

Optimizations, before and after

All three designs went through one optimization pass. This page shows two sets of Docker Postgres 18 runs side by side. The first set is from before the pass (untagged runs), and the second set is from after the pass (tagged opt). The machine, the configurations and the pgbench settings are the same, with one run for each. The results before and after are identical, because every per-policy checksum matches. Only the cost changed.

The design colors are the same as everywhere else: A tuple core, B policy first, C compiled model. In the charts a hollow dot is before and a filled dot is after.

The optimizations report is the written analysis. It gives the claim of each optimization and shows how the claim held up. The Postgres versions page shows the other Postgres versions. The dashboard shows the rest of the numbers.

The code computes these takeaways from the data below.

  • Biggest single win: B nesting write: 107 to 0.62 ms (170x faster). Per design, the largest gain on the key workloads is A sharing page, wide teams: 125 to 14.4 ms (8.6x faster); B nesting write: 107 to 0.62 ms (170x faster); C nesting write: 300 to 57.7 ms (5.2x faster).
  • Who gained how much: A is more than 20% faster on 4 of 5 key workloads; B is more than 20% faster on 5 of 5 key workloads; C is more than 20% faster on 4 of 5 key workloads.
  • RBAC did not move: 14 of 15 RBAC measurements are within 20% of their old values.
  • Ranking: 6 of 11 workload classes changed order or tie pattern. The slowest design is unchanged on Parents, projection page; Parents, single-branch page; Nesting write; Seed time: parent links; Seed time: wide sharing.
  • What is still expensive: nesting a team still costs 57.7 ms on C against 0.15 ms on A and 0.62 ms on B.

Key workloads, before and after

Before: PG 18 (docker, untagged); after: PG 18 (docker, optimized) (Postgres 18.6). p50 in milliseconds on a log axis, lower is better. The sharing reads use the medium wide shape, the others the large default (1M documents).

  • A Tuple core
  • B Policy first
  • C Compiled model
  • D Per-model tables
  • E Cedar
  • before
  • after
0.10.1111010100100ms, before to afterSharing page, wide teamsshare_select_pagemedium wideA125 → 14.48.6x fasterB36.9 → 17.82.1x fasterC10.3 → 8.201.3x fasterSharing id list, wide teamsshare_list_objectsmedium wideA29.1 → 4.446.5x fasterB11.4 → 8.411.4x fasterC4.25 → 3.411.2x fasterParents, single-branch pageparents_select_pagelarge defaultA240 → 45.65.3x fasterB102 → 13.77.5x fasterC9.26 → 4.941.9x fasterNesting writewrite_nest_teamlarge defaultA0.15 → 0.15within 20%B107 → 0.62170x fasterC300 → 57.75.2x fasterCheck through a nested teamnest_checklarge defaultA0.52 → 0.311.7x fasterB1.14 → 0.323.6x fasterC0.30 → 0.30within 20%

Takeaway. Largest changes: B nesting write: 107 to 0.62 ms (170x faster); A sharing page, wide teams: 125 to 14.4 ms (8.6x faster); B parents, single-branch page: 102 to 13.7 ms (7.5x faster); A sharing id list, wide teams: 29.1 to 4.44 ms (6.5x faster). Not moved (within 20%): A nesting write (0.15 to 0.15 ms), C check through a nested team (0.30 to 0.30 ms).

Table view
WorkloadConfigurationDesignBefore p50 msAfter p50 msChange
Sharing: first pagemedium wideA12514.48.6x faster
Sharing: first pagemedium wideB36.917.82.1x faster
Sharing: first pagemedium wideC10.38.201.3x faster
Sharing: list visible idsmedium wideA29.14.446.5x faster
Sharing: list visible idsmedium wideB11.48.411.4x faster
Sharing: list visible idsmedium wideC4.253.411.2x faster
Parents, single branch: first pagelarge defaultA24045.65.3x faster
Parents, single branch: first pagelarge defaultB10213.77.5x faster
Parents, single branch: first pagelarge defaultC9.264.941.9x faster
Nesting: nest a teamlarge defaultA0.150.15within 20%
Nesting: nest a teamlarge defaultB1070.62170x faster
Nesting: nest a teamlarge defaultC30057.75.2x faster
Nesting: check through a nested teamlarge defaultA0.520.311.7x faster
Nesting: check through a nested teamlarge defaultB1.140.323.6x faster
Nesting: check through a nested teamlarge defaultC0.300.30within 20%

Raw report: per-run reports (pg18-docker-*, pg18-docker-*-opt)

RBAC workloads, before and after

Control group, Before: PG 18 (docker, untagged); after: PG 18 (docker, optimized) (Postgres 18.6). Large default. p50 in milliseconds on a log axis; the dots overlap when nothing changed.

  • A Tuple core
  • B Policy first
  • C Compiled model
  • D Per-model tables
  • E Cedar
  • before
  • after
0.10.10.20.20.50.511ms, before to afterRBAC: single checkrbac_checklarge defaultA0.26 → 0.30within 20%B0.26 → 0.27within 20%C0.25 → 0.24within 20%RBAC: first pagerbac_select_pagelarge defaultA0.76 → 0.76within 20%B0.77 → 0.73within 20%C0.73 → 0.67within 20%RBAC: count visiblerbac_select_countlarge defaultA0.85 → 0.78within 20%B0.85 → 0.78within 20%C0.82 → 0.73within 20%RBAC: update one rowrbac_update_onelarge defaultA0.53 → 0.47within 20%B0.52 → 0.47within 20%C0.45 → 0.361.3x fasterRBAC: assign a rolewrite_assign_rolelarge defaultA0.16 → 0.15within 20%B0.14 → 0.14within 20%C0.18 → 0.17within 20%

Takeaway. 14 of 15 RBAC measurements (5 workloads, 3 designs) are within 20% of their old value; 1 moved by more than that. The largest relative change is C rbac_update_one: 0.45 to 0.36 ms. 2 of 15 got slower, by at most 0.04 ms; the optimizations target sharing, parents and nesting, so RBAC is the control group.

Table view
WorkloadDesignBefore p50 msAfter p50 msChange
rbac_check (large default)A0.260.30within 20%
rbac_check (large default)B0.260.27within 20%
rbac_check (large default)C0.250.24within 20%
rbac_select_page (large default)A0.760.76within 20%
rbac_select_page (large default)B0.770.73within 20%
rbac_select_page (large default)C0.730.67within 20%
rbac_select_count (large default)A0.850.78within 20%
rbac_select_count (large default)B0.850.78within 20%
rbac_select_count (large default)C0.820.73within 20%
rbac_update_one (large default)A0.530.47within 20%
rbac_update_one (large default)B0.520.47within 20%
rbac_update_one (large default)C0.450.361.3x faster
write_assign_role (large default)A0.160.15within 20%
write_assign_role (large default)B0.140.14within 20%
write_assign_role (large default)C0.180.17within 20%

Raw report: per-run reports

Seed time and storage, before and after

Before: PG 18 (docker, untagged); after: PG 18 (docker, optimized) (Postgres 18.6). Top: seconds to load a phase. Bottom: cumulative size of the permissions tables (MB) after that phase. Log axes, lower is better.

  • A Tuple core
  • B Policy first
  • C Compiled model
  • D Per-model tables
  • E Cedar
  • before
  • after

Seed time

1010100100s, before to afterSeed time: parent linksseed seconds: parentslarge defaultA27.3 → 31.0within 20%B121 → 44.82.7x fasterC17.1 → 17.6within 20%Seed time: wide sharingseed seconds: sharingmedium wideA7.75 → 5.881.3x fasterB26.1 → 12.62.1x fasterC176 → 24.77.1x faster

Storage

50501001002002005005001k1kMB, before to afterStorage: parent linkssize after phase: parentslarge defaultA398 → 404within 20%B763 → 3782.0x fasterC182 → 182within 20%Storage: wide sharingsize after phase: sharingmedium wideA97.1 → 97.4within 20%B226 → 1052.1x fasterC192 → 192within 20%

Takeaway. Seeding: C seed time: wide sharing: 176 to 24.7 s (7.1x faster); B seed time: parent links: 121 to 44.8 s (2.7x faster); B seed time: wide sharing: 26.1 to 12.6 s (2.1x faster). Storage: B storage: wide sharing: 226 to 105 MB (2.1x faster); B storage: parent links: 763 to 378 MB (2.0x faster). A storage: parent links grew 398 to 404 MB, A storage: wide sharing grew 97.1 to 97.4 MB (a new index).

Table view
MeasureConfigurationDesignBeforeAfterChange
Seed: per-document parent linkslarge defaultA27.3 s31.0 swithin 20%
Seed: per-document parent linkslarge defaultB121 s44.8 s2.7x faster
Seed: per-document parent linkslarge defaultC17.1 s17.6 swithin 20%
Seed: sharingmedium wideA7.75 s5.88 s1.3x faster
Seed: sharingmedium wideB26.1 s12.6 s2.1x faster
Seed: sharingmedium wideC176 s24.7 s7.1x faster
Storage after the parents seedlarge defaultA398 MB404 MBwithin 20%
Storage after the parents seedlarge defaultB763 MB378 MB2.0x faster
Storage after the parents seedlarge defaultC182 MB182 MBwithin 20%
Storage after the sharing seedmedium wideA97.1 MB97.4 MBwithin 20%
Storage after the sharing seedmedium wideB226 MB105 MB2.1x faster
Storage after the sharing seedmedium wideC192 MB192 MBwithin 20%

Raw report: per-run reports (Storage and seed cost)

Ranking, before and after

PG 18 (docker) against PG 18 (docker, optimized). Best first. Designs within 20% of the best in a group are tied (=); "<" means the next group is slower.

Workload classBeforeAfterChanged
RBAC, first page
large default
A=B=C
0.76 / 0.77 / 0.73 ms
A=B=C
0.76 / 0.73 / 0.67 ms
same
Sharing page, default teams
large default
B=C<A
1.45 / 1.16 / 1.00 ms
A=B=C
1.03 / 1.00 / 0.93 ms
changed
Sharing page, wide teams
medium wide
C<B<A
125 / 36.9 / 10.3 ms
C<A<B
14.4 / 17.8 / 8.20 ms
changed
Sharing id list, wide teams
medium wide
C<B<A
29.1 / 11.4 / 4.25 ms
C<A<B
4.44 / 8.41 / 3.41 ms
changed
Parents, projection page
large default
C<B<A
2.09 / 1.30 / 1.05 ms
B=C<A
1.33 / 1.15 / 1.05 ms
changed
Parents, single-branch page
large default
C<B<A
240 / 102 / 9.26 ms
C<B<A
45.6 / 13.7 / 4.94 ms
same
Nesting write
large default
A<B<C
0.15 / 107 / 300 ms
A<B<C
0.15 / 0.62 / 57.7 ms
same
Check through a nested team
large default
C<A<B
0.52 / 1.14 / 0.30 ms
A=B=C
0.31 / 0.32 / 0.30 ms
changed
Storage: parent links
large default
C<A<B
398 / 763 / 182 MB
C<A=B
404 / 378 / 182 MB
changed
Seed time: parent links
large default
C<A<B
27.3 / 121 / 17.1 s
C<A<B
31.0 / 44.8 / 17.6 s
same
Seed time: wide sharing
medium wide
A<B<C
7.75 / 26.1 / 176 s
A<B<C
5.88 / 12.6 / 24.7 s
same

Values under each ranking are A / B / C.

Takeaway. 6 of 11 workload classes changed order or tie pattern. Changed: Sharing page, default teams; Sharing page, wide teams; Sharing id list, wide teams; Parents, projection page; Check through a nested team; Storage: parent links. Now a tie between all three designs: Sharing page, default teams; Check through a nested team.

The same speedups on every Postgres version?

Speedup = before p50 / after p50, Docker, key workloads. Page main version is 18. Below 1.0x means slower after.

WorkloadDesignPG 16PG 17PG 18
Sharing page, wide teams
medium wide
A6.7x
98.6 to 14.7 ms
5.1x
104 to 20.2 ms
8.6x
125 to 14.4 ms
Sharing page, wide teams
medium wide
B2.8x
33.6 to 11.9 ms
1.8x
33.8 to 18.9 ms
2.1x
36.9 to 17.8 ms
Sharing page, wide teams
medium wide
C1.1x
9.40 to 8.89 ms
1.2x
9.59 to 8.27 ms
1.3x
10.3 to 8.20 ms
Sharing id list, wide teams
medium wide
A5.4x
24.5 to 4.57 ms
3.6x
26.1 to 7.18 ms
6.5x
29.1 to 4.44 ms
Sharing id list, wide teams
medium wide
B2.0x
8.96 to 4.47 ms
1.0x
9.02 to 9.26 ms
1.4x
11.4 to 8.41 ms
Sharing id list, wide teams
medium wide
C1.2x
4.08 to 3.40 ms
1.2x
4.23 to 3.39 ms
1.2x
4.25 to 3.41 ms
Parents, single-branch page
large default
A5.3x
242 to 46.0 ms
5.2x
246 to 47.0 ms
5.3x
240 to 45.6 ms
Parents, single-branch page
large default
B6.3x
95.4 to 15.3 ms
4.7x
94.7 to 20.1 ms
7.5x
102 to 13.7 ms
Parents, single-branch page
large default
C2.0x
10.7 to 5.26 ms
2.1x
10.5 to 5.04 ms
1.9x
9.26 to 4.94 ms
Nesting write
large default
A1.0x
0.16 to 0.16 ms
1.0x
0.15 to 0.15 ms
1.0x
0.15 to 0.15 ms
Nesting write
large default
B150x
107 to 0.72 ms
160x
112 to 0.70 ms
170x
107 to 0.62 ms
Nesting write
large default
C5.4x
299 to 55.0 ms
5.3x
303 to 56.9 ms
5.2x
300 to 57.7 ms
Check through a nested team
large default
A2.2x
0.67 to 0.30 ms
2.2x
0.69 to 0.31 ms
1.7x
0.52 to 0.31 ms
Check through a nested team
large default
B4.1x
1.30 to 0.32 ms
4.1x
1.31 to 0.32 ms
3.6x
1.14 to 0.32 ms
Check through a nested team
large default
C1.0x
0.33 to 0.32 ms
1.0x
0.33 to 0.32 ms
1.0x
0.30 to 0.30 ms

Takeaway. 9 of 15 design and workload pairs have a speedup within 30% across Postgres 16, 17, 18. The biggest spread is B sharing id list, wide teams: 1.0x on 17 against 2.0x on 16 (9.02 to 9.26 ms against 8.96 to 4.47 ms).