Parallel posting-tree build (4.3.1)
Status: implemented in src/am/ambuild.c. This note records why it is shaped
the way it is.
Before
participants (leader + W workers): scan -> extract -> worker-role sort (parallel)
leader: merge W+1 runs -> for each trigram: posting tree (serial)
leader: upper tree, SuRF (serial, small)
At large scale the serial middle step dominates. Reading the merged stream and encoding and writing every posting runs on one core, while the workers sit idle.
After
The 64-bit trigram hash space is cut into R equal ranges, with R = 4 x the
number of planned participants. Each participant feeds R worker-role sorts,
one per range, instead of one. Range r of every participant lands in
shared sort r.
participants: scan -> extract -> route by hash range -> R worker-role sorts
barrier: every participant has performsort'ed all R sorts
participants: claim ranges (atomic counter), for each claimed range r:
leader-role merge of sort r -> posting trees for range r
-> append (hash, root|inline) and SuRF keys to a range file
leader: wait for all ranges; read range files 0..R-1 in order
-> upper tree (global hash order = range order), SuRF
- Ordering. Ranges are contiguous and ascending in hash, so concatenating
range results 0..R-1 gives exactly the serial
accums[]order. The upper tree and SuRF are identical in content. Only the block numbers of posting pages differ, because ranges write interleaved. - Leader-role merges. A tuplesort leader-role merge needs only the shared
fileset and
nParticipants, and any backend attached to the DSM can run one.leader_takeover_tapeschecks that all worker runs are finished; the barrier guarantees that. - Writing pages from workers. Workers write index pages through
pg_tre_extend(ExtendBufferedRel; relation extension locks still conflict within a lock group, so extension serialises safely) and GenericXLog. Parallel vacuum workers already WAL-log index pages, andRestorePendingSyncsgives workers the sameRelationNeedsWALanswer underwal_level=minimal. - Results back to the leader. BufFiles in a SharedFileSet, one per range,
as in
sharedtuplestore.c. Records are(hash u64, root u32, inline_bytes u32, inline bytes…), followed by SuRF keys. - Memory. Each participant’s sort memory (
maintenance_work_mem / P) is split across itsRsorts. Each range merge runs withmaintenance_work_mem / P. - Load balance. TID volume per trigram is Zipf-skewed, and the hash’s
high bits are not uniform. With one range per participant, the largest
range ran 50 s past the others on the 2 GB corpus (posting phase 81 s). With
4 ranges per participant, claimed dynamically, it took 53 s.
PG_TRE_RANGES_PER_PARTICIPANT. Ranges are counted from planned participants, so a worker that never launches just leaves its share to the others. - Serial builds (no workers, or
pg_tre.enable_parallel_build = off) are unchanged: one sort, leader readout, through the same per-range function with a single range.
Not done: sizing ranges from a hash sample. Add it if skew at scale outgrows the fixed 4x.