Thread
-
Restructured Shared Buffer Hash Table
Dhruv Aron <dhruv.aron@gmail.com> — 2026-07-07T18:41:47Z
Hi, At Databricks, we’ve found that the existing dynahash table structure is leaving performance gains on the table when it comes to shared buffer lookups: the multi-level structure (directory, segment, bucket chain, freelist) appears excessive for the shared buffers and could be simplified to boost performance and lower memory overhead. As such, we are proposing a specialized hash table just for this purpose and would appreciate feedback on this approach. To give a brief overview, our new table operates primarily on two arrays, one for the entries and one for the bucket heads, and it enforces the invariant that *entries[x]* describes the page in buffer *x*. Each entry stores only a BufferTag and a ‘next’ index (representing the next entry in the same bucket chain), with each bucket head only storing a ‘head’ index (representing the first entry in the bucket chain). At a high-level, the table essentially creates a logical linked list for each bucket on top of the flat physical arrays. The attached patch implements this functionality and passes the existing regression tests; the buf_table.c functions were modified directly, with bufmgr.c also changed slightly to prevent a race condition. My testing (helper script also attached) indicates that all three standard hash table operations (insert, lookup, and delete) generally execute significantly faster than the existing PG18 dynahash counterparts: 4 GB Operation Average Dynahash Execution Time (ns) Average New Table Execution Time (ns) Speedup (Dynahash / New Table) Lookup 77.33 46.50 1.66x Insert 111.78 86.33 1.29x Delete 148.73 104.13 1.43x 16 GB Operation Average Dynahash Execution Time (ns) Average New Table Execution Time (ns) Speedup (Dynahash / New Table) Lookup 98.18 83.01 1.18x Insert 198.58 195.30 1.02x Delete 279.48 274.77 1.02x I would like to note, however, that this patch is part of a larger effort around dynamic shared buffers alongside this patch <https://www.postgresql.org/message-id/CAM1e6U5XDwKYZo6Jj3yD3xpCB4qkhRSQn8upauHt=WhEbK9VZA@mail.gmail.com> and additional internal functionality to dynamically resize this new shared buffer table, i.e. adding *and* removing the number of buckets and entry slots without requiring a restart or total table rehash; I believe the table should be resized to prevent it from consuming a disproportionate amount of memory (or being too slow) relative to the size of the shared buffers, and I would be happy to create a follow-up patch demonstrating those resizing capabilities. That being said, I would like to emphasize that I think the changes here offer enough standalone benefits to merit their own patch. Thanks, Dhruv Aron
-
Re: Restructured Shared Buffer Hash Table
Heikki Linnakangas <hlinnaka@iki.fi> — 2026-07-07T19:13:48Z
On 07/07/2026 21:41, Dhruv Aron wrote: > At Databricks, we’ve found that the existing dynahash table structure is > leaving performance gains on the table when it comes to shared buffer > lookups: the multi-level structure (directory, segment, bucket chain, > freelist) appears excessive for the shared buffers and could be > simplified to boost performance and lower memory overhead. As such, we > are proposing a specialized hash table just for this purpose and would > appreciate feedback on this approach. Yeah, the dynahash has features that we just don't need in the buffer lookup table... The indirection with the directory is actually unnecessary for all the shared memory hash tables, since none of them can be resized. I've wondered if we should try to eliminate that from dynahash for all shmem hash tables. But the buffer lookup table is very performance-critical, so if there are any performance gains to be had, it's indeed probably worthwhile to have a separate implementation just for it. > bufmgr.c also changed slightly to prevent a race condition. Hmm, we're now holding the buffer header lock much longer than before, in InvalidateBuffer(). It's a spinlock, it really should not be held for more than a few instructions. BufTableDelete() is very fast in the new implementation, but still. Could we perhaps do some of BufTableDelete()'s work ahead of time, before we acquire the buffer header lock? Or maybe it's not a problem, in which case some kind of a worst case scenario benchmark to show that would be nice. Maybe test how it behaves when you have a lot of hash collisions, I think that'd make BufTableDelete() more expensive. > My testing > (helper script also attached) indicates that all three standard hash > table operations (insert, lookup, and delete) generally execute > significantly faster than the existing PG18 dynahash counterparts: Nice! I wonder how big the impact is with real world workloads. I've certainly seen the buffer table lookups consume a fair share of CPU time, so I'd assume that it shows up. Another data point: unpatched master, with shared_buffers='128 MB': postgres=# select * from pg_shmem_allocations where name like 'Shared Buffer%' order by name ; name | off | size | allocated_size ----------------------------+-----------+--------+---------------- Shared Buffer Lookup Table | 141607040 | 926000 | 926108 (1 row) With this patch: postgres=# select * from pg_shmem_allocations where name like 'Shared Buffer%' order by name ; name | off | size | allocated_size ------------------------------+-----------+--------+---------------- Shared Buffer Lookup Buckets | 141607040 | 65536 | 65644 Shared Buffer Lookup Entries | 141672576 | 393216 | 393216 (2 rows) So the new hash table takes much less memory. That's nice because you can then fit more in CPU caches. - Heikki -
Re: Restructured Shared Buffer Hash Table
Thom Brown <thom@linux.com> — 2026-07-07T21:08:03Z
On Tue, 7 Jul 2026 at 20:14, Heikki Linnakangas <hlinnaka@iki.fi> wrote: > > On 07/07/2026 21:41, Dhruv Aron wrote: > > At Databricks, we’ve found that the existing dynahash table structure is > > leaving performance gains on the table when it comes to shared buffer > > lookups: the multi-level structure (directory, segment, bucket chain, > > freelist) appears excessive for the shared buffers and could be > > simplified to boost performance and lower memory overhead. As such, we > > are proposing a specialized hash table just for this purpose and would > > appreciate feedback on this approach. > > Yeah, the dynahash has features that we just don't need in the buffer > lookup table... > > The indirection with the directory is actually unnecessary for all the > shared memory hash tables, since none of them can be resized. I've > wondered if we should try to eliminate that from dynahash for all shmem > hash tables. But the buffer lookup table is very performance-critical, > so if there are any performance gains to be had, it's indeed probably > worthwhile to have a separate implementation just for it. > > > bufmgr.c also changed slightly to prevent a race condition. > > Hmm, we're now holding the buffer header lock much longer than before, > in InvalidateBuffer(). It's a spinlock, it really should not be held for > more than a few instructions. BufTableDelete() is very fast in the new > implementation, but still. Could we perhaps do some of > BufTableDelete()'s work ahead of time, before we acquire the buffer > header lock? Or maybe it's not a problem, in which case some kind of a > worst case scenario benchmark to show that would be nice. Maybe test how > it behaves when you have a lot of hash collisions, I think that'd make > BufTableDelete() more expensive. > > > My testing > > (helper script also attached) indicates that all three standard hash > > table operations (insert, lookup, and delete) generally execute > > significantly faster than the existing PG18 dynahash counterparts: > > Nice! > > I wonder how big the impact is with real world workloads. I've certainly > seen the buffer table lookups consume a fair share of CPU time, so I'd > assume that it shows up. > > > Another data point: > > unpatched master, with shared_buffers='128 MB': > > postgres=# select * from pg_shmem_allocations where name like 'Shared > Buffer%' order by name ; > name | off | size | allocated_size > ----------------------------+-----------+--------+---------------- > Shared Buffer Lookup Table | 141607040 | 926000 | 926108 > (1 row) > > With this patch: > > postgres=# select * from pg_shmem_allocations where name like 'Shared > Buffer%' order by name ; > name | off | size | allocated_size > ------------------------------+-----------+--------+---------------- > Shared Buffer Lookup Buckets | 141607040 | 65536 | 65644 > Shared Buffer Lookup Entries | 141672576 | 393216 | 393216 > (2 rows) > > So the new hash table takes much less memory. That's nice because you > can then fit more in CPU caches. Where did this thread come from? I can't see any conversation beyond this single email, and as such, can't see any patch either. Thom
-
Re: Restructured Shared Buffer Hash Table
Heikki Linnakangas <hlinnaka@iki.fi> — 2026-07-07T21:12:36Z
On 08/07/2026 00:08, Thom Brown wrote: > Where did this thread come from? I can't see any conversation beyond > this single email, and as such, can't see any patch either. Huh, maybe it got stuck in moderation? Dhruv CC'd me directly, but you're right, I don't see it in the archives yet. - Heikki
-
Re: Restructured Shared Buffer Hash Table
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com> — 2026-07-08T03:28:27Z
On Wed, Jul 8, 2026 at 12:44 AM Heikki Linnakangas <hlinnaka@iki.fi> wrote: > > On 07/07/2026 21:41, Dhruv Aron wrote: > > At Databricks, we’ve found that the existing dynahash table structure is > > leaving performance gains on the table when it comes to shared buffer > > lookups: the multi-level structure (directory, segment, bucket chain, > > freelist) appears excessive for the shared buffers and could be > > simplified to boost performance and lower memory overhead. As such, we > > are proposing a specialized hash table just for this purpose and would > > appreciate feedback on this approach. > > Yeah, the dynahash has features that we just don't need in the buffer > lookup table... > > The indirection with the directory is actually unnecessary for all the > shared memory hash tables, since none of them can be resized. I've > wondered if we should try to eliminate that from dynahash for all shmem > hash tables. But the buffer lookup table is very performance-critical, > so if there are any performance gains to be had, it's indeed probably > worthwhile to have a separate implementation just for it. For very large buffer pools, the buffer lookup table spans multiple memory pages. With buffer resizing capability it will be good to be able to resize the buffer lookup table as well. Right now the patch does not resize buffer look up table because of its memory layout and complexity involved in that operation. If we are using a different data structure for buffer lookup, it will be good to consider ease of resizing as well. -- Best Wishes, Ashutosh Bapat