public inbox for [email protected]  
help / color / mirror / Atom feed
Re: pg_upgrade instructions involving "rsync --size-only" might lead to standby corruption?
4+ messages / 4 participants
[nested] [flat]

* Re: pg_upgrade instructions involving "rsync --size-only" might lead to standby corruption?
@ 2023-06-30 21:33 Bruce Momjian <[email protected]>
  2023-06-30 22:18 ` Re: pg_upgrade instructions involving "rsync --size-only" might lead to standby corruption? Nikolay Samokhvalov <[email protected]>
  0 siblings, 1 reply; 4+ messages in thread

From: Bruce Momjian @ 2023-06-30 21:33 UTC (permalink / raw)
  To: Robert Haas <[email protected]>; +Cc: Nikolay Samokhvalov <[email protected]>; pgsql-hackers; Stephen Frost <[email protected]>

On Fri, Jun 30, 2023 at 04:16:31PM -0400, Robert Haas wrote:
> On Fri, Jun 30, 2023 at 1:41 PM Bruce Momjian <[email protected]> wrote:
> > I think --size-only was chosen only because it is the minimal comparison
> > option.
> 
> I think it's worse than that. I think that the procedure relies on
> using the --size-only option to intentionally trick rsync into
> thinking that files are identical when they're not.
> 
> Say we have a file like base/23246/78901 on the primary. Unless
> wal_log_hints=on, the standby version is very likely different, but
> only in ways that don't matter to WAL replay. So the procedure aims to
> trick rsync into hard-linking the version of that file that exists on
> the standby in the old cluster into the new cluster on the standby,
> instead of copying the slightly-different version from the master,
> thus making the upgrade very fast. If rsync actually checksummed the
> files, it would realize that they're different and copy the file from
> the original primary, which the person who wrote this procedure does
> not want.

What is the problem with having different hint bits between the two
servers?

> That's kind of a crazy thing for us to be documenting. I think we
> really ought to consider removing from this documentation. If somebody
> wants to write a reliable tool for this to ship as part of PostgreSQL,
> well and good. But this procedure has no real sanity checks and is
> based on very fragile assumptions. That doesn't seem suitable for
> end-user use.
> 
> I'm not quite clear on how Nikolay got into trouble here. I don't
> think I understand under exactly what conditions the procedure is
> reliable and under what conditions it isn't. But there is no way in
> heck I would ever advise anyone to use this procedure on a database
> they actually care about. This is a great party trick or something to
> show off in a lightning talk at PGCon, not something you ought to be
> doing with valuable data that you actually care about.

Well, it does get used, and if we remove it perhaps we can have it on
our wiki and point to it from our docs.

-- 
  Bruce Momjian  <[email protected]>        https://momjian.us
  EDB                                      https://enterprisedb.com

  Only you can decide what is important to you.






^ permalink  raw  reply  [nested|flat] 4+ messages in thread

* Re: pg_upgrade instructions involving "rsync --size-only" might lead to standby corruption?
  2023-06-30 21:33 Re: pg_upgrade instructions involving "rsync --size-only" might lead to standby corruption? Bruce Momjian <[email protected]>
@ 2023-06-30 22:18 ` Nikolay Samokhvalov <[email protected]>
  2023-07-07 13:31   ` Re: pg_upgrade instructions involving "rsync --size-only" might lead to standby corruption? Stephen Frost <[email protected]>
  0 siblings, 1 reply; 4+ messages in thread

From: Nikolay Samokhvalov @ 2023-06-30 22:18 UTC (permalink / raw)
  To: Bruce Momjian <[email protected]>; +Cc: Robert Haas <[email protected]>; Stephen Frost <[email protected]>; pgsql-hackers

On Fri, Jun 30, 2023 at 14:33 Bruce Momjian <[email protected]> wrote:

> On Fri, Jun 30, 2023 at 04:16:31PM -0400, Robert Haas wrote:
> > I'm not quite clear on how Nikolay got into trouble here. I don't
> > think I understand under exactly what conditions the procedure is
> > reliable and under what conditions it isn't. But there is no way in
> > heck I would ever advise anyone to use this procedure on a database
> > they actually care about. This is a great party trick or something to
> > show off in a lightning talk at PGCon, not something you ought to be
> > doing with valuable data that you actually care about.
>
> Well, it does get used, and if we remove it perhaps we can have it on
> our wiki and point to it from our docs.


In my case, we performed some additional writes on the primary before
running "pg_upgrade -k" and we did it *after* we shut down all the
standbys. So those changes were not replicated and then "rsync --size-only"
ignored them. (By the way, that cluster has wal_log_hints=on to allow
Patroni run pg_rewind when needed.)

But this can happen with anyone who follows the procedure from the docs as
is and doesn't do any additional steps, because in step 9 "Prepare for
standby server upgrades":

1) there is no requirement to follow specific order to shut down the nodes
   - "Streaming replication and log-shipping standby servers can remain
running until a later step" should probably be changed to a
requirement-like "keep them running"

2) checking the latest checkpoint position with pg_controldata now looks
like a thing that is good to do, but with uncertainty purpose -- it does
not seem to be used to support any decision
  - "There will be a mismatch if old standby servers were shut down before
the old primary or if the old standby servers are still running" should
probably be rephrased saying that if there is mismatch, it's a big problem

So following the steps as is, if some writes on the primary are not
replicated (due to whatever reason) before execution of pg_upgrade -k +
rsync --size-only, then those writes are going to be silently lost on
standbys.

I wonder, if we ensure that standbys are fully caught up before upgrading
the primary, if we check the latest checkpoint positions, are we good to
use "rsync --size-only", or there are still some concerns? It seems so to
me, but maybe I'm missing something.

> --

Thanks,
Nikolay Samokhvalov
Founder, Postgres.ai


^ permalink  raw  reply  [nested|flat] 4+ messages in thread

* Re: pg_upgrade instructions involving "rsync --size-only" might lead to standby corruption?
  2023-06-30 21:33 Re: pg_upgrade instructions involving "rsync --size-only" might lead to standby corruption? Bruce Momjian <[email protected]>
  2023-06-30 22:18 ` Re: pg_upgrade instructions involving "rsync --size-only" might lead to standby corruption? Nikolay Samokhvalov <[email protected]>
@ 2023-07-07 13:31   ` Stephen Frost <[email protected]>
  0 siblings, 0 replies; 4+ messages in thread

From: Stephen Frost @ 2023-07-07 13:31 UTC (permalink / raw)
  To: Nikolay Samokhvalov <[email protected]>; +Cc: Bruce Momjian <[email protected]>; Robert Haas <[email protected]>; pgsql-hackers

Greetings,

* Nikolay Samokhvalov ([email protected]) wrote:
> On Fri, Jun 30, 2023 at 14:33 Bruce Momjian <[email protected]> wrote:
> > On Fri, Jun 30, 2023 at 04:16:31PM -0400, Robert Haas wrote:
> > > I'm not quite clear on how Nikolay got into trouble here. I don't
> > > think I understand under exactly what conditions the procedure is
> > > reliable and under what conditions it isn't. But there is no way in
> > > heck I would ever advise anyone to use this procedure on a database
> > > they actually care about. This is a great party trick or something to
> > > show off in a lightning talk at PGCon, not something you ought to be
> > > doing with valuable data that you actually care about.
> >
> > Well, it does get used, and if we remove it perhaps we can have it on
> > our wiki and point to it from our docs.

I was never a fan of having it actually documented because it's a pretty
complex and involved process that really requires someone doing it have
a strong understanding of how PG works.

> In my case, we performed some additional writes on the primary before
> running "pg_upgrade -k" and we did it *after* we shut down all the
> standbys. So those changes were not replicated and then "rsync --size-only"
> ignored them. (By the way, that cluster has wal_log_hints=on to allow
> Patroni run pg_rewind when needed.)

That's certainly going to cause problems..

> But this can happen with anyone who follows the procedure from the docs as
> is and doesn't do any additional steps, because in step 9 "Prepare for
> standby server upgrades":
> 
> 1) there is no requirement to follow specific order to shut down the nodes
>    - "Streaming replication and log-shipping standby servers can remain
> running until a later step" should probably be changed to a
> requirement-like "keep them running"

Agreed that it would be good to clarify that the primary should be shut
down first, to make sure everything written by the primary has been
replicated to all of the replicas.

> 2) checking the latest checkpoint position with pg_controldata now looks
> like a thing that is good to do, but with uncertainty purpose -- it does
> not seem to be used to support any decision
>   - "There will be a mismatch if old standby servers were shut down before
> the old primary or if the old standby servers are still running" should
> probably be rephrased saying that if there is mismatch, it's a big problem

Yes, it's absolutely a big problem and that's the point of the check.
Slightly surprised that we need to explicitly say "if they don't match
then you need to figure out what you did wrong and don't move forward
until you get everything shut down and with matching values", but that's
also why it isn't a great idea to try and do this without a solid
understanding of how PG works.

> So following the steps as is, if some writes on the primary are not
> replicated (due to whatever reason) before execution of pg_upgrade -k +
> rsync --size-only, then those writes are going to be silently lost on
> standbys.

Yup.

> I wonder, if we ensure that standbys are fully caught up before upgrading
> the primary, if we check the latest checkpoint positions, are we good to
> use "rsync --size-only", or there are still some concerns? It seems so to
> me, but maybe I'm missing something.

I've seen a lot of success with it.

Ultimately, when I was describing this process, it was always with the
idea that it would be performed by someone quite familiar with the
internals of PG or, ideally, could be an outline of how an interested PG
hacker could write a tool to do it.  Hard to say, but I do feel like
having it documented has actually reduced the interest in writing a tool
to do it, which, if that's the case, is quite unfortunate.

Thanks,

Stephen


Attachments:

  [application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 4+ messages in thread

* Re: [PATCH] Batched clock sweep to reduce cross-socket atomic contention
@ 2026-07-10 14:00 Greg Burd <[email protected]>
  0 siblings, 0 replies; 4+ messages in thread

From: Greg Burd @ 2026-07-10 14:00 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>; Tomas Vondra <[email protected]>; Nathan Bossart <[email protected]>

Hello again,

Removing the freelist [1] turned out to be a good idea, so why not remove
and simplify more?

Included is a three-patch series (v3, on 2e6578292a9 cf-7000) that
replaces the 0..5 usage_count clock sweep with a cooling-stage clock,
and because this evictor algorithm is scan resistance 0003 removes
the BufferAccessStrategy ring buffers entirely.  The net is slightly
faster (TPS), hotter buffers (fewer misses) and a lot less code to
maintain (no special case for scan resistant work) going forward.

  0001  Batch the clock sweep to reduce nextVictimBuffer atomic contention
  0002  Replace the usage_count clock sweep with a cooling-stage evictor
  0003  Remove BufferAccessStrategy; scan resistance is now intrinsic

Net it is +510/-1642 over 83 files; almost all of the deletion is 0003.

On a 6-NUMA-node r8i.metal-96xl under continuous eviction, this change is
a consistent +4–5% on read-heavy pgbench with equal-or-better hit ratios.
The same test on a 2-node box is flat, which is the tell that the win
is the cross-socket clock-hand contention the batched sweep removes. Under
real storage IO (working set > RAM on local NVMe), the scan-resistance
path is +1.6–3.9% with measurably fewer heap reads.

I'll put the design decisions and the benchmark methodology on the table
first, then talk through the results because if the model is wrong on
principle, or the benchmarks are measuring the wrong thing, I would much
rather hear it now.

Contention arises on high core-count systems from the of cost advancing
nextVictimBuffer in StrategyGetBuffer() via a fetch-add of 1 per tick.
This contention is exacerbated on a multi-socket box.  That one cache
line bounces over the interconnect on every eviction; under pressure
with hundreds of backends it is the dominant cost of the sweep.  This is
why we're investing time on NUMA optimizations.


== What each patch does, and why ==

0001 -- Batch the clock sweep.

  This is the setup/baseline commit and is an incremental improvement
  over the existing batched approach, but it is not the final goal and
  should not be committed without 0002.

  Each backend will claim a run of consecutive buffer IDs with a single
  fetch-add and iterate them privately.  Global sweep order is preserved
  -- each buffer is visited exactly once per pass -- only the intra-pass
  ordering of visits changes, which the algorithm does not depend on.
  The atomic fires ~1/batch as often.

  The batch is one cache line's worth of hand values (PG_CACHE_LINE_SIZE
  / sizeof(uint32)), capped at NBuffers.  It is gated on multi-node NUMA
  (pg_numa_get_max_node() >= 1); on a single socket the batch is 1 and
  the path is byte-identical to today.  This is essentially Jim's patch;
  the only substantive change from his v1/v2 is deriving the batch size
  from the cache-line size rather than a fixed 64/tiered constant.

0002 -- Replace usage_count with a cooling-stage evictor.

  This is the heart of the series and where I most want the design
  attacked.

  A buffer is HOT (recently used) or COOL (an eviction candidate);
  "pinned" is the existing refcount.  There is no per-buffer 0..5
  counter.  A demand-loaded page is admitted COOL (probationary); a
  second access promotes it COOL->HOT.  So a page touched once -- as is
  the case with a sequential scan -- fills and drains the COOL stage and
  is evicted from it without displacing the HOT working set.  This is
  the LeanStore / 2Q-A1 idea, and it is the whole reason 0003 can exist:
  scan resistance stops being a ring-buffer bolt-on and becomes a
  property of the replacement algorithm.

  The 4-bit usage_count field is reinterpreted in place -- bit 0
  HOT/COOL, bit 1 a reference bit, the top 2 bits unused -- so the 64-bit
  buffer-state layout, its refcount / flag / lock offsets.  The only
  change to the StaticAsserts is a new one asserting the field is at
  least 2 bits wide, since we now use two of its bits.  The eviction
  claim (COOL, unpinned -> pinned) stays a CAS so a racing PinBuffer
  always wins; promotion and demotion are single-bit transitions.

  The one non-obvious decision, which I got wrong first and want to flag
  loudly: *Who* demotes HOT->COOL.  My first cut was "prefer-COOL": the
  foreground sweep skips HOT buffers hunting for an already-COOL victim
  and only cools HOT buffers once a full pass finds none.  That
  collapses under an ordinary OLTP workload where the working set is
  larger than shared_buffers (no scan at all).  Every access promotes
  its buffer to HOT, so there are almost no COOL buffers, the "cool a
  full pass" fallback fires on nearly every allocation, and each victim
  search becomes a ~2x full-pool scan of scattered BufferDescs.  I
  measured 3-17x throughput loss vs stock -- a cliff.

  HOT->COOL demotion is done during the background writer's existing LRU
  scan, which already runs ahead of the clock hand.  It demotes just
  enough HOT buffers to keep a supply of COOL victims (bounded by the
  predicted next-cycle allocation, so it does not cool the whole pool),
  under the buffer header lock it already holds.  A single reference bit
  gives a recently-accessed buffer one reprieve before it is cooled,
  which keeps the genuinely-hot set out of the COOL stage under scan
  pressure.  With that, the foreground finds a victim in a single pass
  and the cliff is gone (data below).

  I chose prefer-COOL-plus-bgwriter-precooling because it can protect a
  hot buffer slightly longer (the pre-cooler, tuned to a budget, decides
  when to demote rather than demoting on contact), which should help hit
  ratio on a stable hot set -- IF the pre-cooler keeps up.  When it lags
  (bgwriter off or behind), the foreground force_cool is still there as
  a correctness fallback, but it is the expensive path.

0003 -- Remove BufferAccessStrategy.

  With scan resistance intrinsic, the BAS_BULKREAD/BULKWRITE/VACUUM
  rings are dead weight, so this removes them end to end: the type and
  enum, the ring machinery in freelist.c, the strategy parameter
  threaded through ReadBufferExtended / the ExtendBufferedRel* family /
  read_stream / every scan/vacuum/analyze/index-AM caller, the strategy
  fields on the scan and bulk-insert descriptors, and
  _hash_getbuf_with_strategy.  pg_stat_io's per-strategy IO contexts
  collapse to normal/init (IOOP_REUSE only ever happened while recycling
  a ring buffer, so it is gone), and the vacuum_buffer_usage_limit GUC /
  VACUUM (BUFFER_USAGE_LIMIT ...) option / vacuumdb --buffer-usage-limit
  go with it.

  This is the patch most likely to be contentious for reasons unrelated
  to the sweep: it removes a user-visible GUC and changes pg_stat_io's
  shape.  I have kept it as its own commit precisely so 0001+0002 can be
  judged on the algorithm without swallowing the removal.  If the
  consensus is that the cooling model is fine but the ring machinery
  should stay for other reasons (BUFFER_USAGE_LIMIT as an operator
  control, say), 0003 can simply be dropped and 0002 still stands -- the
  rings just become redundant rather than removed.  I would like to know
  if that is where people land.


== Benchmarks ==

I want to be careful here, because Andres's central criticism of the
original batched-sweep numbers [2] was that they measured the wrong
thing -- an all-in-page-cache config where the only bottleneck left is
the atomic, which is not how anyone runs a 384-vCPU box.  That criticism
is correct and I have tried to design around it, but I have almost
certainly not fully escaped it, so the methodology is laid out below in
enough detail to shoot at.

I refer to the COOL/HOT approach (the changes in this patch set) as
"bcs", I've forgotten what that stands for... "buffer cache solution?"
I really don't remember (ha!).

Hardware / method (both instances bare-metal, Amazon Linux 2023):

- m6i.metal      -- 128 vCPU, 2 sockets, 2 NUMA nodes (distances 10/20), 503GB
- r8i.metal-96xl -- 384 vCPU, 2 sockets, 6 NUMA nodes via SNC3, 3TB

Builds: --buildtype=debugoptimized (-O2), cassert off, --with-libnuma,
both branches from the same tree.  Postmaster pinned with numactl
--cpunodebind=0 --membind=0 (without it stock TPS varied ~30% by launch
node -- worth flagging for anyone reproducing).  pgbench dataset loaded
once per build into its own datadir.

The regime I settled on: a dataset that fits fully in OS page cache but
exceeds shared_buffers, warmed before each cell, caches NOT dropped
between cells.  The point is a sweep that runs continuously with
sub-millisecond read latency and NO storage IO in the critical path --
so what I am measuring is the eviction machinery itself, not the disk
(as best as I can tell).

I sweep the ratio (working-set / shared_buffers) from 0.8 (fits, no
eviction, a control) up to 8x by varying shared_buffers against a fixed
63GB dataset.

3 iterations per cell, medians reported, 256 clients on m6i / 384 on r8i.

Two workloads: uniform pgbench -S (the pure eviction-churn case, and the
worst case for the cooling model -- no hot set to protect), and
"hotscan" (a Zipfian hot set plus a handful of clients running large
range scans -- the scan-resistance case).

I want the regime itself critiqued.  It deliberately removes storage IO
to expose the sweep; the flip side is it is not a production
configuration, and the read-path win (fewer misses under scan
resistance) shows up as read count, not TPS, because a "miss" here is an
OS-cache memcpy, not a device read.

r8i.metal-96xl, uniform pgbench -S, 384 clients, TPS (median of 3):

  ratio  SB(GB)   stock       bcs        delta   stock/bcs hit%
  0.8    ~400     1,623,536   1,692,090   +4.2%   99.2 / 99.9
  1.25   ~320     1,443,343   1,519,290   +5.3%   94.8 / 95.2
  1.5    ~266     1,344,051   1,417,462   +5.5%   91.9 / 92.1
  2      ~200     1,293,871   1,358,550   +5.0%   87.9 / 87.8
  4      ~100     1,194,405   1,240,334   +3.8%   77.1 / 78.5
  8      ~50      1,090,873   1,140,839   +4.6%   69.7 / 70.6

Consistent +4-5% across the eviction range, growing with pressure, with
equal-or-better hit ratio (better at 4x/8x).  bcs also showed lower
cache-miss rate in perf stat (~28-35% vs ~31-37%), which is the batched
sweep's reduced cross-node line bouncing showing through.

r8i, hotscan (Zipfian hot set + range scanners), TPS / hit% / heap reads:

  ratio  stock TPS   bcs TPS    stock hit  bcs hit   stock reads  bcs reads
  1.25   1,833,675   1,871,289   99.43     99.57     7,309,457    5,503,927
  1.5    1,876,846   1,942,877   99.35     99.42     8,335,144    7,565,667
  2      1,868,591   1,853,547   99.12     99.15    11,173,831   10,987,371

The scan-resistance signal: at 1.25-1.5x, bcs holds a higher hit ratio
and does up to 25% fewer heap reads (24.7% at 1.25x, ~9% at 1.5x) -- it
is keeping the hot set resident through the scans where stock lets them
flush it.  Muted in TPS only because everything is in OS cache (a miss
is cheap); on real storage this read reduction is where the win would
land.  At 2x it washes out (enough pressure that both evict heavily).

m6i.metal (2 nodes), uniform, 256 clients -- the smaller box, for contrast:
essentially parity, bcs -2% to +1% across ratios.  The 2-node box barely
exercises the atomic, so 0001's contention win does not appear; that it does
not regress is the result that matters here.

Huge pages on vs off (r8i, uniform, medians): I ran this because the
original thread flagged it as uncharacterized.  bcs won by +3-7% both
ways, no regression without huge pages -- the win is from cutting the
frequency of atomic ops on the counter line, which does not depend on
where the descriptors physically live.  (This is why the batching gate
is NUMA-only and not also huge-pages-gated.)

The "prefer-COOL cliff" I mentioned under 0002, so the failure mode is
on the record: BEFORE moving cooling into the bgwriter, the m6i uniform
run at 256 clients was bcs 274K vs stock 840K at ratio 2 (-67%), and
42.8K vs 762K at 8x (-94%), with cache-miss rate exploding to ~40%.
That is the shape of getting the demotion policy wrong; the r8i +5%
table above is after the fix.

Reproduction: the whole harness (instance launch, OS tuning, per-build
load, the ratio sweep, perf stat capture) is scripted; I will attach it
as a DO-NOT-MERGE commit / put it in the CF entry so the methodology can
be reproduced and picked apart rather than taken on faith.  Raw per-run
CSVs and perf output likewise.


A Real IO (working set > RAM, evictions hitting storage) Benchmark

This is the regime Andres asked for, and the one I flagged earlier as not yet
done cleanly.  The earlier attempt was EBS-latency-bound; this one uses local
NVMe so eviction reads hit real storage at ~microsecond, not ~15ms, latency --
during the run the array sat at 100% utilization and ~145K read IOPS, so the
eviction path is genuinely storage-bound, not cache-served.

m6id.metal -- 128 vCPU, 2 sockets, 2 NUMA nodes, 503GB, 4x1.9TB local NVMe in
RAID0.  Dataset ~700GB (pgbench scale 47000), i.e. LARGER than RAM, so the
working set cannot sit in the OS page cache.  shared_buffers is a small window
over it -- 63GB (11x) and 31GB (22x) -- caches dropped per cell, 256 clients, 3
iterations, medians.  Same builds/method as the in-cache runs otherwise.

hotscan (Zipfian hot set + range scanners), median of 3:

  ratio  SB(GB)   stock TPS   bcs TPS     dTPS    stock/bcs hit   reads d
  11     63       877,357     911,363    +3.9%    96.30 / 96.43   -2.2%
  22     31       873,880     888,010    +1.6%    94.02 / 94.22   -1.5%

This is the result the in-cache runs could only hint at: under real storage IO
the scan-resistance read reduction converts to throughput.  The bcs approach
keeps a higher hit ratio and does 1.5-2.2% fewer heap reads, and here -- unlike
in cache, where a miss is a cheap memcpy -- a read it avoids is an NVMe round
trip, so the read reduction shows up as +1.6-3.9% TPS.

uniform pgbench -S (pure eviction churn, no hot set to protect), median of 3:

  ratio  SB(GB)   stock TPS   bcs TPS     dTPS    stock/bcs hit
  11     63       605,656     616,247    +1.7%    67.0 / 67.3
  22     31       605,517     611,312    +1.0%    63.5 / 63.6

Hit ratio here is 63-67% -- a third of accesses miss and hit NVMe (190M-220M
evictions per run), so this is deep, genuinely storage-bound churn.  bcs is
+1-1.7%, i.e. flat-to-slightly-positive, which is the honest production picture:
with no hot set to protect, scan resistance has nothing to do, and the win is
just the batched sweep's reduced contention showing faintly through the IO wait.
Notably bcs does not regress even when its policy has no advantage to exploit.


== Side note for the curious... ==

Separately, Dhruv Aron has proposed restructuring the shared-buffer lookup table
[2], replacing dynahash with a flat two-array structure. That attacks the
other hot cost on a buffer miss — resolving a page to its buffer — where this
series attacks the eviction that a miss triggers. They touch buf_table.c and a
lock-ordering change in InvalidateBuffer(); this series touches freelist.c and
the per-buffer replacement state, and removes BufferAccessStrategy. The two are
complementary and should compound on the miss path; the only overlap is
InvalidateBuffer()/GetVictimBuffer(), where their extended buffer-header-lock
hold and this series' CAS-claim + bgwriter pre-cooling both take that lock, and
would want reconciling if both land. I have not benchmarked them together (yet).

== What I have not done, honestly ==

  - Hardening the foreground force_cool fallback to be cheap when it
    fires, rather than relying on the bgwriter pre-cooler never lagging.

  - Anything on single-socket beyond "does not regress"; the design is
    not trying to help there.


== The ask ==

  1. 0002's demotion policy: is prefer-COOL + bgwriter pre-cooling the
     right call, or is the other team's cool-in-place the more robust
     default given it has no cliff and no background-process dependency?
     This is the decision everything else hangs on.

  2. Is admitting demand-loaded pages COOL (probationary,
     promote-on-second- touch) an acceptable basis for scan resistance
     in the core buffer manager, i.e. is it OK to make scan resistance
     an algorithm property and retire the strategy rings (0003)?  Or
     should the rings stay and 0002 ride alongside them?

  3. The benchmark methodology: where is the in-OS-cache regime
     misleading, and what would you want measured instead?  I am most
     worried I am flattering the sweep by removing the IO that would
     otherwise hide it.

  4. Reinterpreting the usage_count field as {HOT/COOL, ref} bits and
     collapsing pg_stat_io's contexts -- acceptable, or is there a
     cleaner representation the project would want before this is worth
     pursuing?

I have measured that the 0..5 count is overhead and provides no
meaningful signal at all, that a HOT/COLD approach provides a simpler
more stable and better performing eviction model for the buffer pool.
If you dispute that, let's dig in and compare notes. :)

I would be remiss if I didn't point out the thread [3] by Tomas et. al.,
whose NUMA investigation targets the same bottlenecks, and inspired the
work that led to this set of ideas. 

Thanks for reading this far.  I look forward to the critique.

best.

-greg

[1] Reconsidering the freelist
    https://www.postgresql.org/message-id/f0e3c02e-e217-4f04-8dab-1e7e80a228c0%40burd.me
[2] Re: Restructured Shared Buffer Hash Table
    https://www.postgresql.org/message-id/[email protected]
[3] Adding basic NUMA awareness (Tomas Vondra)
    https://www.postgresql.org/message-id/099b9433-2855-4f1b-b421-d078a5d82017%40vondra.me

Attachments:

  [text/x-patch] v3-0001-Batch-the-clock-sweep-to-reduce-nextVictimBuffer-.patch (8.9K, ../../[email protected]/2-v3-0001-Batch-the-clock-sweep-to-reduce-nextVictimBuffer-.patch)
  download | inline diff:
From e73fcc94e915d1b29e7ebed14b659495d78e6cc3 Mon Sep 17 00:00:00 2001
From: Greg Burd <[email protected]>
Date: Mon, 6 Jul 2026 17:25:00 -0400
Subject: [PATCH v3 1/3] Batch the clock sweep to reduce nextVictimBuffer
 atomic contention

StrategyGetBuffer() advances the shared clock hand, nextVictimBuffer, with a
pg_atomic_fetch_add_u32(..., 1) on every tick.  On a multi-socket system the
cache line holding that counter has to travel over the interconnect on each
operation, pushing a sweep tick from ~20ns (same-socket, line warm in L1/L2)
into the ~100-200ns range.  Under eviction pressure with hundreds of backends
in StrategyGetBuffer() concurrently, that single cache line becomes the
dominant cost of the sweep, visible as elevated bus-cycles and cache-misses in
a perf profile.

Have each backend claim a run of consecutive buffer IDs from the shared hand
with a single fetch-add and then iterate through them privately.  The sweep
still advances through the pool in order, each buffer is still visited exactly
once per complete pass, and the meaning of the clock state is unchanged; only
the temporal ordering of visits within a pass changes, which the algorithm
does not depend on.  The contended atomic now fires roughly once per batch
rather than once per buffer.

The batch is one cache line's worth of clock-hand values --
PG_CACHE_LINE_SIZE / sizeof(uint32) -- capped at NBuffers so a claim can never
wrap the pool more than once.  Batching only helps when the counter's cache
line actually bounces between sockets, so it is enabled only on multi-node
NUMA hardware (pg_numa_get_max_node() >= 1); on a single socket, or where
libnuma is unavailable, the batch size stays 1 and the code path is
byte-identical to the stock clock sweep.

Wraparound handling is adjusted: with batching, several backends can each see
a fetch-add return a value past NBuffers within the same pass.  Any such
backend takes buffer_strategy_lock, re-reads the counter, and if it is still
out of range wraps it with a single CAS and increments completePasses.
StrategySyncStart() continues to see a consistent (nextVictimBuffer,
completePasses) pair.

This is the batched-clock-sweep idea from Jim Mlodgenski's pgsql-hackers
thread, adapted to derive the batch size from the platform cache-line size.

Co-authored-by: Jim Mlodgenski <[email protected]>
---
 src/backend/storage/buffer/freelist.c | 134 +++++++++++++++++---------
 1 file changed, 91 insertions(+), 43 deletions(-)

diff --git a/src/backend/storage/buffer/freelist.c b/src/backend/storage/buffer/freelist.c
index fdb5bad7910..93fc4dd758b 100644
--- a/src/backend/storage/buffer/freelist.c
+++ b/src/backend/storage/buffer/freelist.c
@@ -22,6 +22,7 @@
 #include "storage/proc.h"
 #include "storage/shmem.h"
 #include "storage/subsystems.h"
+#include "port/pg_numa.h"
 
 #define INT_ACCESS_ONCE(var)	((int)(*((volatile int *)&(var))))
 
@@ -100,68 +101,92 @@ static BufferDesc *GetBufferFromRing(BufferAccessStrategy strategy,
 static void AddBufferToRing(BufferAccessStrategy strategy,
 							BufferDesc *buf);
 
+/*
+ * Per-backend state for the batched clock sweep.  Each backend claims a run
+ * of consecutive clock-hand values with a single atomic fetch-add and then
+ * iterates through them privately, so the contended nextVictimBuffer cache
+ * line is touched roughly 1/batch as often.  MyBatchPos is the next hand
+ * value to hand out; MyBatchEnd is one past the end of the claimed run.  Both
+ * are absolute (monotonically increasing) hand values; the buffer id is the
+ * value modulo NBuffers.
+ */
+static uint32 MyBatchPos = 0;
+static uint32 MyBatchEnd = 0;
+
+/*
+ * Number of clock-hand values a backend claims per atomic fetch-add,
+ * computed once at startup (see StrategyCtlShmemInit).  When batching is
+ * enabled it is one cache line's worth of hand advance, so concurrent
+ * backends sweep non-overlapping, cache-line-sized runs of the pool; the
+ * global sweep order is preserved (each buffer is still visited exactly once
+ * per pass).  Batching is enabled only on multi-node NUMA hardware; otherwise
+ * this stays 1 and the sweep is byte-identical to the stock clock.
+ */
+static uint32 ClockSweepBatchSize = 1;
+
 /*
  * ClockSweepTick - Helper routine for StrategyGetBuffer()
  *
- * Move the clock hand one buffer ahead of its current position and return the
- * id of the buffer now under the hand.
+ * Return the next buffer to consider for eviction.  Backends claim batches of
+ * consecutive buffer IDs from the shared clock hand, then iterate through
+ * them locally without further atomic operations.  This preserves the global
+ * sweep order while reducing contention on the shared counter.
  */
 static inline uint32
 ClockSweepTick(void)
 {
 	uint32		victim;
 
-	/*
-	 * Atomically move hand ahead one buffer - if there's several processes
-	 * doing this, this can lead to buffers being returned slightly out of
-	 * apparent order.
-	 */
-	victim =
-		pg_atomic_fetch_add_u32(&StrategyControl->nextVictimBuffer, 1);
-
-	if (victim >= NBuffers)
+	if (MyBatchPos >= MyBatchEnd)
 	{
-		uint32		originalVictim = victim;
-
-		/* always wrap what we look up in BufferDescriptors */
-		victim = victim % NBuffers;
-
 		/*
-		 * If we're the one that just caused a wraparound, force
-		 * completePasses to be incremented while holding the spinlock. We
-		 * need the spinlock so StrategySyncStart() can return a consistent
-		 * value consisting of nextVictimBuffer and completePasses.
+		 * Claim a fresh batch from the shared clock hand.  This is the only
+		 * atomic operation per batch, reducing contention by the batch size.
 		 */
-		if (victim == 0)
-		{
-			uint32		expected;
-			uint32		wrapped;
-			bool		success = false;
+		uint32		start;
+		uint32		batch_size = ClockSweepBatchSize;
 
-			expected = originalVictim + 1;
+		start = pg_atomic_fetch_add_u32(&StrategyControl->nextVictimBuffer,
+										batch_size);
 
-			while (!success)
-			{
-				/*
-				 * Acquire the spinlock while increasing completePasses. That
-				 * allows other readers to read nextVictimBuffer and
-				 * completePasses in a consistent manner which is required for
-				 * StrategySyncStart().  In theory delaying the increment
-				 * could lead to an overflow of nextVictimBuffers, but that's
-				 * highly unlikely and wouldn't be particularly harmful.
-				 */
-				SpinLockAcquire(&StrategyControl->buffer_strategy_lock);
+		if (start >= (uint32) NBuffers)
+		{
+			start = start % NBuffers;
 
-				wrapped = expected % NBuffers;
+			/*
+			 * The counter has grown past NBuffers; try to wrap it back.  We
+			 * must hold the spinlock so StrategySyncStart() can read
+			 * nextVictimBuffer and completePasses consistently.
+			 *
+			 * With batching, multiple backends may each land a fetch-add
+			 * that returns a value past NBuffers in the same pass.  After
+			 * acquiring the spinlock we re-read the counter: if another
+			 * backend already wrapped it below NBuffers we are done.
+			 */
+			SpinLockAcquire(&StrategyControl->buffer_strategy_lock);
+			{
+				uint32		current;
+				uint32		wrapped;
 
-				success = pg_atomic_compare_exchange_u32(&StrategyControl->nextVictimBuffer,
-														 &expected, wrapped);
-				if (success)
-					StrategyControl->completePasses++;
-				SpinLockRelease(&StrategyControl->buffer_strategy_lock);
+				current = pg_atomic_read_u32(&StrategyControl->nextVictimBuffer);
+				if (current >= (uint32) NBuffers)
+				{
+					wrapped = current % NBuffers;
+					if (pg_atomic_compare_exchange_u32(&StrategyControl->nextVictimBuffer,
+													   &current, wrapped))
+						StrategyControl->completePasses++;
+				}
 			}
+			SpinLockRelease(&StrategyControl->buffer_strategy_lock);
 		}
+
+		MyBatchPos = start;
+		MyBatchEnd = start + batch_size;
 	}
+
+	victim = MyBatchPos % NBuffers;
+	MyBatchPos++;
+
 	return victim;
 }
 
@@ -408,6 +433,29 @@ StrategyCtlShmemInit(void *arg)
 
 	/* No pending notification */
 	StrategyControl->bgwprocno = -1;
+
+	/*
+	 * Decide whether to batch the clock sweep.
+	 *
+	 * Batching claims a run of consecutive buffer IDs per atomic fetch-add so
+	 * concurrent backends touch the shared nextVictimBuffer cache line ~1/batch
+	 * as often -- a win only when that line actually bounces across sockets,
+	 * i.e. on multi-node NUMA hardware.  On a single socket the atomic is
+	 * already node-local and batching would only make backends skip ahead for
+	 * no benefit, so we fall back to batch size 1 there (byte-identical to the
+	 * stock one-buffer-at-a-time clock sweep).
+	 *
+	 * Batch (> 1) only when libnuma reports more than one node
+	 * (pg_numa_get_max_node() >= 1); pg_numa_init() returns -1 when NUMA is
+	 * unavailable.  (Benchmarking showed the win holds with or without huge
+	 * pages, so huge-page availability is intentionally not part of the gate.)
+	 */
+	if (pg_numa_init() != -1 &&
+		pg_numa_get_max_node() >= 1)
+		ClockSweepBatchSize = Min(PG_CACHE_LINE_SIZE / (uint32) sizeof(uint32),
+								  (uint32) NBuffers);
+	else
+		ClockSweepBatchSize = 1;
 }
 
 
-- 
2.51.2



  [text/x-patch] v3-0002-Replace-the-usage_count-clock-sweep-with-a-coolin.patch (27.5K, ../../[email protected]/3-v3-0002-Replace-the-usage_count-clock-sweep-with-a-coolin.patch)
  download | inline diff:
From d8e8f0f16bd0c3610ae1ea5fea80433d670a6505 Mon Sep 17 00:00:00 2001
From: Greg Burd <[email protected]>
Date: Mon, 6 Jul 2026 19:22:45 -0400
Subject: [PATCH v3 2/3] Replace the usage_count clock sweep with a
 cooling-stage evictor

Replace the 0..5 usage_count buffer-replacement policy with a cooling-stage
clock (the LeanStore / 2Q-A1 model): a buffer is simply HOT (recently used)
or COOL (an eviction candidate), with "pinned" being the existing refcount.
There is no per-buffer access counter.

  - A demand-loaded page is admitted COOL (probationary), not HOT.  A second
    access via PinBuffer promotes it COOL -> HOT (the rescue).  So a page
    touched once -- a sequential scan -- fills and drains the COOL stage and
    is evicted from it without ever displacing the HOT working set.  Scan
    resistance is intrinsic to the replacement algorithm, which is what lets a
    later commit remove the BufferAccessStrategy ring buffers entirely.

  - The foreground sweep in StrategyGetBuffer() reclaims an already-COOL,
    unpinned buffer, pinning it with a CAS so a racing PinBuffer always wins.
    Promotion (COOL -> HOT) and demotion (HOT -> COOL) are single-bit
    transitions; only the eviction claim is a CAS.

  - The background writer maintains the supply of COOL victims.  As its LRU
    scan runs ahead of the clock hand it demotes HOT buffers to COOL, so the
    foreground finds a victim in a single pass rather than having to cool
    buffers itself.  The demotion is demand-driven -- bounded by the predicted
    allocation for the next cycle -- so it stages just enough COOL buffers
    without cooling the whole pool, and it is done under the buffer header
    lock the scan already holds.  A single second-chance reference bit
    (set by PinBuffer, cleared on the bgwriter's first pass over a HOT buffer)
    spares a recently-accessed buffer one cooling pass, keeping the genuinely
    hot set out of the COOL stage under scan pressure.  BgBufferSync also
    tracks the reusable-buffer density it directly observes on a shorter
    smoothing window, so a burst of probationary/scan COOL pages is followed
    promptly rather than averaged away.

The 4-bit usage_count field is reinterpreted in place: bit 0 is the HOT/COOL
state (BUF_COOLSTATE_ONE), bit 1 the reference bit (BUF_REFBIT).  The 64-bit
buffer-state layout -- refcount, flag and lock offsets and their StaticAsserts
-- is unchanged; only the meaning of the field and the instructions that touch
it change.  A StaticAssert requires the field to be at least 2 bits wide so a
future width change cannot push the reference bit into the flag bits.
BM_MAX_USAGE_COUNT becomes BUF_COOLSTATE_HOT (1), so the pin fast path
saturates at HOT.  Local (temp-table) buffers get the same two-state
treatment; being single-backend they need no background cooler.

Because the reference bit shares the field, the full 4-bit value can be 0..3;
readers that mean "the cooling state" must use BUF_STATE_GET_COOLSTATE(), which
masks to bit 0 and returns only 0 (COOL) or 1 (HOT), never the raw field.
contrib/pg_buffercache is updated accordingly: its usagecount column and the
pg_buffercache_summary average report the cooling state (0 = COOL, 1 = HOT),
and pg_buffercache_usage_counts() buckets on it.  Using the raw 4-bit getter
there would index its BM_MAX_USAGE_COUNT+1 = 2-element arrays with values up
to 3 and overrun the stack; masking to the cooling bit keeps the index in
range.  The reference bit is deliberately not exposed as usagecount.

Depends on the batched clock sweep from the previous commit.
---
 contrib/pg_buffercache/pg_buffercache_pages.c |   6 +-
 src/backend/storage/buffer/bufmgr.c           | 103 ++++++++++----
 src/backend/storage/buffer/freelist.c         | 128 ++++++++++++------
 src/backend/storage/buffer/localbuf.c         |  13 +-
 src/include/storage/buf_internals.h           |  63 +++++++--
 5 files changed, 229 insertions(+), 84 deletions(-)

diff --git a/contrib/pg_buffercache/pg_buffercache_pages.c b/contrib/pg_buffercache/pg_buffercache_pages.c
index 510455998aa..b94e6f6fda8 100644
--- a/contrib/pg_buffercache/pg_buffercache_pages.c
+++ b/contrib/pg_buffercache/pg_buffercache_pages.c
@@ -161,7 +161,7 @@ pg_buffercache_pages(PG_FUNCTION_ARGS)
 		reldatabase = bufHdr->tag.dbOid;
 		forknum = BufTagGetForkNum(&bufHdr->tag);
 		blocknum = bufHdr->tag.blockNum;
-		usagecount = BUF_STATE_GET_USAGECOUNT(buf_state);
+		usagecount = BUF_STATE_GET_COOLSTATE(buf_state);
 		pinning_backends = BUF_STATE_GET_REFCOUNT(buf_state);
 
 		if (buf_state & BM_DIRTY)
@@ -605,7 +605,7 @@ pg_buffercache_summary(PG_FUNCTION_ARGS)
 		if (buf_state & BM_VALID)
 		{
 			buffers_used++;
-			usagecount_total += BUF_STATE_GET_USAGECOUNT(buf_state);
+			usagecount_total += BUF_STATE_GET_COOLSTATE(buf_state);
 
 			if (buf_state & BM_DIRTY)
 				buffers_dirty++;
@@ -655,7 +655,7 @@ pg_buffercache_usage_counts(PG_FUNCTION_ARGS)
 
 		CHECK_FOR_INTERRUPTS();
 
-		usage_count = BUF_STATE_GET_USAGECOUNT(buf_state);
+		usage_count = BUF_STATE_GET_COOLSTATE(buf_state);
 		usage_counts[usage_count]++;
 
 		if (buf_state & BM_DIRTY)
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 9ab282a76d1..1173b50b7c1 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -83,6 +83,7 @@
 /* Bits in SyncOneBuffer's return value */
 #define BUF_WRITTEN				0x01
 #define BUF_REUSABLE			0x02
+#define BUF_COOLED				0x04
 
 #define RELS_BSEARCH_THRESHOLD		20
 
@@ -634,7 +635,7 @@ static void PinBuffer_Locked(BufferDesc *buf);
 static void UnpinBuffer(BufferDesc *buf);
 static void UnpinBufferNoOwner(BufferDesc *buf);
 static void BufferSync(int flags);
-static int	SyncOneBuffer(int buf_id, bool skip_recently_used,
+static int	SyncOneBuffer(int buf_id, bool skip_recently_used, bool cool_if_hot,
 						  WritebackContext *wb_context);
 static void WaitIO(BufferDesc *buf);
 static void AbortBufferIO(Buffer buffer);
@@ -2333,7 +2334,10 @@ BufferAlloc(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 	 * checkpoints, except for their "init" forks, which need to be treated
 	 * just like permanent relations.
 	 */
-	set_bits |= BM_TAG_VALID | BUF_USAGECOUNT_ONE;
+	set_bits |= BM_TAG_VALID;
+	/* Admit the newly loaded page COOL (probation); a second access via
+	 * PinBuffer promotes it to HOT.  This is what makes a one-touch scan
+	 * self-evicting -- see the cooling-state notes in buf_internals.h. */
 	if (relpersistence == RELPERSISTENCE_PERMANENT || forkNum == INIT_FORKNUM)
 		set_bits |= BM_PERMANENT;
 
@@ -3002,7 +3006,9 @@ ExtendBufferedRelShared(BufferManagerRelation bmr,
 
 			victim_buf_hdr->tag = tag;
 
-			set_bits |= BM_TAG_VALID | BUF_USAGECOUNT_ONE;
+			set_bits |= BM_TAG_VALID;
+			/* Admit COOL (probation); see the comment at the other admission
+			 * site and the cooling-state notes in buf_internals.h. */
 			if (bmr.relpersistence == RELPERSISTENCE_PERMANENT || fork == INIT_FORKNUM)
 				set_bits |= BM_PERMANENT;
 
@@ -3332,21 +3338,17 @@ PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy,
 			/* increase refcount */
 			buf_state += BUF_REFCOUNT_ONE;
 
-			if (strategy == NULL)
-			{
-				/* Default case: increase usagecount unless already max. */
-				if (BUF_STATE_GET_USAGECOUNT(buf_state) < BM_MAX_USAGE_COUNT)
-					buf_state += BUF_USAGECOUNT_ONE;
-			}
-			else
-			{
-				/*
-				 * Ring buffers shouldn't evict others from pool.  Thus we
-				 * don't make usagecount more than 1.
-				 */
-				if (BUF_STATE_GET_USAGECOUNT(buf_state) == 0)
-					buf_state += BUF_USAGECOUNT_ONE;
-			}
+			/*
+			 * Accessing a resident buffer promotes it to HOT (the 2Q rescue):
+			 * a page loaded COOL on probation becomes part of the hot working
+			 * set on its second touch.  BM_MAX_USAGE_COUNT is
+			 * BUF_COOLSTATE_HOT (1), so this saturates at HOT and never
+			 * overflows the field.  We also set the second-chance ref bit so
+			 * the bgwriter's next cooling pass spares this recently-used buffer.
+			 */
+			if (BUF_STATE_GET_COOLSTATE(buf_state) < BUF_COOLSTATE_HOT)
+				buf_state += BUF_COOLSTATE_ONE;
+			buf_state |= BUF_REFBIT;
 
 			if (pg_atomic_compare_exchange_u64(&buf->state, &old_buf_state,
 											   buf_state))
@@ -3785,7 +3787,7 @@ BufferSync(int flags)
 		 */
 		if (pg_atomic_read_u64(&bufHdr->state) & BM_CHECKPOINT_NEEDED)
 		{
-			if (SyncOneBuffer(buf_id, false, &wb_context) & BUF_WRITTEN)
+			if (SyncOneBuffer(buf_id, false, false, &wb_context) & BUF_WRITTEN)
 			{
 				TRACE_POSTGRESQL_BUFFER_SYNC_WRITTEN(buf_id);
 				PendingCheckpointerStats.buffers_written++;
@@ -3876,6 +3878,19 @@ BgBufferSync(WritebackContext *wb_context)
 	float		smoothing_samples = 16;
 	float		scan_whole_pool_milliseconds = 120000.0;
 
+	/*
+	 * The cleaner scan directly observes the reusable (COOL, unpinned) buffer
+	 * density over the region it walks, which -- with the cooling-stage
+	 * evictor -- is exactly the sweep's victim predicate.  That observation is
+	 * ground truth for the buffers about to be reused, whereas the strategy
+	 * scan's positional proxy (strategy_delta/recent_alloc) blurs a pool whose
+	 * COOL population is spatially clustered (a scan burst leaves whole regions
+	 * COOL, hot OLTP regions not).  So we let the cleaner's own sample adapt on
+	 * a shorter window than the strategy proxy, tracking a burst of
+	 * probationary/scan COOL pages within a cycle or two instead of lagging it.
+	 */
+	float		cleaner_smoothing_samples = 4;
+
 	/* Used to compute how far we scan ahead */
 	long		strategy_delta;
 	int			bufs_to_lap;
@@ -4073,7 +4088,7 @@ BgBufferSync(WritebackContext *wb_context)
 	/* Execute the LRU scan */
 	while (num_to_scan > 0 && reusable_buffers < upcoming_alloc_est)
 	{
-		int			sync_state = SyncOneBuffer(next_to_clean, true,
+		int			sync_state = SyncOneBuffer(next_to_clean, true, true,
 											   wb_context);
 
 		if (++next_to_clean >= NBuffers)
@@ -4121,7 +4136,7 @@ BgBufferSync(WritebackContext *wb_context)
 	{
 		scans_per_alloc = (float) new_strategy_delta / (float) new_recent_alloc;
 		smoothed_density += (scans_per_alloc - smoothed_density) /
-			smoothing_samples;
+			cleaner_smoothing_samples;
 
 #ifdef BGW_DEBUG
 		elog(DEBUG2, "bgwriter: cleaner density alloc=%u scan=%ld density=%.2f new smoothed=%.2f",
@@ -4140,16 +4155,26 @@ BgBufferSync(WritebackContext *wb_context)
  * If skip_recently_used is true, we don't write currently-pinned buffers, nor
  * buffers marked recently used, as these are not replacement candidates.
  *
+ * If cool_if_hot is true (the bgwriter's LRU scan), an unpinned HOT buffer is
+ * demoted HOT -> COOL as we pass it, pre-staging eviction candidates so the
+ * foreground clock sweep finds a COOL victim in a single pass instead of
+ * having to cool buffers itself (force_cool).  The demotion is done under the
+ * buffer header lock we already hold, so it needs no CAS and cannot race a
+ * concurrent demotion.  A concurrent PinBuffer promotes it back to HOT, which
+ * is the intended 2Q behavior (a re-accessed buffer is rescued).
+ *
  * Returns a bitmask containing the following flag bits:
  *	BUF_WRITTEN: we wrote the buffer.
  *	BUF_REUSABLE: buffer is available for replacement, ie, it has
- *		pin count 0 and usage count 0.
+ *		pin count 0 and is COOL (an eviction candidate).
+ *	BUF_COOLED: we demoted this buffer HOT -> COOL this call.
  *
  * (BUF_WRITTEN could be set in error if FlushBuffer finds the buffer clean
  * after locking it, but we don't care all that much.)
  */
 static int
-SyncOneBuffer(int buf_id, bool skip_recently_used, WritebackContext *wb_context)
+SyncOneBuffer(int buf_id, bool skip_recently_used, bool cool_if_hot,
+			  WritebackContext *wb_context)
 {
 	BufferDesc *bufHdr = GetBufferDescriptor(buf_id);
 	int			result = 0;
@@ -4171,8 +4196,38 @@ SyncOneBuffer(int buf_id, bool skip_recently_used, WritebackContext *wb_context)
 	 */
 	buf_state = LockBufHdr(bufHdr);
 
+	/*
+	 * Pre-cool with a second chance: if asked, act on an unpinned HOT buffer.
+	 * If its ref bit is set (accessed since our last pass), clear the ref bit
+	 * and leave it HOT -- a recently-used buffer earns one reprieve, keeping
+	 * the hot working set out of the COOL stage under scan pressure.  Only a
+	 * HOT buffer whose ref bit is already clear is demoted HOT -> COOL,
+	 * pre-staging it as an eviction candidate for the foreground sweep.  We
+	 * hold the header lock, so each transition is a plain masked store applied
+	 * atomically by UnlockBufHdrExt; we re-lock to continue the dirty-write
+	 * inspection below.
+	 */
+	if (cool_if_hot &&
+		BUF_STATE_GET_REFCOUNT(buf_state) == 0 &&
+		BUF_STATE_GET_COOLSTATE(buf_state) != BUF_COOLSTATE_COOL)
+	{
+		if (BUF_STATE_GET_REFBIT(buf_state))
+		{
+			/* second chance: consume the ref bit, stay HOT */
+			UnlockBufHdrExt(bufHdr, buf_state, 0, BUF_REFBIT, 0);
+			buf_state = LockBufHdr(bufHdr);
+		}
+		else
+		{
+			/* not re-accessed since last pass: demote to COOL */
+			UnlockBufHdrExt(bufHdr, buf_state, 0, BUF_USAGECOUNT_MASK, 0);
+			buf_state = LockBufHdr(bufHdr);
+			result |= BUF_COOLED;
+		}
+	}
+
 	if (BUF_STATE_GET_REFCOUNT(buf_state) == 0 &&
-		BUF_STATE_GET_USAGECOUNT(buf_state) == 0)
+		BUF_STATE_GET_COOLSTATE(buf_state) == BUF_COOLSTATE_COOL)
 	{
 		result |= BUF_REUSABLE;
 	}
diff --git a/src/backend/storage/buffer/freelist.c b/src/backend/storage/buffer/freelist.c
index 93fc4dd758b..cc77144224c 100644
--- a/src/backend/storage/buffer/freelist.c
+++ b/src/backend/storage/buffer/freelist.c
@@ -49,6 +49,16 @@ typedef struct
 	uint32		completePasses; /* Complete cycles of the clock-sweep */
 	pg_atomic_uint32 numBufferAllocs;	/* Buffers allocated since last reset */
 
+	/*
+	 * Number of clock-hand values a backend claims per atomic fetch-add.
+	 * Computed once at startup (see StrategyCtlShmemInit).  Kept in shared
+	 * memory rather than a backend-local static so that EXEC_BACKEND children
+	 * (which do not inherit the postmaster's statics) see the same value; a
+	 * backend-local copy would silently reset to 1 there, disabling batching
+	 * on Windows.
+	 */
+	uint32		batchSize;
+
 	/*
 	 * Bgworker process to be notified upon activity or -1 if none. See
 	 * StrategyNotifyBgWriter.
@@ -113,17 +123,6 @@ static void AddBufferToRing(BufferAccessStrategy strategy,
 static uint32 MyBatchPos = 0;
 static uint32 MyBatchEnd = 0;
 
-/*
- * Number of clock-hand values a backend claims per atomic fetch-add,
- * computed once at startup (see StrategyCtlShmemInit).  When batching is
- * enabled it is one cache line's worth of hand advance, so concurrent
- * backends sweep non-overlapping, cache-line-sized runs of the pool; the
- * global sweep order is preserved (each buffer is still visited exactly once
- * per pass).  Batching is enabled only on multi-node NUMA hardware; otherwise
- * this stays 1 and the sweep is byte-identical to the stock clock.
- */
-static uint32 ClockSweepBatchSize = 1;
-
 /*
  * ClockSweepTick - Helper routine for StrategyGetBuffer()
  *
@@ -144,7 +143,7 @@ ClockSweepTick(void)
 		 * atomic operation per batch, reducing contention by the batch size.
 		 */
 		uint32		start;
-		uint32		batch_size = ClockSweepBatchSize;
+		uint32		batch_size = StrategyControl->batchSize;
 
 		start = pg_atomic_fetch_add_u32(&StrategyControl->nextVictimBuffer,
 										batch_size);
@@ -211,6 +210,7 @@ StrategyGetBuffer(BufferAccessStrategy strategy, uint64 *buf_state, bool *from_r
 	BufferDesc *buf;
 	int			bgwprocno;
 	int			trycounter;
+	bool		force_cool;
 
 	*from_ring = false;
 
@@ -261,12 +261,32 @@ StrategyGetBuffer(BufferAccessStrategy strategy, uint64 *buf_state, bool *from_r
 	 */
 	pg_atomic_fetch_add_u32(&StrategyControl->numBufferAllocs, 1);
 
-	/* Use the "clock sweep" algorithm to find a free buffer */
+	/*
+	 * Use the cooling-stage clock sweep to find a victim.
+	 *
+	 * A buffer is HOT (recently used) or COOL (an eviction candidate).  We
+	 * prefer to reclaim an already-COOL buffer and demote a HOT buffer to
+	 * COOL only once a full sweep has found no COOL victim (force_cool) -- so
+	 * an abundant supply of COOL/probationary pages (e.g. a scan) is drained
+	 * before the hot working set is cooled.  Newly loaded pages are admitted
+	 * COOL (see BufferAlloc), so scan resistance falls out of the algorithm.
+	 *
+	 * trycounter bounds the search.  Any tick that does not produce a victim
+	 * and does not make progress -- a pinned buffer, or a HOT buffer skipped
+	 * on a prefer-COOL pass -- decrements it.  Cooling a HOT buffer (under
+	 * force_cool) is progress and resets it.  When a full pass (NBuffers)
+	 * makes no progress we escalate to force_cool so the next pass cools HOT
+	 * buffers into victims; if a force_cool pass ALSO makes no progress every
+	 * buffer is pinned and we fail, matching the stock "no unpinned buffers
+	 * available" contract.
+	 */
 	trycounter = NBuffers;
+	force_cool = false;
 	for (;;)
 	{
 		uint64		old_buf_state;
 		uint64		local_buf_state;
+		bool		no_progress = false;
 
 		buf = GetBufferDescriptor(ClockSweepTick());
 
@@ -279,25 +299,10 @@ StrategyGetBuffer(BufferAccessStrategy strategy, uint64 *buf_state, bool *from_r
 		{
 			local_buf_state = old_buf_state;
 
-			/*
-			 * If the buffer is pinned or has a nonzero usage_count, we cannot
-			 * use it; decrement the usage_count (unless pinned) and keep
-			 * scanning.
-			 */
-
+			/* If the buffer is pinned we cannot use it; keep scanning. */
 			if (BUF_STATE_GET_REFCOUNT(local_buf_state) != 0)
 			{
-				if (--trycounter == 0)
-				{
-					/*
-					 * We've scanned all the buffers without making any state
-					 * changes, so all the buffers are pinned (or were when we
-					 * looked at them). We could hope that someone will free
-					 * one eventually, but it's probably better to fail than
-					 * to risk getting stuck in an infinite loop.
-					 */
-					elog(ERROR, "no unpinned buffers available");
-				}
+				no_progress = true;
 				break;
 			}
 
@@ -308,20 +313,43 @@ StrategyGetBuffer(BufferAccessStrategy strategy, uint64 *buf_state, bool *from_r
 				continue;
 			}
 
-			if (BUF_STATE_GET_USAGECOUNT(local_buf_state) != 0)
+			if (BUF_STATE_GET_COOLSTATE(local_buf_state) != BUF_COOLSTATE_COOL)
 			{
-				local_buf_state -= BUF_USAGECOUNT_ONE;
+				/*
+				 * HOT buffer.  Prefer a COOL victim: on a normal pass just
+				 * advance the hand (no progress).  Under force_cool demote it
+				 * HOT -> COOL -- the cooling tick, which IS progress.
+				 */
+				if (!force_cool)
+				{
+					no_progress = true;
+					break;			/* advance the hand, look for COOL */
+				}
+
+				local_buf_state &= ~BUF_USAGECOUNT_MASK;	/* HOT -> COOL, clear ref bit */
 
 				if (pg_atomic_compare_exchange_u64(&buf->state, &old_buf_state,
 												   local_buf_state))
 				{
+					/*
+					 * Cooling a buffer is progress toward a victim, so reset the
+					 * counter and drop back out of force_cool.  Because we clear
+					 * force_cool after a single demotion, an all-HOT pool can take
+					 * up to ~2 full passes to yield a victim (one to discover no
+					 * COOL buffer exists, one to cool + reclaim).  That is still
+					 * within the stock clock's worst case (up to
+					 * BM_MAX_USAGE_COUNT+1 passes), and in practice the bgwriter
+					 * pre-cooling keeps a COOL victim available so force_cool
+					 * rarely fires at all.
+					 */
 					trycounter = NBuffers;
+					force_cool = false;
 					break;
 				}
 			}
 			else
 			{
-				/* pin the buffer if the CAS succeeds */
+				/* COOL and unpinned: claim it.  Pin if the CAS succeeds. */
 				local_buf_state += BUF_REFCOUNT_ONE;
 
 				if (pg_atomic_compare_exchange_u64(&buf->state, &old_buf_state,
@@ -338,6 +366,21 @@ StrategyGetBuffer(BufferAccessStrategy strategy, uint64 *buf_state, bool *from_r
 				}
 			}
 		}
+
+		/*
+		 * A tick that made no progress toward a victim counts down trycounter.
+		 * A full unproductive pass escalates to force_cool (cool HOT buffers
+		 * into victims); a second unproductive full pass means everything is
+		 * pinned, so fail rather than spin forever.  (A failed CAS above is
+		 * neither progress nor a full miss: we simply retry the same buffer.)
+		 */
+		if (no_progress && --trycounter == 0)
+		{
+			if (force_cool)
+				elog(ERROR, "no unpinned buffers available");
+			force_cool = true;
+			trycounter = NBuffers;
+		}
 	}
 }
 
@@ -452,10 +495,10 @@ StrategyCtlShmemInit(void *arg)
 	 */
 	if (pg_numa_init() != -1 &&
 		pg_numa_get_max_node() >= 1)
-		ClockSweepBatchSize = Min(PG_CACHE_LINE_SIZE / (uint32) sizeof(uint32),
-								  (uint32) NBuffers);
+		StrategyControl->batchSize = Min(PG_CACHE_LINE_SIZE / (uint32) sizeof(uint32),
+										 (uint32) NBuffers);
 	else
-		ClockSweepBatchSize = 1;
+		StrategyControl->batchSize = 1;
 }
 
 
@@ -703,14 +746,13 @@ GetBufferFromRing(BufferAccessStrategy strategy, uint64 *buf_state)
 		/*
 		 * If the buffer is pinned we cannot use it under any circumstances.
 		 *
-		 * If usage_count is 0 or 1 then the buffer is fair game (we expect 1,
-		 * since our own previous usage of the ring element would have left it
-		 * there, but it might've been decremented by clock-sweep since then).
-		 * A higher usage_count indicates someone else has touched the buffer,
-		 * so we shouldn't re-use it.
+		 * With the cooling-state replacement the field holds only COOL or HOT,
+		 * so the stock "usage_count > 1 means another backend touched it"
+		 * heuristic no longer applies: a ring element is reusable whenever it
+		 * is unpinned.  (The whole ring mechanism is removed in a later patch;
+		 * scan resistance is now intrinsic to the sweep.)
 		 */
-		if (BUF_STATE_GET_REFCOUNT(local_buf_state) != 0
-			|| BUF_STATE_GET_USAGECOUNT(local_buf_state) > 1)
+		if (BUF_STATE_GET_REFCOUNT(local_buf_state) != 0)
 			break;
 
 		/* See equivalent code in PinBuffer() */
diff --git a/src/backend/storage/buffer/localbuf.c b/src/backend/storage/buffer/localbuf.c
index 4870c8e13d0..21c08091c49 100644
--- a/src/backend/storage/buffer/localbuf.c
+++ b/src/backend/storage/buffer/localbuf.c
@@ -167,7 +167,7 @@ LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum,
 
 		buf_state = pg_atomic_read_u64(&bufHdr->state);
 		buf_state &= ~(BUF_FLAG_MASK | BUF_USAGECOUNT_MASK);
-		buf_state |= BM_TAG_VALID | BUF_USAGECOUNT_ONE;
+		buf_state |= BM_TAG_VALID;	/* admit COOL (probation) */
 		pg_atomic_unlocked_write_u64(&bufHdr->state, buf_state);
 
 		*foundPtr = false;
@@ -248,9 +248,10 @@ GetLocalVictimBuffer(void)
 		{
 			uint64		buf_state = pg_atomic_read_u64(&bufHdr->state);
 
-			if (BUF_STATE_GET_USAGECOUNT(buf_state) > 0)
+			if (BUF_STATE_GET_COOLSTATE(buf_state) != BUF_COOLSTATE_COOL)
 			{
-				buf_state -= BUF_USAGECOUNT_ONE;
+				/* HOT: give it a second chance, cool it and keep scanning. */
+				buf_state -= BUF_COOLSTATE_ONE;
 				pg_atomic_unlocked_write_u64(&bufHdr->state, buf_state);
 				trycounter = NLocBuffer;
 			}
@@ -454,7 +455,7 @@ ExtendBufferedRelLocal(BufferManagerRelation bmr,
 
 			victim_buf_hdr->tag = tag;
 
-			buf_state |= BM_TAG_VALID | BUF_USAGECOUNT_ONE;
+			buf_state |= BM_TAG_VALID;	/* admit COOL (probation) */
 
 			pg_atomic_unlocked_write_u64(&victim_buf_hdr->state, buf_state);
 
@@ -839,9 +840,9 @@ PinLocalBuffer(BufferDesc *buf_hdr, bool adjust_usagecount)
 		NLocalPinnedBuffers++;
 		buf_state += BUF_REFCOUNT_ONE;
 		if (adjust_usagecount &&
-			BUF_STATE_GET_USAGECOUNT(buf_state) < BM_MAX_USAGE_COUNT)
+			BUF_STATE_GET_COOLSTATE(buf_state) < BUF_COOLSTATE_HOT)
 		{
-			buf_state += BUF_USAGECOUNT_ONE;
+			buf_state += BUF_COOLSTATE_ONE;
 		}
 		pg_atomic_unlocked_write_u64(&buf_hdr->state, buf_state);
 
diff --git a/src/include/storage/buf_internals.h b/src/include/storage/buf_internals.h
index 2a1ffcc6c5c..89b11051577 100644
--- a/src/include/storage/buf_internals.h
+++ b/src/include/storage/buf_internals.h
@@ -67,6 +67,50 @@ StaticAssertDecl(BUF_REFCOUNT_BITS + BUF_USAGECOUNT_BITS + BUF_FLAG_BITS + BUF_L
 #define BUF_USAGECOUNT_ONE \
 	(UINT64CONST(1) << BUF_REFCOUNT_BITS)
 
+/*
+ * Cooling state (LeanStore / 2Q-A1 cooling-stage clock sweep).
+ *
+ * The field historically used for the 0..5 usage_count now holds a single
+ * cooling-state bit: HOT (recently accessed, not an eviction candidate) or
+ * COOL (an eviction candidate).  We reuse BUF_USAGECOUNT_ONE as the unit so
+ * the buffer-state bit geography -- refcount, flag, and lock offsets, and the
+ * 64-bit StaticAsserts -- is unchanged; only the meaning of the field and the
+ * instructions that touch it change.
+ *
+ * A demand-loaded page is admitted COOL (probation); a second access promotes
+ * it to HOT (the rescue).  The sweep prefers COOL victims and demotes HOT to
+ * COOL only when a full pass finds no COOL victim.  So a one-touch scan fills
+ * and drains the COOL stage without displacing the HOT working set -- scan
+ * resistance intrinsic to the replacement algorithm.
+ */
+#define BUF_COOLSTATE_COOL	0
+#define BUF_COOLSTATE_HOT	1
+#define BUF_COOLSTATE_ONE	BUF_USAGECOUNT_ONE
+
+/*
+ * Second-chance reference bit, bit 1 of the (former usagecount) field, one
+ * position above the cooling-state bit.  PinBuffer sets it on every access.
+ * The bgwriter's pre-cooling gives a HOT buffer a second chance: the first
+ * time it passes a HOT buffer whose ref bit is set, it clears the ref bit and
+ * leaves the buffer HOT; only a HOT buffer whose ref bit is already clear (not
+ * re-accessed since the previous bgwriter pass) is demoted to COOL.  This
+ * keeps genuinely-hot pages out of the COOL stage (protecting the working set
+ * from being cooled under scan pressure) while leaving the foreground sweep a
+ * single-pass search over the pre-staged COOL buffers.  A separate bit (not a
+ * count) so it stays a plain masked store under the header lock.
+ */
+#define BUF_REFBIT			(UINT64CONST(2) << BUF_REFCOUNT_BITS)
+#define BUF_STATE_GET_REFBIT(state)	(((state) & BUF_REFBIT) != 0)
+
+/*
+ * The cooling state occupies bit 0 and the reference bit occupies bit 1 of
+ * the (former usagecount) field, so the field must be at least 2 bits wide.
+ * Assert it here so a future change to BUF_USAGECOUNT_BITS cannot silently
+ * push BUF_REFBIT up into the flag bits.
+ */
+StaticAssertDecl(BUF_USAGECOUNT_BITS >= 2,
+				 "cooling state + reference bit need at least 2 bits in the usagecount field");
+
 /* flags related definitions */
 #define BUF_FLAG_SHIFT \
 	(BUF_REFCOUNT_BITS + BUF_USAGECOUNT_BITS)
@@ -92,6 +136,11 @@ StaticAssertDecl(BUF_REFCOUNT_BITS + BUF_USAGECOUNT_BITS + BUF_FLAG_BITS + BUF_L
 #define BUF_STATE_GET_USAGECOUNT(state) \
 	((uint32)(((state) & BUF_USAGECOUNT_MASK) >> BUF_USAGECOUNT_SHIFT))
 
+/* Cooling state (HOT/COOL) from buffer state -- bit 0 of the field only, so
+ * the second-chance ref bit (bit 1) does not perturb the HOT/COOL test. */
+#define BUF_STATE_GET_COOLSTATE(state) \
+	((uint32) (((state) >> BUF_USAGECOUNT_SHIFT) & 1))
+
 /*
  * Flags for buffer descriptors
  *
@@ -134,17 +183,15 @@ StaticAssertDecl(MAX_BACKENDS_BITS <= (BUF_LOCK_BITS - 2),
 
 
 /*
- * The maximum allowed value of usage_count represents a tradeoff between
- * accuracy and speed of the clock-sweep buffer management algorithm.  A
- * large value (comparable to NBuffers) would approximate LRU semantics.
- * But it can take as many as BM_MAX_USAGE_COUNT+1 complete cycles of the
- * clock-sweep hand to find a free buffer, so in practice we don't want the
- * value to be very large.
+ * The cooling state is a single bit (HOT/COOL); the maximum value stored in
+ * the field is therefore BUF_COOLSTATE_HOT.  Retained under the historical
+ * name BM_MAX_USAGE_COUNT so the pin fast path ("promote unless already at
+ * max") reads naturally.
  */
-#define BM_MAX_USAGE_COUNT	5
+#define BM_MAX_USAGE_COUNT	BUF_COOLSTATE_HOT
 
 StaticAssertDecl(BM_MAX_USAGE_COUNT < (UINT64CONST(1) << BUF_USAGECOUNT_BITS),
-				 "BM_MAX_USAGE_COUNT doesn't fit in BUF_USAGECOUNT_BITS bits");
+				 "cooling state doesn't fit in BUF_USAGECOUNT_BITS bits");
 
 /*
  * Buffer tag identifies which disk block the buffer contains.
-- 
2.51.2



  [text/x-patch] v3-0003-Remove-BufferAccessStrategy-scan-resistance-is-no.patch (177.4K, ../../[email protected]/4-v3-0003-Remove-BufferAccessStrategy-scan-resistance-is-no.patch)
  download | inline diff:
From b404c46f18946bd118bb1cf77dc0b306f7cb8458 Mon Sep 17 00:00:00 2001
From: Greg Burd <[email protected]>
Date: Tue, 7 Jul 2026 08:45:39 -0400
Subject: [PATCH v3 3/3] Remove BufferAccessStrategy; scan resistance is now
 intrinsic

The cooling-stage evictor admits demand-loaded pages COOL and promotes them to
HOT only on a second access, so a one-touch sequential scan fills and drains
the COOL stage without displacing the hot working set.  Scan resistance is
therefore a property of the replacement algorithm itself, and the
BufferAccessStrategy ring buffers that previously provided it are dead weight.
Remove them end to end.

Deleted:
  - the BufferAccessStrategy type and the BufferAccessStrategyType enum
    (BAS_NORMAL/BULKREAD/BULKWRITE/VACUUM);
  - the ring machinery in freelist.c (GetAccessStrategy[WithSize],
    GetAccessStrategyBufferCount, GetAccessStrategyPinLimit,
    FreeAccessStrategy, GetBufferFromRing, AddBufferToRing,
    StrategyRejectBuffer, IOContextForStrategy);
  - the strategy parameter from ReadBufferExtended, ReadBufferWithoutRelcache,
    the ExtendBufferedRel* family, StrategyGetBuffer, read_stream_begin_*,
    and every scan/vacuum/analyze/index-AM caller;
  - the strategy fields on HeapScanDescData, IndexScanDescData,
    BulkInsertStateData, ReadBuffersOperation, and ReadStream;
  - _hash_getbuf_with_strategy (identical to _hash_getbuf without a strategy).

pg_stat_io's per-strategy IO contexts collapse: IOCONTEXT_BULKREAD,
IOCONTEXT_BULKWRITE and IOCONTEXT_VACUUM are removed, leaving IOCONTEXT_INIT
and IOCONTEXT_NORMAL.  IOOP_REUSE only ever occurred while recycling a ring
buffer, so it is no longer tracked; GetVictimBuffer counts IOOP_EVICT only.

The vacuum_buffer_usage_limit GUC and the VACUUM/ANALYZE (BUFFER_USAGE_LIMIT
...) option are removed, along with the VacuumBufferUsageLimit global, the
ring-size plumbing through VacuumParams and parallel vacuum, and vacuumdb's
--buffer-usage-limit client option.  read_stream's per-backend pin budget is
now enforced solely by GetPinLimit()/GetLocalPinLimit(), which already applied
and is unchanged for the (formerly universal) no-strategy case.

Documentation and the stats/amcheck regression tests are updated to drop the
removed contexts and options.
---
 contrib/amcheck/expected/check_heap.out       |  16 +-
 contrib/amcheck/sql/check_heap.sql            |  16 +-
 contrib/amcheck/verify_gin.c                  |  18 +-
 contrib/amcheck/verify_heapam.c               |   3 -
 contrib/amcheck/verify_nbtree.c               |  11 +-
 contrib/bloom/blscan.c                        |   8 -
 contrib/bloom/blutils.c                       |   4 +-
 contrib/bloom/blvacuum.c                      |   2 -
 contrib/pageinspect/rawpage.c                 |   2 +-
 contrib/pg_prewarm/autoprewarm.c              |   1 -
 contrib/pg_prewarm/pg_prewarm.c               |   1 -
 contrib/pg_visibility/pg_visibility.c         |   7 +-
 contrib/pgstattuple/pgstatapprox.c            |   3 -
 contrib/pgstattuple/pgstatindex.c             |   9 +-
 contrib/pgstattuple/pgstattuple.c             |  37 +-
 doc/src/sgml/config.sgml                      |  30 --
 doc/src/sgml/monitoring.sgml                  |  38 +-
 doc/src/sgml/ref/analyze.sgml                 |  21 -
 doc/src/sgml/ref/vacuum.sgml                  |  25 -
 src/backend/access/brin/brin.c                |  11 +-
 src/backend/access/brin/brin_revmap.c         |   2 +-
 src/backend/access/gin/gininsert.c            |   4 +-
 src/backend/access/gin/ginutil.c              |   2 +-
 src/backend/access/gin/ginvacuum.c            |  17 +-
 src/backend/access/gist/gist.c                |   2 +-
 src/backend/access/gist/gistutil.c            |   2 +-
 src/backend/access/gist/gistvacuum.c          |  10 +-
 src/backend/access/hash/hash.c                |  13 +-
 src/backend/access/hash/hashovfl.c            |  55 +--
 src/backend/access/hash/hashpage.c            |  40 +-
 src/backend/access/heap/heapam.c              |  31 +-
 src/backend/access/heap/heapam_handler.c      |   4 +-
 src/backend/access/heap/hio.c                 |  13 +-
 src/backend/access/heap/vacuumlazy.c          |  22 +-
 src/backend/access/heap/visibilitymap.c       |   4 +-
 src/backend/access/nbtree/nbtpage.c           |   2 +-
 src/backend/access/nbtree/nbtree.c            |   4 +-
 src/backend/access/spgist/spgutils.c          |   2 +-
 src/backend/access/spgist/spgvacuum.c         |   4 +-
 src/backend/access/transam/xloginsert.c       |   2 +-
 src/backend/access/transam/xlogutils.c        |   3 +-
 src/backend/catalog/index.c                   |   1 -
 src/backend/commands/analyze.c                |   7 +-
 src/backend/commands/dbcommands.c             |   6 +-
 src/backend/commands/repack.c                 |   2 +-
 src/backend/commands/sequence.c               |   2 +-
 src/backend/commands/vacuum.c                 | 118 +----
 src/backend/commands/vacuumparallel.c         |  21 +-
 src/backend/postmaster/autovacuum.c           |  29 +-
 src/backend/postmaster/datachecksum_state.c   |  33 +-
 src/backend/storage/aio/read_stream.c         |  17 +-
 src/backend/storage/buffer/README             |  44 --
 src/backend/storage/buffer/bufmgr.c           | 185 +++-----
 src/backend/storage/buffer/freelist.c         | 434 +-----------------
 src/backend/storage/freespace/freespace.c     |   4 +-
 src/backend/storage/smgr/md.c                 |  11 +-
 src/backend/utils/activity/pgstat_io.c        |  45 +-
 src/backend/utils/init/globals.c              |   1 -
 src/backend/utils/misc/guc_parameters.dat     |  10 -
 src/backend/utils/misc/postgresql.conf.sample |   3 -
 src/bin/scripts/vacuumdb.c                    |  13 -
 src/bin/scripts/vacuuming.c                   |  21 -
 src/bin/scripts/vacuuming.h                   |   1 -
 src/include/access/genam.h                    |   1 -
 src/include/access/hash.h                     |   9 +-
 src/include/access/heapam.h                   |   4 +-
 src/include/access/hio.h                      |   1 -
 src/include/access/tableam.h                  |   8 +-
 src/include/commands/vacuum.h                 |   8 +-
 src/include/miscadmin.h                       |   9 -
 src/include/pgstat.h                          |   5 +-
 src/include/storage/buf.h                     |   7 -
 src/include/storage/buf_internals.h           |   6 +-
 src/include/storage/bufmgr.h                  |  32 +-
 src/include/storage/read_stream.h             |   5 +-
 src/include/utils/guc_hooks.h                 |   2 -
 src/test/modules/test_aio/test_aio.c          |   7 +-
 src/test/regress/expected/stats.out           | 120 +----
 src/test/regress/expected/vacuum.out          |  19 -
 src/test/regress/sql/stats.sql                |  42 --
 src/test/regress/sql/vacuum.sql               |  15 -
 src/tools/pgindent/typedefs.list              |   2 -
 82 files changed, 252 insertions(+), 1559 deletions(-)

diff --git a/contrib/amcheck/expected/check_heap.out b/contrib/amcheck/expected/check_heap.out
index 979e5e84e72..569b0202f1c 100644
--- a/contrib/amcheck/expected/check_heap.out
+++ b/contrib/amcheck/expected/check_heap.out
@@ -67,11 +67,11 @@ INSERT INTO heaptest (a, b)
 	(SELECT gs, repeat('x', gs)
 		FROM generate_series(1,50) gs);
 -- pg_stat_io test:
--- verify_heapam always uses a BAS_BULKREAD BufferAccessStrategy, whereas a
--- sequential scan does so only if the table is large enough when compared to
--- shared buffers (see initscan()). CREATE DATABASE ... also unconditionally
--- uses a BAS_BULKREAD strategy, but we have chosen to use a tablespace and
--- verify_heapam to provide coverage instead of adding another expensive
+-- verify_heapam reads the heap through the buffer manager; with the
+-- cooling-stage clock sweep there are no per-strategy IO contexts, so the
+-- reads are counted in the 'normal' context.  CREATE DATABASE ... likewise
+-- reads through the normal context, but we have chosen to use a tablespace
+-- and verify_heapam to provide coverage instead of adding another expensive
 -- operation to the main regression test suite.
 --
 -- Create an alternative tablespace and move the heaptest table to it, causing
@@ -83,7 +83,7 @@ INSERT INTO heaptest (a, b)
 SET allow_in_place_tablespaces = true;
 CREATE TABLESPACE regress_test_stats_tblspc LOCATION '';
 SELECT sum(reads) AS stats_bulkreads_before
-  FROM pg_stat_io WHERE context = 'bulkread' \gset
+  FROM pg_stat_io WHERE context = 'normal' AND object = 'relation' \gset
 BEGIN;
 ALTER TABLE heaptest SET TABLESPACE regress_test_stats_tblspc;
 -- Check that valid options are not rejected nor corruption reported
@@ -111,7 +111,7 @@ SELECT * FROM verify_heapam(relation := 'heaptest', startblock := 0, endblock :=
 COMMIT;
 -- verify_heapam should have read in the page written out by
 --   ALTER TABLE ... SET TABLESPACE ...
--- causing an additional bulkread, which should be reflected in pg_stat_io.
+-- causing additional reads, which should be reflected in pg_stat_io.
 SELECT pg_stat_force_next_flush();
  pg_stat_force_next_flush 
 --------------------------
@@ -119,7 +119,7 @@ SELECT pg_stat_force_next_flush();
 (1 row)
 
 SELECT sum(reads) AS stats_bulkreads_after
-  FROM pg_stat_io WHERE context = 'bulkread' \gset
+  FROM pg_stat_io WHERE context = 'normal' AND object = 'relation' \gset
 SELECT :stats_bulkreads_after > :stats_bulkreads_before;
  ?column? 
 ----------
diff --git a/contrib/amcheck/sql/check_heap.sql b/contrib/amcheck/sql/check_heap.sql
index 1745bae634e..61f710dbeda 100644
--- a/contrib/amcheck/sql/check_heap.sql
+++ b/contrib/amcheck/sql/check_heap.sql
@@ -27,11 +27,11 @@ INSERT INTO heaptest (a, b)
 		FROM generate_series(1,50) gs);
 
 -- pg_stat_io test:
--- verify_heapam always uses a BAS_BULKREAD BufferAccessStrategy, whereas a
--- sequential scan does so only if the table is large enough when compared to
--- shared buffers (see initscan()). CREATE DATABASE ... also unconditionally
--- uses a BAS_BULKREAD strategy, but we have chosen to use a tablespace and
--- verify_heapam to provide coverage instead of adding another expensive
+-- verify_heapam reads the heap through the buffer manager; with the
+-- cooling-stage clock sweep there are no per-strategy IO contexts, so the
+-- reads are counted in the 'normal' context.  CREATE DATABASE ... likewise
+-- reads through the normal context, but we have chosen to use a tablespace
+-- and verify_heapam to provide coverage instead of adding another expensive
 -- operation to the main regression test suite.
 --
 -- Create an alternative tablespace and move the heaptest table to it, causing
@@ -43,7 +43,7 @@ INSERT INTO heaptest (a, b)
 SET allow_in_place_tablespaces = true;
 CREATE TABLESPACE regress_test_stats_tblspc LOCATION '';
 SELECT sum(reads) AS stats_bulkreads_before
-  FROM pg_stat_io WHERE context = 'bulkread' \gset
+  FROM pg_stat_io WHERE context = 'normal' AND object = 'relation' \gset
 BEGIN;
 ALTER TABLE heaptest SET TABLESPACE regress_test_stats_tblspc;
 -- Check that valid options are not rejected nor corruption reported
@@ -56,10 +56,10 @@ COMMIT;
 
 -- verify_heapam should have read in the page written out by
 --   ALTER TABLE ... SET TABLESPACE ...
--- causing an additional bulkread, which should be reflected in pg_stat_io.
+-- causing additional reads, which should be reflected in pg_stat_io.
 SELECT pg_stat_force_next_flush();
 SELECT sum(reads) AS stats_bulkreads_after
-  FROM pg_stat_io WHERE context = 'bulkread' \gset
+  FROM pg_stat_io WHERE context = 'normal' AND object = 'relation' \gset
 SELECT :stats_bulkreads_after > :stats_bulkreads_before;
 
 CREATE ROLE regress_heaptest_role;
diff --git a/contrib/amcheck/verify_gin.c b/contrib/amcheck/verify_gin.c
index fa06689ed5b..ef2e64475c6 100644
--- a/contrib/amcheck/verify_gin.c
+++ b/contrib/amcheck/verify_gin.c
@@ -63,8 +63,7 @@ static void gin_check_parent_keys_consistency(Relation rel,
 static void check_index_page(Relation rel, Buffer buffer, BlockNumber blockNo);
 static IndexTuple gin_refind_parent(Relation rel,
 									BlockNumber parentblkno,
-									BlockNumber childblkno,
-									BufferAccessStrategy strategy);
+									BlockNumber childblkno);
 static ItemId PageGetItemIdCareful(Relation rel, BlockNumber block, Page page,
 								   OffsetNumber offset);
 
@@ -133,7 +132,6 @@ ginReadTupleWithoutState(IndexTuple itup, int *nitems)
 static void
 gin_check_posting_tree_parent_keys_consistency(Relation rel, BlockNumber posting_tree_root)
 {
-	BufferAccessStrategy strategy = GetAccessStrategy(BAS_BULKREAD);
 	GinPostingTreeScanItem *stack;
 	MemoryContext mctx;
 	MemoryContext oldcontext;
@@ -171,8 +169,7 @@ gin_check_posting_tree_parent_keys_consistency(Relation rel, BlockNumber posting
 
 		CHECK_FOR_INTERRUPTS();
 
-		buffer = ReadBufferExtended(rel, MAIN_FORKNUM, stack->blkno,
-									RBM_NORMAL, strategy);
+		buffer = ReadBufferExtended(rel, MAIN_FORKNUM, stack->blkno, RBM_NORMAL);
 		LockBuffer(buffer, GIN_SHARE);
 		page = BufferGetPage(buffer);
 
@@ -391,7 +388,6 @@ gin_check_parent_keys_consistency(Relation rel,
 								  void *callback_state,
 								  bool readonly)
 {
-	BufferAccessStrategy strategy = GetAccessStrategy(BAS_BULKREAD);
 	GinScanItem *stack;
 	MemoryContext mctx;
 	MemoryContext oldcontext;
@@ -430,8 +426,7 @@ gin_check_parent_keys_consistency(Relation rel,
 
 		CHECK_FOR_INTERRUPTS();
 
-		buffer = ReadBufferExtended(rel, MAIN_FORKNUM, stack->blkno,
-									RBM_NORMAL, strategy);
+		buffer = ReadBufferExtended(rel, MAIN_FORKNUM, stack->blkno, RBM_NORMAL);
 		LockBuffer(buffer, GIN_SHARE);
 		page = BufferGetPage(buffer);
 		maxoff = PageGetMaxOffsetNumber(page);
@@ -567,7 +562,7 @@ gin_check_parent_keys_consistency(Relation rel,
 					 */
 					pfree(stack->parenttup);
 					stack->parenttup = gin_refind_parent(rel, stack->parentblk,
-														 stack->blkno, strategy);
+														 stack->blkno);
 
 					/* We found it - make a final check before failing */
 					if (!stack->parenttup)
@@ -719,7 +714,7 @@ check_index_page(Relation rel, Buffer buffer, BlockNumber blockNo)
  */
 static IndexTuple
 gin_refind_parent(Relation rel, BlockNumber parentblkno,
-				  BlockNumber childblkno, BufferAccessStrategy strategy)
+				  BlockNumber childblkno)
 {
 	Buffer		parentbuf;
 	Page		parentpage;
@@ -727,8 +722,7 @@ gin_refind_parent(Relation rel, BlockNumber parentblkno,
 				parent_maxoff;
 	IndexTuple	result = NULL;
 
-	parentbuf = ReadBufferExtended(rel, MAIN_FORKNUM, parentblkno, RBM_NORMAL,
-								   strategy);
+	parentbuf = ReadBufferExtended(rel, MAIN_FORKNUM, parentblkno, RBM_NORMAL);
 
 	LockBuffer(parentbuf, GIN_SHARE);
 	parentpage = BufferGetPage(parentbuf);
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 20ff58aa782..e4336eb5fc2 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -126,7 +126,6 @@ typedef struct HeapCheckContext
 	 * recent block in the buffer yielded by the read stream API.
 	 */
 	BlockNumber blkno;
-	BufferAccessStrategy bstrategy;
 	Buffer		buffer;
 	Page		page;
 
@@ -374,7 +373,6 @@ verify_heapam(PG_FUNCTION_ARGS)
 		PG_RETURN_NULL();
 	}
 
-	ctx.bstrategy = GetAccessStrategy(BAS_BULKREAD);
 	ctx.buffer = InvalidBuffer;
 	ctx.page = NULL;
 
@@ -472,7 +470,6 @@ verify_heapam(PG_FUNCTION_ARGS)
 	}
 
 	stream = read_stream_begin_relation(stream_flags,
-										ctx.bstrategy,
 										ctx.rel,
 										MAIN_FORKNUM,
 										stream_cb,
diff --git a/contrib/amcheck/verify_nbtree.c b/contrib/amcheck/verify_nbtree.c
index 3ef2d66f826..18cd8928fdd 100644
--- a/contrib/amcheck/verify_nbtree.c
+++ b/contrib/amcheck/verify_nbtree.c
@@ -88,8 +88,6 @@ typedef struct BtreeCheckState
 	bool		checkunique;
 	/* Per-page context */
 	MemoryContext targetcontext;
-	/* Buffer access strategy */
-	BufferAccessStrategy checkstrategy;
 
 	/*
 	 * Info for uniqueness checking. Fill this field and the one below once
@@ -490,7 +488,6 @@ bt_check_every_level(Relation rel, Relation heaprel, bool heapkeyspace,
 	state->targetcontext = AllocSetContextCreate(CurrentMemoryContext,
 												 "amcheck context",
 												 ALLOCSET_DEFAULT_SIZES);
-	state->checkstrategy = GetAccessStrategy(BAS_BULKREAD);
 
 	/* Get true root block from meta-page */
 	metapage = palloc_btree_page(state, BTREE_METAPAGE);
@@ -1115,7 +1112,7 @@ bt_recheck_sibling_links(BtreeCheckState *state,
 
 		/* Couple locks in the usual order for nbtree:  Left to right */
 		lbuf = ReadBufferExtended(state->rel, MAIN_FORKNUM, leftcurrent,
-								  RBM_NORMAL, state->checkstrategy);
+								  RBM_NORMAL);
 		LockBuffer(lbuf, BT_READ);
 		_bt_checkpage(state->rel, lbuf);
 		page = BufferGetPage(lbuf);
@@ -1138,8 +1135,7 @@ bt_recheck_sibling_links(BtreeCheckState *state,
 		if (newtargetblock != leftcurrent)
 		{
 			newtargetbuf = ReadBufferExtended(state->rel, MAIN_FORKNUM,
-											  newtargetblock, RBM_NORMAL,
-											  state->checkstrategy);
+											  newtargetblock, RBM_NORMAL);
 			LockBuffer(newtargetbuf, BT_READ);
 			_bt_checkpage(state->rel, newtargetbuf);
 			page = BufferGetPage(newtargetbuf);
@@ -3300,8 +3296,7 @@ palloc_btree_page(BtreeCheckState *state, BlockNumber blocknum)
 	 * We copy the page into local storage to avoid holding pin on the buffer
 	 * longer than we must.
 	 */
-	buffer = ReadBufferExtended(state->rel, MAIN_FORKNUM, blocknum, RBM_NORMAL,
-								state->checkstrategy);
+	buffer = ReadBufferExtended(state->rel, MAIN_FORKNUM, blocknum, RBM_NORMAL);
 	LockBuffer(buffer, BT_READ);
 
 	/*
diff --git a/contrib/bloom/blscan.c b/contrib/bloom/blscan.c
index 1a0e42021ec..ed62d5c04e6 100644
--- a/contrib/bloom/blscan.c
+++ b/contrib/bloom/blscan.c
@@ -80,7 +80,6 @@ blgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 	BlockNumber blkno,
 				npages;
 	int			i;
-	BufferAccessStrategy bas;
 	BloomScanOpaque so = (BloomScanOpaque) scan->opaque;
 	BlockRangeReadStreamPrivate p;
 	ReadStream *stream;
@@ -113,11 +112,6 @@ blgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 		}
 	}
 
-	/*
-	 * We're going to read the whole index. This is why we use appropriate
-	 * buffer access strategy.
-	 */
-	bas = GetAccessStrategy(BAS_BULKREAD);
 	npages = RelationGetNumberOfBlocks(scan->indexRelation);
 	pgstat_count_index_scan(scan->indexRelation);
 	if (scan->instrument)
@@ -133,7 +127,6 @@ blgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 	 */
 	stream = read_stream_begin_relation(READ_STREAM_FULL |
 										READ_STREAM_USE_BATCHING,
-										bas,
 										scan->indexRelation,
 										MAIN_FORKNUM,
 										block_range_read_stream_cb,
@@ -184,7 +177,6 @@ blgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 
 	Assert(read_stream_next_buffer(stream, NULL) == InvalidBuffer);
 	read_stream_end(stream);
-	FreeAccessStrategy(bas);
 
 	return ntids;
 }
diff --git a/contrib/bloom/blutils.c b/contrib/bloom/blutils.c
index 5111cdc6dd6..6c1ec15c59c 100644
--- a/contrib/bloom/blutils.c
+++ b/contrib/bloom/blutils.c
@@ -392,7 +392,7 @@ BloomNewBuffer(Relation index)
 	}
 
 	/* Must extend the file */
-	buffer = ExtendBufferedRel(BMR_REL(index), MAIN_FORKNUM, NULL,
+	buffer = ExtendBufferedRel(BMR_REL(index), MAIN_FORKNUM,
 							   EB_LOCK_FIRST);
 
 	return buffer;
@@ -460,7 +460,7 @@ BloomInitMetapage(Relation index, ForkNumber forknum)
 	 * block number 0 (BLOOM_METAPAGE_BLKNO).  No need to hold the extension
 	 * lock because there cannot be concurrent inserters yet.
 	 */
-	metaBuffer = ReadBufferExtended(index, forknum, P_NEW, RBM_NORMAL, NULL);
+	metaBuffer = ReadBufferExtended(index, forknum, P_NEW, RBM_NORMAL);
 	LockBuffer(metaBuffer, BUFFER_LOCK_EXCLUSIVE);
 	Assert(BufferGetBlockNumber(metaBuffer) == BLOOM_METAPAGE_BLKNO);
 
diff --git a/contrib/bloom/blvacuum.c b/contrib/bloom/blvacuum.c
index 6beb1c20ebb..08d7705e365 100644
--- a/contrib/bloom/blvacuum.c
+++ b/contrib/bloom/blvacuum.c
@@ -66,7 +66,6 @@ blbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 	stream = read_stream_begin_relation(READ_STREAM_MAINTENANCE |
 										READ_STREAM_FULL |
 										READ_STREAM_USE_BATCHING,
-										info->strategy,
 										index,
 										MAIN_FORKNUM,
 										block_range_read_stream_cb,
@@ -219,7 +218,6 @@ blvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats)
 	stream = read_stream_begin_relation(READ_STREAM_MAINTENANCE |
 										READ_STREAM_FULL |
 										READ_STREAM_USE_BATCHING,
-										info->strategy,
 										index,
 										MAIN_FORKNUM,
 										block_range_read_stream_cb,
diff --git a/contrib/pageinspect/rawpage.c b/contrib/pageinspect/rawpage.c
index d136593edb2..bbe8edf9161 100644
--- a/contrib/pageinspect/rawpage.c
+++ b/contrib/pageinspect/rawpage.c
@@ -188,7 +188,7 @@ get_raw_page_internal(text *relname, ForkNumber forknum, BlockNumber blkno)
 
 	/* Take a verbatim copy of the page */
 
-	buf = ReadBufferExtended(rel, forknum, blkno, RBM_NORMAL, NULL);
+	buf = ReadBufferExtended(rel, forknum, blkno, RBM_NORMAL);
 	LockBuffer(buf, BUFFER_LOCK_SHARE);
 
 	memcpy(raw_page_data, BufferGetPage(buf), BLCKSZ);
diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index deb4c2671b5..33ad88ea8b1 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -630,7 +630,6 @@ autoprewarm_database_main(Datum main_arg)
 			stream = read_stream_begin_relation(READ_STREAM_MAINTENANCE |
 												READ_STREAM_DEFAULT |
 												READ_STREAM_USE_BATCHING,
-												NULL,
 												rel,
 												p.forknum,
 												apw_read_stream_next_block,
diff --git a/contrib/pg_prewarm/pg_prewarm.c b/contrib/pg_prewarm/pg_prewarm.c
index c2716086693..716a6754a7e 100644
--- a/contrib/pg_prewarm/pg_prewarm.c
+++ b/contrib/pg_prewarm/pg_prewarm.c
@@ -251,7 +251,6 @@ pg_prewarm(PG_FUNCTION_ARGS)
 		stream = read_stream_begin_relation(READ_STREAM_MAINTENANCE |
 											READ_STREAM_FULL |
 											READ_STREAM_USE_BATCHING,
-											NULL,
 											rel,
 											forkNumber,
 											block_range_read_stream_cb,
diff --git a/contrib/pg_visibility/pg_visibility.c b/contrib/pg_visibility/pg_visibility.c
index dfab0b64cf5..ccb829a09eb 100644
--- a/contrib/pg_visibility/pg_visibility.c
+++ b/contrib/pg_visibility/pg_visibility.c
@@ -488,7 +488,6 @@ collect_visibility_data(Oid relid, bool include_pd)
 	vbits	   *info;
 	BlockNumber blkno;
 	Buffer		vmbuffer = InvalidBuffer;
-	BufferAccessStrategy bstrategy = GetAccessStrategy(BAS_BULKREAD);
 	BlockRangeReadStreamPrivate p;
 	ReadStream *stream = NULL;
 
@@ -514,7 +513,6 @@ collect_visibility_data(Oid relid, bool include_pd)
 		 */
 		stream = read_stream_begin_relation(READ_STREAM_FULL |
 											READ_STREAM_USE_BATCHING,
-											bstrategy,
 											rel,
 											MAIN_FORKNUM,
 											block_range_read_stream_cb,
@@ -538,8 +536,7 @@ collect_visibility_data(Oid relid, bool include_pd)
 
 		/*
 		 * Page-level data requires reading every block, so only get it if the
-		 * caller needs it.  Use a buffer access strategy, too, to prevent
-		 * cache-trashing.
+		 * caller needs it.
 		 */
 		if (include_pd)
 		{
@@ -700,7 +697,6 @@ collect_corrupt_items(Oid relid, bool all_visible, bool all_frozen)
 	Relation	rel;
 	corrupt_items *items;
 	Buffer		vmbuffer = InvalidBuffer;
-	BufferAccessStrategy bstrategy = GetAccessStrategy(BAS_BULKREAD);
 	TransactionId OldestXmin = InvalidTransactionId;
 	struct collect_corrupt_items_read_stream_private p;
 	ReadStream *stream;
@@ -734,7 +730,6 @@ collect_corrupt_items(Oid relid, bool all_visible, bool all_frozen)
 	p.all_frozen = all_frozen;
 	p.all_visible = all_visible;
 	stream = read_stream_begin_relation(READ_STREAM_FULL,
-										bstrategy,
 										rel,
 										MAIN_FORKNUM,
 										collect_corrupt_items_read_stream_next_block,
diff --git a/contrib/pgstattuple/pgstatapprox.c b/contrib/pgstattuple/pgstatapprox.c
index 21e0b50fb4b..8e17d48991e 100644
--- a/contrib/pgstattuple/pgstatapprox.c
+++ b/contrib/pgstattuple/pgstatapprox.c
@@ -116,13 +116,11 @@ static void
 statapprox_heap(Relation rel, output_type *stat)
 {
 	BlockNumber nblocks;
-	BufferAccessStrategy bstrategy;
 	TransactionId OldestXmin;
 	StatApproxReadStreamPrivate p;
 	ReadStream *stream;
 
 	OldestXmin = GetOldestNonRemovableTransactionId(rel);
-	bstrategy = GetAccessStrategy(BAS_BULKREAD);
 
 	nblocks = RelationGetNumberOfBlocks(rel);
 
@@ -141,7 +139,6 @@ statapprox_heap(Relation rel, output_type *stat)
 	 * caution.
 	 */
 	stream = read_stream_begin_relation(READ_STREAM_FULL,
-										bstrategy,
 										rel,
 										MAIN_FORKNUM,
 										statapprox_heap_read_stream_next,
diff --git a/contrib/pgstattuple/pgstatindex.c b/contrib/pgstattuple/pgstatindex.c
index 8951ad0aac4..a0922b386a0 100644
--- a/contrib/pgstattuple/pgstatindex.c
+++ b/contrib/pgstattuple/pgstatindex.c
@@ -217,7 +217,6 @@ pgstatindex_impl(Relation rel, FunctionCallInfo fcinfo)
 	BlockNumber nblocks;
 	BlockNumber blkno;
 	BTIndexStat indexStat;
-	BufferAccessStrategy bstrategy = GetAccessStrategy(BAS_BULKREAD);
 	BlockRangeReadStreamPrivate p;
 	ReadStream *stream;
 	BlockNumber startblk;
@@ -254,7 +253,7 @@ pgstatindex_impl(Relation rel, FunctionCallInfo fcinfo)
 	 * Read metapage
 	 */
 	{
-		Buffer		buffer = ReadBufferExtended(rel, MAIN_FORKNUM, 0, RBM_NORMAL, bstrategy);
+		Buffer		buffer = ReadBufferExtended(rel, MAIN_FORKNUM, 0, RBM_NORMAL);
 		Page		page = BufferGetPage(buffer);
 		BTMetaPageData *metad = BTPageGetMeta(page);
 
@@ -291,7 +290,6 @@ pgstatindex_impl(Relation rel, FunctionCallInfo fcinfo)
 	 */
 	stream = read_stream_begin_relation(READ_STREAM_FULL |
 										READ_STREAM_USE_BATCHING,
-										bstrategy,
 										rel,
 										MAIN_FORKNUM,
 										block_range_read_stream_cb,
@@ -612,7 +610,6 @@ pgstathashindex(PG_FUNCTION_ARGS)
 	BlockNumber blkno;
 	Relation	rel;
 	HashIndexStat stats;
-	BufferAccessStrategy bstrategy;
 	HeapTuple	tuple;
 	TupleDesc	tupleDesc;
 	Datum		values[8];
@@ -665,9 +662,6 @@ pgstathashindex(PG_FUNCTION_ARGS)
 	/* Get the current relation length */
 	nblocks = RelationGetNumberOfBlocks(rel);
 
-	/* prepare access strategy for this index */
-	bstrategy = GetAccessStrategy(BAS_BULKREAD);
-
 	/* Scan all blocks except the metapage (0th page) using streaming reads */
 	startblk = HASH_METAPAGE + 1;
 
@@ -680,7 +674,6 @@ pgstathashindex(PG_FUNCTION_ARGS)
 	 */
 	stream = read_stream_begin_relation(READ_STREAM_FULL |
 										READ_STREAM_USE_BATCHING,
-										bstrategy,
 										rel,
 										MAIN_FORKNUM,
 										block_range_read_stream_cb,
diff --git a/contrib/pgstattuple/pgstattuple.c b/contrib/pgstattuple/pgstattuple.c
index 6a7f8cb4a7c..90f7938e953 100644
--- a/contrib/pgstattuple/pgstattuple.c
+++ b/contrib/pgstattuple/pgstattuple.c
@@ -64,22 +64,18 @@ typedef struct pgstattuple_type
 	uint64		free_space;		/* free/reusable space in bytes */
 } pgstattuple_type;
 
-typedef void (*pgstat_page) (pgstattuple_type *, Relation, BlockNumber,
-							 BufferAccessStrategy);
+typedef void (*pgstat_page) (pgstattuple_type *, Relation, BlockNumber);
 
 static Datum build_pgstattuple_type(pgstattuple_type *stat,
 									FunctionCallInfo fcinfo);
 static Datum pgstat_relation(Relation rel, FunctionCallInfo fcinfo);
 static Datum pgstat_heap(Relation rel, FunctionCallInfo fcinfo);
 static void pgstat_btree_page(pgstattuple_type *stat,
-							  Relation rel, BlockNumber blkno,
-							  BufferAccessStrategy bstrategy);
+							  Relation rel, BlockNumber blkno);
 static void pgstat_hash_page(pgstattuple_type *stat,
-							 Relation rel, BlockNumber blkno,
-							 BufferAccessStrategy bstrategy);
+							 Relation rel, BlockNumber blkno);
 static void pgstat_gist_page(pgstattuple_type *stat,
-							 Relation rel, BlockNumber blkno,
-							 BufferAccessStrategy bstrategy);
+							 Relation rel, BlockNumber blkno);
 static Datum pgstat_index(Relation rel, BlockNumber start,
 						  pgstat_page pagefn, FunctionCallInfo fcinfo);
 static void pgstat_index_page(pgstattuple_type *stat, Page page,
@@ -376,7 +372,7 @@ pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
 			CHECK_FOR_INTERRUPTS();
 
 			buffer = ReadBufferExtended(rel, MAIN_FORKNUM, block,
-										RBM_NORMAL, hscan->rs_strategy);
+										RBM_NORMAL);
 			LockBuffer(buffer, BUFFER_LOCK_SHARE);
 			stat.free_space += PageGetExactFreeSpace(BufferGetPage(buffer));
 			UnlockReleaseBuffer(buffer);
@@ -389,7 +385,7 @@ pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
 		CHECK_FOR_INTERRUPTS();
 
 		buffer = ReadBufferExtended(rel, MAIN_FORKNUM, block,
-									RBM_NORMAL, hscan->rs_strategy);
+									RBM_NORMAL);
 		LockBuffer(buffer, BUFFER_LOCK_SHARE);
 		stat.free_space += PageGetExactFreeSpace(BufferGetPage(buffer));
 		UnlockReleaseBuffer(buffer);
@@ -408,13 +404,12 @@ pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
  * pgstat_btree_page -- check tuples in a btree page
  */
 static void
-pgstat_btree_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno,
-				  BufferAccessStrategy bstrategy)
+pgstat_btree_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno)
 {
 	Buffer		buf;
 	Page		page;
 
-	buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, bstrategy);
+	buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL);
 	LockBuffer(buf, BT_READ);
 	page = BufferGetPage(buf);
 
@@ -452,13 +447,12 @@ pgstat_btree_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno,
  * pgstat_hash_page -- check tuples in a hash page
  */
 static void
-pgstat_hash_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno,
-				 BufferAccessStrategy bstrategy)
+pgstat_hash_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno)
 {
 	Buffer		buf;
 	Page		page;
 
-	buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, bstrategy);
+	buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL);
 	LockBuffer(buf, HASH_READ);
 	page = BufferGetPage(buf);
 
@@ -500,13 +494,12 @@ pgstat_hash_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno,
  * pgstat_gist_page -- check tuples in a gist page
  */
 static void
-pgstat_gist_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno,
-				 BufferAccessStrategy bstrategy)
+pgstat_gist_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno)
 {
 	Buffer		buf;
 	Page		page;
 
-	buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, bstrategy);
+	buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL);
 	LockBuffer(buf, GIST_SHARE);
 	page = BufferGetPage(buf);
 	if (PageIsNew(page))
@@ -539,12 +532,8 @@ pgstat_index(Relation rel, BlockNumber start, pgstat_page pagefn,
 {
 	BlockNumber nblocks;
 	BlockNumber blkno;
-	BufferAccessStrategy bstrategy;
 	pgstattuple_type stat = {0};
 
-	/* prepare access strategy for this index */
-	bstrategy = GetAccessStrategy(BAS_BULKREAD);
-
 	blkno = start;
 	for (;;)
 	{
@@ -565,7 +554,7 @@ pgstat_index(Relation rel, BlockNumber start, pgstat_page pagefn,
 		{
 			CHECK_FOR_INTERRUPTS();
 
-			pagefn(&stat, rel, blkno, bstrategy);
+			pagefn(&stat, rel, blkno);
 		}
 	}
 
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index c67130c620e..535226c05db 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -2107,36 +2107,6 @@ include_dir 'conf.d'
       </listitem>
      </varlistentry>
 
-     <varlistentry id="guc-vacuum-buffer-usage-limit" xreflabel="vacuum_buffer_usage_limit">
-      <term>
-       <varname>vacuum_buffer_usage_limit</varname> (<type>integer</type>)
-       <indexterm>
-        <primary><varname>vacuum_buffer_usage_limit</varname> configuration parameter</primary>
-       </indexterm>
-      </term>
-      <listitem>
-       <para>
-        Specifies the size of the
-        <glossterm linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm>
-        used by the <command>VACUUM</command> and <command>ANALYZE</command>
-        commands.  A setting of <literal>0</literal> will allow the operation
-        to use any number of <varname>shared_buffers</varname>.  Otherwise
-        valid sizes range from <literal>128 kB</literal> to
-        <literal>16 GB</literal>.  If the specified size would exceed 1/8 the
-        size of <varname>shared_buffers</varname>, the size is silently capped
-        to that value.  The default value is <literal>2MB</literal>.  If
-        this value is specified without units, it is taken as kilobytes.  This
-        parameter can be set at any time.  It can be overridden for
-        <xref linkend="sql-vacuum"/> and <xref linkend="sql-analyze"/>
-        when passing the <option>BUFFER_USAGE_LIMIT</option> option.  Higher
-        settings can allow <command>VACUUM</command> and
-        <command>ANALYZE</command> to run more quickly, but having too large a
-        setting may cause too many other useful pages to be evicted from
-        shared buffers.
-       </para>
-      </listitem>
-     </varlistentry>
-
      <varlistentry id="guc-logical-decoding-work-mem" xreflabel="logical_decoding_work_mem">
       <term><varname>logical_decoding_work_mem</varname> (<type>integer</type>)
       <indexterm>
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 858788b227c..d83142fd039 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -2993,28 +2993,6 @@ description | Waiting for a newly initialized WAL file to reach durable storage
           <literal>init</literal>.
          </para>
         </listitem>
-        <listitem>
-         <para>
-          <literal>vacuum</literal>: I/O operations performed outside of shared
-          buffers while vacuuming and analyzing permanent relations. Temporary
-          table vacuums use the same local buffer pool as other temporary table
-          I/O operations and are tracked in <varname>context</varname>
-          <literal>normal</literal>.
-         </para>
-        </listitem>
-        <listitem>
-         <para>
-          <literal>bulkread</literal>: Certain large read I/O operations
-          done outside of shared buffers, for example, a sequential scan of a
-          large table.
-         </para>
-        </listitem>
-        <listitem>
-         <para>
-          <literal>bulkwrite</literal>: Certain large write I/O operations
-          done outside of shared buffers, such as <command>COPY</command>.
-         </para>
-        </listitem>
        </itemizedlist>
       </entry>
      </row>
@@ -3180,13 +3158,8 @@ description | Waiting for a newly initialized WAL file to reach durable storage
         buffer in order to make it available for another use.
        </para>
        <para>
-        In <varname>context</varname> <literal>normal</literal>, this counts
-        the number of times a block was evicted from a buffer and replaced with
-        another block. In <varname>context</varname>s
-        <literal>bulkwrite</literal>, <literal>bulkread</literal>, and
-        <literal>vacuum</literal>, this counts the number of times a block was
-        evicted from shared buffers in order to add the shared buffer to a
-        separate, size-limited ring buffer for use in a bulk I/O operation.
+        This counts the number of times a block was evicted from a buffer and
+        replaced with another block.
         </para>
       </entry>
      </row>
@@ -3197,10 +3170,9 @@ description | Waiting for a newly initialized WAL file to reach durable storage
         <structfield>reuses</structfield> <type>bigint</type>
        </para>
        <para>
-        The number of times an existing buffer in a size-limited ring buffer
-        outside of shared buffers was reused as part of an I/O operation in the
-        <literal>bulkread</literal>, <literal>bulkwrite</literal>, or
-        <literal>vacuum</literal> <varname>context</varname>s.
+        Always zero.  This column previously counted reuses of buffers in a
+        size-limited ring buffer (buffer access strategy); ring buffers have
+        been removed, so no reuses are tracked.
        </para>
       </entry>
      </row>
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index ec81f00fecf..7f5159111d8 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -27,7 +27,6 @@ ANALYZE [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] [ <r
 
     VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
     SKIP_LOCKED [ <replaceable class="parameter">boolean</replaceable> ]
-    BUFFER_USAGE_LIMIT <replaceable class="parameter">size</replaceable>
 
 <phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
 
@@ -88,26 +87,6 @@ ANALYZE [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] [ <r
     </listitem>
    </varlistentry>
 
-   <varlistentry>
-    <term><literal>BUFFER_USAGE_LIMIT</literal></term>
-    <listitem>
-     <para>
-      Specifies the
-      <glossterm linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm>
-      ring buffer size for <command>ANALYZE</command>.  This size is used to
-      calculate the number of shared buffers which will be reused as part of
-      this strategy.  <literal>0</literal> disables use of a
-      <literal>Buffer Access Strategy</literal>.   When this option is not
-      specified, <command>ANALYZE</command> uses the value from
-      <xref linkend="guc-vacuum-buffer-usage-limit"/>.  Higher settings can
-      allow <command>ANALYZE</command> to run more quickly, but having too
-      large a setting may cause too many other useful pages to be evicted from
-      shared buffers.  The minimum value is <literal>128 kB</literal> and the
-      maximum value is <literal>16 GB</literal>.
-     </para>
-    </listitem>
-   </varlistentry>
-
    <varlistentry>
     <term><replaceable class="parameter">boolean</replaceable></term>
     <listitem>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 38ee973ea05..d44a49f8efe 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -37,7 +37,6 @@ VACUUM [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] [ <re
     PARALLEL <replaceable class="parameter">integer</replaceable>
     SKIP_DATABASE_STATS [ <replaceable class="parameter">boolean</replaceable> ]
     ONLY_DATABASE_STATS [ <replaceable class="parameter">boolean</replaceable> ]
-    BUFFER_USAGE_LIMIT <replaceable class="parameter">size</replaceable>
     FULL [ <replaceable class="parameter">boolean</replaceable> ]
 
 <phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -309,30 +308,6 @@ VACUUM [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] [ <re
     </listitem>
    </varlistentry>
 
-   <varlistentry>
-    <term><literal>BUFFER_USAGE_LIMIT</literal></term>
-    <listitem>
-     <para>
-      Specifies the
-      <glossterm linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm>
-      ring buffer size for <command>VACUUM</command>.  This size is used to
-      calculate the number of shared buffers which will be reused as part of
-      this strategy.  <literal>0</literal> disables use of a
-      <literal>Buffer Access Strategy</literal>.  If <option>ANALYZE</option>
-      is also specified, the <option>BUFFER_USAGE_LIMIT</option> value is used
-      for both the vacuum and analyze stages.  This option can't be used with
-      the <option>FULL</option> option except if <option>ANALYZE</option> is
-      also specified.  When this option is not specified,
-      <command>VACUUM</command> uses the value from
-      <xref linkend="guc-vacuum-buffer-usage-limit"/>.  Higher settings can
-      allow <command>VACUUM</command> to run more quickly, but having too
-      large a setting may cause too many other useful pages to be evicted from
-      shared buffers.  The minimum value is <literal>128 kB</literal> and the
-      maximum value is <literal>16 GB</literal>.
-     </para>
-    </listitem>
-   </varlistentry>
-
    <varlistentry>
     <term><literal>FULL</literal></term>
     <listitem>
diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..5d6539f5483 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -224,7 +224,7 @@ static void form_and_insert_tuple(BrinBuildState *state);
 static void form_and_spill_tuple(BrinBuildState *state);
 static void union_tuples(BrinDesc *bdesc, BrinMemTuple *a,
 						 BrinTuple *b);
-static void brin_vacuum_scan(Relation idxrel, BufferAccessStrategy strategy);
+static void brin_vacuum_scan(Relation idxrel);
 static bool add_values_to_range(Relation idxRel, BrinDesc *bdesc,
 								BrinMemTuple *dtup, const Datum *values, const bool *nulls);
 static bool check_null_keys(BrinValues *bval, ScanKey *nullkeys, int nnullkeys);
@@ -1129,7 +1129,7 @@ brinbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * whole relation will be rolled back.
 	 */
 
-	meta = ExtendBufferedRel(BMR_REL(index), MAIN_FORKNUM, NULL,
+	meta = ExtendBufferedRel(BMR_REL(index), MAIN_FORKNUM,
 							 EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK);
 	Assert(BufferGetBlockNumber(meta) == BRIN_METAPAGE_BLKNO);
 
@@ -1281,7 +1281,7 @@ brinbuildempty(Relation index)
 	Buffer		metabuf;
 
 	/* An empty BRIN index has a metapage only. */
-	metabuf = ExtendBufferedRel(BMR_REL(index), INIT_FORKNUM, NULL,
+	metabuf = ExtendBufferedRel(BMR_REL(index), INIT_FORKNUM,
 								EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK);
 
 	/* Initialize and xlog metabuffer. */
@@ -1336,7 +1336,7 @@ brinvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats)
 	heapRel = table_open(IndexGetRelation(RelationGetRelid(info->index), false),
 						 AccessShareLock);
 
-	brin_vacuum_scan(info->index, info->strategy);
+	brin_vacuum_scan(info->index);
 
 	brinsummarize(info->index, heapRel, BRIN_ALL_BLOCKRANGES, false,
 				  &stats->num_index_tuples, &stats->num_index_tuples);
@@ -2171,7 +2171,7 @@ union_tuples(BrinDesc *bdesc, BrinMemTuple *a, BrinTuple *b)
  * and such.
  */
 static void
-brin_vacuum_scan(Relation idxrel, BufferAccessStrategy strategy)
+brin_vacuum_scan(Relation idxrel)
 {
 	BlockRangeReadStreamPrivate p;
 	ReadStream *stream;
@@ -2187,7 +2187,6 @@ brin_vacuum_scan(Relation idxrel, BufferAccessStrategy strategy)
 	stream = read_stream_begin_relation(READ_STREAM_MAINTENANCE |
 										READ_STREAM_FULL |
 										READ_STREAM_USE_BATCHING,
-										strategy,
 										idxrel,
 										MAIN_FORKNUM,
 										block_range_read_stream_cb,
diff --git a/src/backend/access/brin/brin_revmap.c b/src/backend/access/brin/brin_revmap.c
index 233355cb2d5..01c4104ac88 100644
--- a/src/backend/access/brin/brin_revmap.c
+++ b/src/backend/access/brin/brin_revmap.c
@@ -559,7 +559,7 @@ revmap_physical_extend(BrinRevmap *revmap)
 	}
 	else
 	{
-		buf = ExtendBufferedRel(BMR_REL(irel), MAIN_FORKNUM, NULL,
+		buf = ExtendBufferedRel(BMR_REL(irel), MAIN_FORKNUM,
 								EB_LOCK_FIRST);
 		if (BufferGetBlockNumber(buf) != mapBlk)
 		{
diff --git a/src/backend/access/gin/gininsert.c b/src/backend/access/gin/gininsert.c
index cb9ed3b563c..32ad65cc95f 100644
--- a/src/backend/access/gin/gininsert.c
+++ b/src/backend/access/gin/gininsert.c
@@ -815,9 +815,9 @@ ginbuildempty(Relation index)
 				MetaBuffer;
 
 	/* An empty GIN index has two pages. */
-	MetaBuffer = ExtendBufferedRel(BMR_REL(index), INIT_FORKNUM, NULL,
+	MetaBuffer = ExtendBufferedRel(BMR_REL(index), INIT_FORKNUM,
 								   EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK);
-	RootBuffer = ExtendBufferedRel(BMR_REL(index), INIT_FORKNUM, NULL,
+	RootBuffer = ExtendBufferedRel(BMR_REL(index), INIT_FORKNUM,
 								   EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK);
 
 	/* Initialize and xlog metabuffer and root buffer. */
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
index e7cba81d477..2d7394aedd5 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -336,7 +336,7 @@ GinNewBuffer(Relation index)
 	}
 
 	/* Must extend the file */
-	buffer = ExtendBufferedRel(BMR_REL(index), MAIN_FORKNUM, NULL,
+	buffer = ExtendBufferedRel(BMR_REL(index), MAIN_FORKNUM,
 							   EB_LOCK_FIRST);
 
 	return buffer;
diff --git a/src/backend/access/gin/ginvacuum.c b/src/backend/access/gin/ginvacuum.c
index 840543eb664..86538de39d6 100644
--- a/src/backend/access/gin/ginvacuum.c
+++ b/src/backend/access/gin/ginvacuum.c
@@ -32,7 +32,6 @@ struct GinVacuumState
 	IndexBulkDeleteCallback callback;
 	void	   *callback_state;
 	GinState	ginstate;
-	BufferAccessStrategy strategy;
 	MemoryContext tmpCxt;
 };
 
@@ -289,7 +288,7 @@ ginScanPostingTreeToDelete(GinVacuumState *gvs, DataPageDeleteStack *myStackItem
 			childBuffer = ReadBufferExtended(gvs->index,
 											 MAIN_FORKNUM,
 											 PostingItemGetBlockNumber(pitem),
-											 RBM_NORMAL, gvs->strategy);
+											 RBM_NORMAL);
 			LockBuffer(childBuffer, GIN_EXCLUSIVE);
 
 			/* Allocate a child stack entry on first use; reuse thereafter */
@@ -389,7 +388,7 @@ ginVacuumPostingTreeLeaves(GinVacuumState *gvs, BlockNumber blkno)
 		PostingItem *pitem;
 
 		buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
-									RBM_NORMAL, gvs->strategy);
+									RBM_NORMAL);
 		LockBuffer(buffer, GIN_SHARE);
 		page = BufferGetPage(buffer);
 
@@ -430,7 +429,7 @@ ginVacuumPostingTreeLeaves(GinVacuumState *gvs, BlockNumber blkno)
 			break;
 
 		buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
-									RBM_NORMAL, gvs->strategy);
+									RBM_NORMAL);
 		LockBuffer(buffer, GIN_EXCLUSIVE);
 		page = BufferGetPage(buffer);
 	}
@@ -454,7 +453,7 @@ ginVacuumPostingTree(GinVacuumState *gvs, BlockNumber rootBlkno)
 		bool		deleted PG_USED_FOR_ASSERTS_ONLY;
 
 		buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, rootBlkno,
-									RBM_NORMAL, gvs->strategy);
+									RBM_NORMAL);
 
 		/*
 		 * Lock posting tree root for cleanup to ensure there are no
@@ -615,7 +614,6 @@ ginbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 	gvs.index = index;
 	gvs.callback = callback;
 	gvs.callback_state = callback_state;
-	gvs.strategy = info->strategy;
 	initGinState(&gvs.ginstate, index);
 
 	/* first time through? */
@@ -636,7 +634,7 @@ ginbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 	gvs.result = stats;
 
 	buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
-								RBM_NORMAL, info->strategy);
+								RBM_NORMAL);
 
 	/* find leaf page */
 	for (;;)
@@ -669,7 +667,7 @@ ginbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 
 		UnlockReleaseBuffer(buffer);
 		buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
-									RBM_NORMAL, info->strategy);
+									RBM_NORMAL);
 	}
 
 	/* right now we found leftmost page in entry's BTree */
@@ -712,7 +710,7 @@ ginbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 			break;
 
 		buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
-									RBM_NORMAL, info->strategy);
+									RBM_NORMAL);
 		LockBuffer(buffer, GIN_EXCLUSIVE);
 	}
 
@@ -794,7 +792,6 @@ ginvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats)
 	stream = read_stream_begin_relation(READ_STREAM_MAINTENANCE |
 										READ_STREAM_FULL |
 										READ_STREAM_USE_BATCHING,
-										info->strategy,
 										index,
 										MAIN_FORKNUM,
 										block_range_read_stream_cb,
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index 8565e225be7..ae494742a4b 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -142,7 +142,7 @@ gistbuildempty(Relation index)
 	Buffer		buffer;
 
 	/* Initialize the root page */
-	buffer = ExtendBufferedRel(BMR_REL(index), INIT_FORKNUM, NULL,
+	buffer = ExtendBufferedRel(BMR_REL(index), INIT_FORKNUM,
 							   EB_SKIP_EXTENSION_LOCK | EB_LOCK_FIRST);
 
 	/* Initialize and xlog buffer */
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index 0f58f61879f..170174f62fd 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -877,7 +877,7 @@ gistNewBuffer(Relation r, Relation heaprel)
 	}
 
 	/* Must extend the file */
-	buffer = ExtendBufferedRel(BMR_REL(r), MAIN_FORKNUM, NULL,
+	buffer = ExtendBufferedRel(BMR_REL(r), MAIN_FORKNUM,
 							   EB_LOCK_FIRST);
 
 	return buffer;
diff --git a/src/backend/access/gist/gistvacuum.c b/src/backend/access/gist/gistvacuum.c
index 686a0418054..c366ed35ea8 100644
--- a/src/backend/access/gist/gistvacuum.c
+++ b/src/backend/access/gist/gistvacuum.c
@@ -218,7 +218,6 @@ gistvacuumscan(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 	stream = read_stream_begin_relation(READ_STREAM_MAINTENANCE |
 										READ_STREAM_FULL |
 										READ_STREAM_USE_BATCHING,
-										info->strategy,
 										rel,
 										MAIN_FORKNUM,
 										block_range_read_stream_cb,
@@ -491,8 +490,7 @@ restart:
 		/* check for vacuum delay while not holding any buffer lock */
 		vacuum_delay_point(false);
 
-		buffer = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL,
-									info->strategy);
+		buffer = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL);
 		goto restart;
 	}
 }
@@ -524,8 +522,7 @@ gistvacuum_delete_empty_pages(IndexVacuumInfo *info, GistVacState *vstate)
 		int			ntodelete;
 		int			deleted;
 
-		buffer = ReadBufferExtended(rel, MAIN_FORKNUM, (BlockNumber) blkno,
-									RBM_NORMAL, info->strategy);
+		buffer = ReadBufferExtended(rel, MAIN_FORKNUM, (BlockNumber) blkno, RBM_NORMAL);
 
 		LockBuffer(buffer, GIST_SHARE);
 		page = BufferGetPage(buffer);
@@ -590,8 +587,7 @@ gistvacuum_delete_empty_pages(IndexVacuumInfo *info, GistVacState *vstate)
 			if (PageGetMaxOffsetNumber(page) == FirstOffsetNumber)
 				break;
 
-			leafbuf = ReadBufferExtended(rel, MAIN_FORKNUM, leafs_to_delete[i],
-										 RBM_NORMAL, info->strategy);
+			leafbuf = ReadBufferExtended(rel, MAIN_FORKNUM, leafs_to_delete[i], RBM_NORMAL);
 			LockBuffer(leafbuf, GIST_EXCLUSIVE);
 			gistcheckpage(rel, leafbuf);
 
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 8d8cd30dc38..1bbb7224bfc 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -542,7 +542,6 @@ hashbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 	 */
 	stream = read_stream_begin_relation(READ_STREAM_MAINTENANCE |
 										READ_STREAM_USE_BATCHING,
-										info->strategy,
 										rel,
 										MAIN_FORKNUM,
 										hash_bulkdelete_read_stream_cb,
@@ -616,7 +615,7 @@ bucket_loop:
 
 		bucket_buf = buf;
 
-		hashbucketcleanup(rel, cur_bucket, bucket_buf, blkno, info->strategy,
+		hashbucketcleanup(rel, cur_bucket, bucket_buf, blkno,
 						  cachedmetap->hashm_maxbucket,
 						  cachedmetap->hashm_highmask,
 						  cachedmetap->hashm_lowmask, &tuples_removed,
@@ -765,7 +764,7 @@ hashvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats)
  */
 void
 hashbucketcleanup(Relation rel, Bucket cur_bucket, Buffer bucket_buf,
-				  BlockNumber bucket_blkno, BufferAccessStrategy bstrategy,
+				  BlockNumber bucket_blkno,
 				  uint32 maxbucket, uint32 highmask, uint32 lowmask,
 				  double *tuples_removed, double *num_index_tuples,
 				  bool split_cleanup,
@@ -935,9 +934,8 @@ hashbucketcleanup(Relation rel, Bucket cur_bucket, Buffer bucket_buf,
 		if (!BlockNumberIsValid(blkno))
 			break;
 
-		next_buf = _hash_getbuf_with_strategy(rel, blkno, HASH_WRITE,
-											  LH_OVERFLOW_PAGE,
-											  bstrategy);
+		next_buf = _hash_getbuf(rel, blkno, HASH_WRITE,
+								LH_OVERFLOW_PAGE);
 
 		/*
 		 * release the lock on previous page after acquiring the lock on next
@@ -1004,8 +1002,7 @@ hashbucketcleanup(Relation rel, Bucket cur_bucket, Buffer bucket_buf,
 	 * ordering of tuples for a scan that has started before it.
 	 */
 	if (bucket_dirty && IsBufferCleanupOK(bucket_buf))
-		_hash_squeezebucket(rel, cur_bucket, bucket_blkno, bucket_buf,
-							bstrategy);
+		_hash_squeezebucket(rel, cur_bucket, bucket_blkno, bucket_buf);
 	else
 		LockBuffer(bucket_buf, BUFFER_LOCK_UNLOCK);
 }
diff --git a/src/backend/access/hash/hashovfl.c b/src/backend/access/hash/hashovfl.c
index dbc57ef958c..5b35fed33f9 100644
--- a/src/backend/access/hash/hashovfl.c
+++ b/src/backend/access/hash/hashovfl.c
@@ -491,8 +491,7 @@ _hash_firstfreebit(uint32 map)
 BlockNumber
 _hash_freeovflpage(Relation rel, Buffer bucketbuf, Buffer ovflbuf,
 				   Buffer wbuf, IndexTuple *itups, OffsetNumber *itup_offsets,
-				   Size *tups_size, uint16 nitups,
-				   BufferAccessStrategy bstrategy)
+				   Size *tups_size, uint16 nitups)
 {
 	HashMetaPage metap;
 	Buffer		metabuf;
@@ -539,20 +538,16 @@ _hash_freeovflpage(Relation rel, Buffer bucketbuf, Buffer ovflbuf,
 		if (prevblkno == writeblkno)
 			prevbuf = wbuf;
 		else
-			prevbuf = _hash_getbuf_with_strategy(rel,
-												 prevblkno,
-												 HASH_WRITE,
-												 LH_BUCKET_PAGE | LH_OVERFLOW_PAGE,
-												 bstrategy);
+			prevbuf = _hash_getbuf(rel,
+								   prevblkno,
+								   HASH_WRITE,
+								   LH_BUCKET_PAGE | LH_OVERFLOW_PAGE);
 	}
 	if (BlockNumberIsValid(nextblkno))
-		nextbuf = _hash_getbuf_with_strategy(rel,
-											 nextblkno,
-											 HASH_WRITE,
-											 LH_OVERFLOW_PAGE,
-											 bstrategy);
-
-	/* Note: bstrategy is intentionally not used for metapage and bitmap */
+		nextbuf = _hash_getbuf(rel,
+							   nextblkno,
+							   HASH_WRITE,
+							   LH_OVERFLOW_PAGE);
 
 	/* Read the metapage so we can determine which bitmap page to use */
 	metabuf = _hash_getbuf(rel, HASH_METAPAGE, HASH_READ, LH_META_PAGE);
@@ -843,8 +838,7 @@ void
 _hash_squeezebucket(Relation rel,
 					Bucket bucket,
 					BlockNumber bucket_blkno,
-					Buffer bucket_buf,
-					BufferAccessStrategy bstrategy)
+					Buffer bucket_buf)
 {
 	BlockNumber wblkno;
 	BlockNumber rblkno;
@@ -886,11 +880,10 @@ _hash_squeezebucket(Relation rel,
 		rblkno = ropaque->hasho_nextblkno;
 		if (rbuf != InvalidBuffer)
 			_hash_relbuf(rel, rbuf);
-		rbuf = _hash_getbuf_with_strategy(rel,
-										  rblkno,
-										  HASH_WRITE,
-										  LH_OVERFLOW_PAGE,
-										  bstrategy);
+		rbuf = _hash_getbuf(rel,
+							rblkno,
+							HASH_WRITE,
+							LH_OVERFLOW_PAGE);
 		rpage = BufferGetPage(rbuf);
 		ropaque = HashPageGetOpaque(rpage);
 		Assert(ropaque->hasho_bucket == bucket);
@@ -952,11 +945,10 @@ readpage:
 
 				/* don't need to move to next page if we reached the read page */
 				if (wblkno != rblkno)
-					next_wbuf = _hash_getbuf_with_strategy(rel,
-														   wblkno,
-														   HASH_WRITE,
-														   LH_OVERFLOW_PAGE,
-														   bstrategy);
+					next_wbuf = _hash_getbuf(rel,
+											 wblkno,
+											 HASH_WRITE,
+											 LH_OVERFLOW_PAGE);
 
 				if (nitups > 0)
 				{
@@ -1098,7 +1090,7 @@ readpage:
 
 		/* free this overflow page (releases rbuf) */
 		_hash_freeovflpage(rel, bucket_buf, rbuf, wbuf, itups, itup_offsets,
-						   tups_size, nitups, bstrategy);
+						   tups_size, nitups);
 
 		/* be tidy */
 		for (i = 0; i < nitups; i++)
@@ -1115,11 +1107,10 @@ readpage:
 			return;
 		}
 
-		rbuf = _hash_getbuf_with_strategy(rel,
-										  rblkno,
-										  HASH_WRITE,
-										  LH_OVERFLOW_PAGE,
-										  bstrategy);
+		rbuf = _hash_getbuf(rel,
+							rblkno,
+							HASH_WRITE,
+							LH_OVERFLOW_PAGE);
 		rpage = BufferGetPage(rbuf);
 		ropaque = HashPageGetOpaque(rpage);
 		Assert(ropaque->hasho_bucket == bucket);
diff --git a/src/backend/access/hash/hashpage.c b/src/backend/access/hash/hashpage.c
index 8099b0d021f..bdf1a255f1f 100644
--- a/src/backend/access/hash/hashpage.c
+++ b/src/backend/access/hash/hashpage.c
@@ -139,8 +139,7 @@ _hash_getinitbuf(Relation rel, BlockNumber blkno)
 	if (blkno == P_NEW)
 		elog(ERROR, "hash AM does not use P_NEW");
 
-	buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_ZERO_AND_LOCK,
-							 NULL);
+	buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_ZERO_AND_LOCK);
 
 	/* ref count and lock type are correct */
 
@@ -209,7 +208,7 @@ _hash_getnewbuf(Relation rel, BlockNumber blkno, ForkNumber forkNum)
 	/* smgr insists we explicitly extend the relation */
 	if (blkno == nblocks)
 	{
-		buf = ExtendBufferedRel(BMR_REL(rel), forkNum, NULL,
+		buf = ExtendBufferedRel(BMR_REL(rel), forkNum,
 								EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK);
 		if (BufferGetBlockNumber(buf) != blkno)
 			elog(ERROR, "unexpected hash relation size: %u, should be %u",
@@ -217,8 +216,7 @@ _hash_getnewbuf(Relation rel, BlockNumber blkno, ForkNumber forkNum)
 	}
 	else
 	{
-		buf = ReadBufferExtended(rel, forkNum, blkno, RBM_ZERO_AND_LOCK,
-								 NULL);
+		buf = ReadBufferExtended(rel, forkNum, blkno, RBM_ZERO_AND_LOCK);
 	}
 
 	/* ref count and lock type are correct */
@@ -229,34 +227,6 @@ _hash_getnewbuf(Relation rel, BlockNumber blkno, ForkNumber forkNum)
 	return buf;
 }
 
-/*
- *	_hash_getbuf_with_strategy() -- Get a buffer with nondefault strategy.
- *
- *		This is identical to _hash_getbuf() but also allows a buffer access
- *		strategy to be specified.  We use this for VACUUM operations.
- */
-Buffer
-_hash_getbuf_with_strategy(Relation rel, BlockNumber blkno,
-						   int access, int flags,
-						   BufferAccessStrategy bstrategy)
-{
-	Buffer		buf;
-
-	if (blkno == P_NEW)
-		elog(ERROR, "hash AM does not use P_NEW");
-
-	buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, bstrategy);
-
-	if (access != HASH_NOLOCK)
-		LockBuffer(buf, access);
-
-	/* ref count and lock type are correct */
-
-	_hash_checkpage(rel, buf, flags);
-
-	return buf;
-}
-
 /*
  *	_hash_relbuf() -- release a locked buffer.
  *
@@ -758,7 +728,7 @@ restart_expand:
 		/* Release the metapage lock. */
 		LockBuffer(metabuf, BUFFER_LOCK_UNLOCK);
 
-		hashbucketcleanup(rel, old_bucket, buf_oblkno, start_oblkno, NULL,
+		hashbucketcleanup(rel, old_bucket, buf_oblkno, start_oblkno,
 						  maxbucket, highmask, lowmask, NULL, NULL, true,
 						  NULL, NULL);
 
@@ -1333,7 +1303,7 @@ _hash_splitbucket(Relation rel,
 	{
 		LockBuffer(bucket_nbuf, BUFFER_LOCK_UNLOCK);
 		hashbucketcleanup(rel, obucket, bucket_obuf,
-						  BufferGetBlockNumber(bucket_obuf), NULL,
+						  BufferGetBlockNumber(bucket_obuf),
 						  maxbucket, highmask, lowmask, NULL, NULL, true,
 						  NULL, NULL);
 	}
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index abfd8e8970a..4331ab53437 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -359,7 +359,6 @@ static void
 initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
 {
 	ParallelBlockTableScanDesc bpscan = NULL;
-	bool		allow_strat;
 	bool		allow_sync;
 
 	/*
@@ -396,24 +395,10 @@ initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
 	if (!RelationUsesLocalBuffers(scan->rs_base.rs_rd) &&
 		scan->rs_nblocks > NBuffers / 4)
 	{
-		allow_strat = (scan->rs_base.rs_flags & SO_ALLOW_STRAT) != 0;
 		allow_sync = (scan->rs_base.rs_flags & SO_ALLOW_SYNC) != 0;
 	}
 	else
-		allow_strat = allow_sync = false;
-
-	if (allow_strat)
-	{
-		/* During a rescan, keep the previous strategy object. */
-		if (scan->rs_strategy == NULL)
-			scan->rs_strategy = GetAccessStrategy(BAS_BULKREAD);
-	}
-	else
-	{
-		if (scan->rs_strategy != NULL)
-			FreeAccessStrategy(scan->rs_strategy);
-		scan->rs_strategy = NULL;
-	}
+		allow_sync = false;
 
 	if (scan->rs_base.rs_parallel != NULL)
 	{
@@ -1202,7 +1187,6 @@ heap_beginscan(Relation relation, Snapshot snapshot,
 	scan->rs_base.rs_flags = flags;
 	scan->rs_base.rs_parallel = parallel_scan;
 	scan->rs_base.rs_instrument = NULL;
-	scan->rs_strategy = NULL;	/* set in initscan */
 	scan->rs_cbuf = InvalidBuffer;
 
 	/*
@@ -1273,8 +1257,7 @@ heap_beginscan(Relation relation, Snapshot snapshot,
 
 	/*
 	 * Set up a read stream for sequential scans and TID range scans. This
-	 * should be done after initscan() because initscan() allocates the
-	 * BufferAccessStrategy object passed to the read stream API.
+	 * should be done after initscan().
 	 */
 	if (scan->rs_base.rs_flags & SO_TYPE_SEQSCAN ||
 		scan->rs_base.rs_flags & SO_TYPE_TIDRANGESCAN)
@@ -1295,7 +1278,6 @@ heap_beginscan(Relation relation, Snapshot snapshot,
 		 */
 		scan->rs_read_stream = read_stream_begin_relation(READ_STREAM_SEQUENTIAL |
 														  READ_STREAM_USE_BATCHING,
-														  scan->rs_strategy,
 														  scan->rs_base.rs_rd,
 														  MAIN_FORKNUM,
 														  cb,
@@ -1306,7 +1288,6 @@ heap_beginscan(Relation relation, Snapshot snapshot,
 	{
 		scan->rs_read_stream = read_stream_begin_relation(READ_STREAM_DEFAULT |
 														  READ_STREAM_USE_BATCHING,
-														  scan->rs_strategy,
 														  scan->rs_base.rs_rd,
 														  MAIN_FORKNUM,
 														  bitmapheap_stream_read_next,
@@ -1402,9 +1383,6 @@ heap_endscan(TableScanDesc sscan)
 	if (BufferIsValid(scan->rs_vmbuffer))
 		ReleaseBuffer(scan->rs_vmbuffer);
 
-	/*
-	 * Must free the read stream before freeing the BufferAccessStrategy.
-	 */
 	if (scan->rs_read_stream)
 		read_stream_end(scan->rs_read_stream);
 
@@ -1416,9 +1394,6 @@ heap_endscan(TableScanDesc sscan)
 	if (scan->rs_base.rs_key)
 		pfree(scan->rs_base.rs_key);
 
-	if (scan->rs_strategy != NULL)
-		FreeAccessStrategy(scan->rs_strategy);
-
 	if (scan->rs_parallelworkerdata != NULL)
 		pfree(scan->rs_parallelworkerdata);
 
@@ -1939,7 +1914,6 @@ GetBulkInsertState(void)
 	BulkInsertState bistate;
 
 	bistate = (BulkInsertState) palloc_object(BulkInsertStateData);
-	bistate->strategy = GetAccessStrategy(BAS_BULKWRITE);
 	bistate->current_buf = InvalidBuffer;
 	bistate->next_free = InvalidBlockNumber;
 	bistate->last_free = InvalidBlockNumber;
@@ -1955,7 +1929,6 @@ FreeBulkInsertState(BulkInsertState bistate)
 {
 	if (bistate->current_buf != InvalidBuffer)
 		ReleaseBuffer(bistate->current_buf);
-	FreeAccessStrategy(bistate->strategy);
 	pfree(bistate);
 }
 
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index bf87430cf01..d14658d7218 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -2213,9 +2213,9 @@ heapam_scan_sample_next_block(TableScanDesc scan, SampleScanState *scanstate)
 	 */
 	CHECK_FOR_INTERRUPTS();
 
-	/* Read page using selected strategy */
+	/* Read page */
 	hscan->rs_cbuf = ReadBufferExtended(hscan->rs_base.rs_rd, MAIN_FORKNUM,
-										blockno, RBM_NORMAL, hscan->rs_strategy);
+										blockno, RBM_NORMAL);
 
 	/* in pagemode, prune the page and determine visible tuple offsets */
 	if (hscan->rs_base.rs_flags & SO_ALLOW_PAGEMODE)
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index e96e0f77d92..9bf7abc247f 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -80,7 +80,8 @@ RelationPutHeapTuple(Relation relation,
 }
 
 /*
- * Read in a buffer in mode, using bulk-insert strategy if bistate isn't NULL.
+ * Read in a buffer in mode, using the bistate's pinned target page if
+ * available.
  */
 static Buffer
 ReadBufferBI(Relation relation, BlockNumber targetBlock,
@@ -91,7 +92,7 @@ ReadBufferBI(Relation relation, BlockNumber targetBlock,
 	/* If not bulk-insert, exactly like ReadBuffer */
 	if (!bistate)
 		return ReadBufferExtended(relation, MAIN_FORKNUM, targetBlock,
-								  mode, NULL);
+								  mode);
 
 	/* If we have the desired block already pinned, re-pin and return it */
 	if (bistate->current_buf != InvalidBuffer)
@@ -113,9 +114,9 @@ ReadBufferBI(Relation relation, BlockNumber targetBlock,
 		bistate->current_buf = InvalidBuffer;
 	}
 
-	/* Perform a read using the buffer strategy */
+	/* Perform the read */
 	buffer = ReadBufferExtended(relation, MAIN_FORKNUM, targetBlock,
-								mode, bistate->strategy);
+								mode);
 
 	/* Save the selected block as target for future inserts */
 	IncrBufferRefCount(buffer);
@@ -337,7 +338,6 @@ RelationAddBlocks(Relation relation, BulkInsertState bistate,
 	 * way larger.
 	 */
 	first_block = ExtendBufferedRelBy(BMR_REL(relation), MAIN_FORKNUM,
-									  bistate ? bistate->strategy : NULL,
 									  EB_LOCK_FIRST,
 									  extend_by_pages,
 									  victim_buffers,
@@ -484,8 +484,7 @@ RelationAddBlocks(Relation relation, BulkInsertState bistate,
  *
  *	The caller can also provide a BulkInsertState object to optimize many
  *	insertions into the same relation.  This keeps a pin on the current
- *	insertion target page (to save pin/unpin cycles) and also passes a
- *	BULKWRITE buffer selection strategy object to the buffer manager.
+ *	insertion target page (to save pin/unpin cycles).
  *	Passing NULL for bistate selects the default behavior.
  *
  *	We don't fill existing pages further than the fillfactor, except for large
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 39395aed0d5..8bc8e470af3 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -257,7 +257,6 @@ typedef struct LVRelState
 	int			nindexes;
 
 	/* Buffer access strategy and parallel vacuum state */
-	BufferAccessStrategy bstrategy;
 	ParallelVacuumState *pvs;
 
 	/* Aggressive VACUUM? (must set relfrozenxid >= FreezeLimit) */
@@ -621,8 +620,7 @@ heap_vacuum_eager_scan_setup(LVRelState *vacrel, const VacuumParams *params)
  *		and locked the relation.
  */
 void
-heap_vacuum_rel(Relation rel, const VacuumParams *params,
-				BufferAccessStrategy bstrategy)
+heap_vacuum_rel(Relation rel, const VacuumParams *params)
 {
 	LVRelState *vacrel;
 	bool		verbose,
@@ -699,7 +697,6 @@ heap_vacuum_rel(Relation rel, const VacuumParams *params,
 	vacrel->rel = rel;
 	vac_open_indexes(vacrel->rel, RowExclusiveLock, &vacrel->nindexes,
 					 &vacrel->indrels);
-	vacrel->bstrategy = bstrategy;
 	if (instrument && vacrel->nindexes > 0)
 	{
 		/* Copy index names used by instrumentation (not error reporting) */
@@ -1311,7 +1308,6 @@ lazy_scan_heap(LVRelState *vacrel)
 	 * explicit work in heap_vac_scan_next_block.
 	 */
 	stream = read_stream_begin_relation(READ_STREAM_MAINTENANCE,
-										vacrel->bstrategy,
 										vacrel->rel,
 										MAIN_FORKNUM,
 										heap_vac_scan_next_block,
@@ -2670,7 +2666,6 @@ lazy_vacuum_heap_rel(LVRelState *vacrel)
 	 */
 	stream = read_stream_begin_relation(READ_STREAM_MAINTENANCE |
 										READ_STREAM_USE_BATCHING,
-										vacrel->bstrategy,
 										vacrel->rel,
 										MAIN_FORKNUM,
 										vacuum_reap_lp_read_stream_next,
@@ -2904,13 +2899,6 @@ lazy_check_wraparound_failsafe(LVRelState *vacrel)
 
 		VacuumFailsafeActive = true;
 
-		/*
-		 * Abandon use of a buffer access strategy to allow use of all of
-		 * shared buffers.  We assume the caller who allocated the memory for
-		 * the BufferAccessStrategy will free it.
-		 */
-		vacrel->bstrategy = NULL;
-
 		/* Disable index vacuuming, index cleanup, and heap rel truncation */
 		vacrel->do_index_vacuuming = false;
 		vacrel->do_index_cleanup = false;
@@ -3023,7 +3011,6 @@ lazy_vacuum_one_index(Relation indrel, IndexBulkDeleteResult *istat,
 	ivinfo.estimated_count = true;
 	ivinfo.message_level = DEBUG2;
 	ivinfo.num_heap_tuples = reltuples;
-	ivinfo.strategy = vacrel->bstrategy;
 
 	/*
 	 * Update error traceback information.
@@ -3074,7 +3061,6 @@ lazy_cleanup_one_index(Relation indrel, IndexBulkDeleteResult *istat,
 	ivinfo.message_level = DEBUG2;
 
 	ivinfo.num_heap_tuples = reltuples;
-	ivinfo.strategy = vacrel->bstrategy;
 
 	/*
 	 * Update error traceback information.
@@ -3354,8 +3340,7 @@ count_nondeletable_pages(LVRelState *vacrel, bool *lock_waiter_detected)
 			prefetchedUntil = prefetchStart;
 		}
 
-		buf = ReadBufferExtended(vacrel->rel, MAIN_FORKNUM, blkno, RBM_NORMAL,
-								 vacrel->bstrategy);
+		buf = ReadBufferExtended(vacrel->rel, MAIN_FORKNUM, blkno, RBM_NORMAL);
 
 		/* In this phase we only need shared access to the buffer */
 		LockBuffer(buf, BUFFER_LOCK_SHARE);
@@ -3446,8 +3431,7 @@ dead_items_alloc(LVRelState *vacrel, int nworkers)
 			vacrel->pvs = parallel_vacuum_init(vacrel->rel, vacrel->indrels,
 											   vacrel->nindexes, nworkers,
 											   vac_work_mem,
-											   vacrel->verbose ? INFO : DEBUG2,
-											   vacrel->bstrategy);
+											   vacrel->verbose ? INFO : DEBUG2);
 
 		/*
 		 * If parallel mode started, dead_items and dead_items_info spaces are
diff --git a/src/backend/access/heap/visibilitymap.c b/src/backend/access/heap/visibilitymap.c
index 4fd470702aa..b2612b1f710 100644
--- a/src/backend/access/heap/visibilitymap.c
+++ b/src/backend/access/heap/visibilitymap.c
@@ -575,7 +575,7 @@ vm_readbuf(Relation rel, BlockNumber blkno, bool extend)
 	}
 	else
 		buf = ReadBufferExtended(rel, VISIBILITYMAP_FORKNUM, blkno,
-								 RBM_ZERO_ON_ERROR, NULL);
+								 RBM_ZERO_ON_ERROR);
 
 	/*
 	 * Initializing the page when needed is trickier than it looks, because of
@@ -611,7 +611,7 @@ vm_extend(Relation rel, BlockNumber vm_nblocks)
 {
 	Buffer		buf;
 
-	buf = ExtendBufferedRelTo(BMR_REL(rel), VISIBILITYMAP_FORKNUM, NULL,
+	buf = ExtendBufferedRelTo(BMR_REL(rel), VISIBILITYMAP_FORKNUM,
 							  EB_CREATE_FORK_IF_NEEDED |
 							  EB_CLEAR_SIZE_CACHE,
 							  vm_nblocks,
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 406088dcb57..23f31f07f17 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -960,7 +960,7 @@ _bt_allocbuf(Relation rel, Relation heaprel)
 	 * otherwise would make, as we can't use _bt_lockbuf() without introducing
 	 * a race.
 	 */
-	buf = ExtendBufferedRel(BMR_REL(rel), MAIN_FORKNUM, NULL, EB_LOCK_FIRST);
+	buf = ExtendBufferedRel(BMR_REL(rel), MAIN_FORKNUM, EB_LOCK_FIRST);
 	if (!RelationUsesLocalBuffers(rel))
 		VALGRIND_MAKE_MEM_DEFINED(BufferGetPage(buf), BLCKSZ);
 
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index 3df2c752ead..379a2d37def 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -1324,7 +1324,6 @@ btvacuumscan(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 	stream = read_stream_begin_relation(READ_STREAM_MAINTENANCE |
 										READ_STREAM_FULL |
 										READ_STREAM_USE_BATCHING,
-										info->strategy,
 										rel,
 										MAIN_FORKNUM,
 										block_range_read_stream_cb,
@@ -1730,8 +1729,7 @@ backtrack:
 		 * recycle all-zero pages, not fail.  Also, we want to use a
 		 * nondefault buffer access strategy.
 		 */
-		buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL,
-								 info->strategy);
+		buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL);
 		goto backtrack;
 	}
 
diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c
index f2ee333f60d..785e385aab6 100644
--- a/src/backend/access/spgist/spgutils.c
+++ b/src/backend/access/spgist/spgutils.c
@@ -432,7 +432,7 @@ SpGistNewBuffer(Relation index)
 		ReleaseBuffer(buffer);
 	}
 
-	buffer = ExtendBufferedRel(BMR_REL(index), MAIN_FORKNUM, NULL,
+	buffer = ExtendBufferedRel(BMR_REL(index), MAIN_FORKNUM,
 							   EB_LOCK_FIRST);
 
 	return buffer;
diff --git a/src/backend/access/spgist/spgvacuum.c b/src/backend/access/spgist/spgvacuum.c
index c461f8dc02d..9f644edaf82 100644
--- a/src/backend/access/spgist/spgvacuum.c
+++ b/src/backend/access/spgist/spgvacuum.c
@@ -704,8 +704,7 @@ spgprocesspending(spgBulkDeleteState *bds)
 
 		/* examine the referenced page */
 		blkno = ItemPointerGetBlockNumber(&pitem->tid);
-		buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
-									RBM_NORMAL, bds->info->strategy);
+		buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno, RBM_NORMAL);
 		LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
 		page = BufferGetPage(buffer);
 
@@ -830,7 +829,6 @@ spgvacuumscan(spgBulkDeleteState *bds)
 	stream = read_stream_begin_relation(READ_STREAM_MAINTENANCE |
 										READ_STREAM_FULL |
 										READ_STREAM_USE_BATCHING,
-										bds->info->strategy,
 										index,
 										MAIN_FORKNUM,
 										block_range_read_stream_cb,
diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c
index f2e10b82b7d..0c16b7fef0f 100644
--- a/src/backend/access/transam/xloginsert.c
+++ b/src/backend/access/transam/xloginsert.c
@@ -1348,7 +1348,7 @@ log_newpage_range(Relation rel, ForkNumber forknum,
 		while (nbufs < XLR_MAX_BLOCK_ID && blkno < endblk)
 		{
 			Buffer		buf = ReadBufferExtended(rel, forknum, blkno,
-												 RBM_NORMAL, NULL);
+												 RBM_NORMAL);
 
 			LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c
index d8c179c5dcc..e5be49f8b5d 100644
--- a/src/backend/access/transam/xlogutils.c
+++ b/src/backend/access/transam/xlogutils.c
@@ -519,7 +519,7 @@ XLogReadBufferExtended(RelFileLocator rlocator, ForkNumber forknum,
 	{
 		/* page exists in file */
 		buffer = ReadBufferWithoutRelcache(rlocator, forknum, blkno,
-										   mode, NULL, true);
+										   mode, true);
 	}
 	else
 	{
@@ -536,7 +536,6 @@ XLogReadBufferExtended(RelFileLocator rlocator, ForkNumber forknum,
 		Assert(InRecovery);
 		buffer = ExtendBufferedRelTo(BMR_SMGR(smgr, RELPERSISTENCE_PERMANENT),
 									 forknum,
-									 NULL,
 									 EB_PERFORMING_RECOVERY |
 									 EB_SKIP_EXTENSION_LOCK,
 									 blkno + 1,
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..b0a7b8a33cd 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -3431,7 +3431,6 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	ivinfo.estimated_count = true;
 	ivinfo.message_level = DEBUG2;
 	ivinfo.num_heap_tuples = heapRelation->rd_rel->reltuples;
-	ivinfo.strategy = NULL;
 
 	/*
 	 * Encode TIDs as int8 values for the sort, rather than directly sorting
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..9b117a53124 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -72,7 +72,6 @@ int			default_statistics_target = 100;
 
 /* A few variables that don't seem worth passing around as parameters */
 static MemoryContext anl_context = NULL;
-static BufferAccessStrategy vac_strategy;
 
 
 static void do_analyze_rel(Relation onerel,
@@ -108,8 +107,7 @@ static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
  */
 void
 analyze_rel(Oid relid, RangeVar *relation,
-			const VacuumParams *params, List *va_cols, bool in_outer_xact,
-			BufferAccessStrategy bstrategy)
+			const VacuumParams *params, List *va_cols, bool in_outer_xact)
 {
 	Relation	onerel;
 	int			elevel;
@@ -124,7 +122,6 @@ analyze_rel(Oid relid, RangeVar *relation,
 		elevel = DEBUG2;
 
 	/* Set up static variables */
-	vac_strategy = bstrategy;
 
 	/*
 	 * Check for user-requested abort.
@@ -730,7 +727,6 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 			ivinfo.estimated_count = true;
 			ivinfo.message_level = elevel;
 			ivinfo.num_heap_tuples = onerel->rd_rel->reltuples;
-			ivinfo.strategy = vac_strategy;
 
 			stats = index_vacuum_cleanup(&ivinfo, NULL);
 
@@ -1302,7 +1298,6 @@ acquire_sample_rows(Relation onerel, int elevel,
 	 */
 	stream = read_stream_begin_relation(READ_STREAM_MAINTENANCE |
 										READ_STREAM_USE_BATCHING,
-										vac_strategy,
 										scan->rs_rd,
 										MAIN_FORKNUM,
 										block_sampling_read_stream_next,
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index f0819d15ab7..6f65d48b6bd 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -262,7 +262,6 @@ ScanSourceDatabasePgClass(Oid tbid, Oid dbid, char *srcpath)
 	LockRelId	relid;
 	Snapshot	snapshot;
 	SMgrRelation smgr;
-	BufferAccessStrategy bstrategy;
 
 	/* Get pg_class relfilenumber. */
 	relfilenumber = RelationMapOidToFilenumberForDatabase(srcpath,
@@ -282,9 +281,6 @@ ScanSourceDatabasePgClass(Oid tbid, Oid dbid, char *srcpath)
 	nblocks = smgrnblocks(smgr, MAIN_FORKNUM);
 	smgrclose(smgr);
 
-	/* Use a buffer access strategy since this is a bulk read operation. */
-	bstrategy = GetAccessStrategy(BAS_BULKREAD);
-
 	/*
 	 * As explained in the function header comments, we need a snapshot that
 	 * will see all committed transactions as committed, and our transaction
@@ -299,7 +295,7 @@ ScanSourceDatabasePgClass(Oid tbid, Oid dbid, char *srcpath)
 		CHECK_FOR_INTERRUPTS();
 
 		buf = ReadBufferWithoutRelcache(rlocator, MAIN_FORKNUM, blkno,
-										RBM_NORMAL, bstrategy, true);
+										RBM_NORMAL, true);
 
 		LockBuffer(buf, BUFFER_LOCK_SHARE);
 		page = BufferGetPage(buf);
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index faa07d1a118..b7d2fb4d228 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2456,7 +2456,7 @@ process_single_relation(RepackStmt *stmt, LOCKMODE lockmode, bool isTopLevel,
 			if (params->options & CLUOPT_VERBOSE)
 				vac_params.options |= VACOPT_VERBOSE;
 			analyze_rel(tableOid, NULL, &vac_params,
-						stmt->relation->va_cols, true, NULL);
+						stmt->relation->va_cols, true);
 			PopActiveSnapshot();
 			CommandCounterIncrement();
 		}
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 551667650ba..ac1ab948211 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -358,7 +358,7 @@ fill_seq_fork_with_data(Relation rel, HeapTuple tuple, ForkNumber forkNum)
 
 	/* Initialize first page of relation with special magic number */
 
-	buf = ExtendBufferedRel(BMR_REL(rel), forkNum, NULL,
+	buf = ExtendBufferedRel(BMR_REL(rel), forkNum,
 							EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK);
 	Assert(BufferGetBlockNumber(buf) == 0);
 
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..66acd27bd48 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -127,32 +127,11 @@ static void vac_truncate_clog(TransactionId frozenXID,
 							  TransactionId lastSaneFrozenXid,
 							  MultiXactId lastSaneMinMulti);
 static bool vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
-					   BufferAccessStrategy bstrategy, bool isTopLevel);
+					   bool isTopLevel);
 static double compute_parallel_delay(void);
 static VacOptValue get_vacoptval_from_boolean(DefElem *def);
 static bool vac_tid_reaped(ItemPointer itemptr, void *state);
 
-/*
- * GUC check function to ensure GUC value specified is within the allowable
- * range.
- */
-bool
-check_vacuum_buffer_usage_limit(int *newval, void **extra,
-								GucSource source)
-{
-	/* Value upper and lower hard limits are inclusive */
-	if (*newval == 0 || (*newval >= MIN_BAS_VAC_RING_SIZE_KB &&
-						 *newval <= MAX_BAS_VAC_RING_SIZE_KB))
-		return true;
-
-	/* Value does not fall within any allowable range */
-	GUC_check_errdetail("\"%s\" must be 0 or between %d kB and %d kB.",
-						"vacuum_buffer_usage_limit",
-						MIN_BAS_VAC_RING_SIZE_KB, MAX_BAS_VAC_RING_SIZE_KB);
-
-	return false;
-}
-
 /*
  * Primary entry point for manual VACUUM and ANALYZE commands
  *
@@ -163,7 +142,6 @@ void
 ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
 {
 	VacuumParams params;
-	BufferAccessStrategy bstrategy = NULL;
 	bool		verbose = false;
 	bool		skip_locked = false;
 	bool		analyze = false;
@@ -172,7 +150,6 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
 	bool		disable_page_skipping = false;
 	bool		process_main = true;
 	bool		process_toast = true;
-	int			ring_size;
 	bool		skip_database_stats = false;
 	bool		only_database_stats = false;
 	MemoryContext vac_context;
@@ -188,12 +165,6 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
 	/* Will be set later if we recurse to a TOAST table. */
 	params.toast_parent = InvalidOid;
 
-	/*
-	 * Set this to an invalid value so it is clear whether or not a
-	 * BUFFER_USAGE_LIMIT was specified when making the access strategy.
-	 */
-	ring_size = -1;
-
 	/* Parse options list */
 	foreach(lc, vacstmt->options)
 	{
@@ -204,32 +175,6 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
 			verbose = defGetBoolean(opt);
 		else if (strcmp(opt->defname, "skip_locked") == 0)
 			skip_locked = defGetBoolean(opt);
-		else if (strcmp(opt->defname, "buffer_usage_limit") == 0)
-		{
-			const char *hintmsg;
-			int			result;
-			char	   *vac_buffer_size;
-
-			vac_buffer_size = defGetString(opt);
-
-			/*
-			 * Check that the specified value is valid and the size falls
-			 * within the hard upper and lower limits if it is not 0.
-			 */
-			if (!parse_int(vac_buffer_size, &result, GUC_UNIT_KB, &hintmsg) ||
-				(result != 0 &&
-				 (result < MIN_BAS_VAC_RING_SIZE_KB || result > MAX_BAS_VAC_RING_SIZE_KB)))
-			{
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-						 errmsg("%s option must be 0 or between %d kB and %d kB",
-								"BUFFER_USAGE_LIMIT",
-								MIN_BAS_VAC_RING_SIZE_KB, MAX_BAS_VAC_RING_SIZE_KB),
-						 hintmsg ? errhint_internal("%s", _(hintmsg)) : 0));
-			}
-
-			ring_size = result;
-		}
 		else if (!vacstmt->is_vacuumcmd)
 			ereport(ERROR,
 					(errcode(ERRCODE_SYNTAX_ERROR),
@@ -325,17 +270,6 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("VACUUM FULL cannot be performed in parallel")));
 
-	/*
-	 * BUFFER_USAGE_LIMIT does nothing for VACUUM (FULL) so just raise an
-	 * ERROR for that case.  VACUUM (FULL, ANALYZE) does make use of it, so
-	 * we'll permit that.
-	 */
-	if (ring_size != -1 && (params.options & VACOPT_FULL) &&
-		!(params.options & VACOPT_ANALYZE))
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("BUFFER_USAGE_LIMIT cannot be specified for VACUUM FULL")));
-
 	/*
 	 * Make sure VACOPT_ANALYZE is specified if any column lists are present.
 	 */
@@ -431,38 +365,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
 										"Vacuum",
 										ALLOCSET_DEFAULT_SIZES);
 
-	/*
-	 * Make a buffer strategy object in the cross-transaction memory context.
-	 * We needn't bother making this for VACUUM (FULL) or VACUUM
-	 * (ONLY_DATABASE_STATS) as they'll not make use of it.  VACUUM (FULL,
-	 * ANALYZE) is possible, so we'd better ensure that we make a strategy
-	 * when we see ANALYZE.
-	 */
-	if ((params.options & (VACOPT_ONLY_DATABASE_STATS |
-						   VACOPT_FULL)) == 0 ||
-		(params.options & VACOPT_ANALYZE) != 0)
-	{
-
-		MemoryContext old_context = MemoryContextSwitchTo(vac_context);
-
-		Assert(ring_size >= -1);
-
-		/*
-		 * If BUFFER_USAGE_LIMIT was specified by the VACUUM or ANALYZE
-		 * command, it overrides the value of VacuumBufferUsageLimit.  Either
-		 * value may be 0, in which case GetAccessStrategyWithSize() will
-		 * return NULL, effectively allowing full use of shared buffers.
-		 */
-		if (ring_size == -1)
-			ring_size = VacuumBufferUsageLimit;
-
-		bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, ring_size);
-
-		MemoryContextSwitchTo(old_context);
-	}
-
 	/* Now go through the common routine */
-	vacuum(vacstmt->rels, &params, bstrategy, vac_context, isTopLevel);
+	vacuum(vacstmt->rels, &params, vac_context, isTopLevel);
 
 	/* Finally, clean up the vacuum memory context */
 	MemoryContextDelete(vac_context);
@@ -479,19 +383,13 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
  * params contains a set of parameters that can be used to customize the
  * behavior.
  *
- * bstrategy may be passed in as NULL when the caller does not want to
- * restrict the number of shared_buffers that VACUUM / ANALYZE can use,
- * otherwise, the caller must build a BufferAccessStrategy with the number of
- * shared_buffers that VACUUM / ANALYZE should try to limit themselves to
- * using.
- *
  * isTopLevel should be passed down from ProcessUtility.
  *
  * It is the caller's responsibility that all parameters are allocated in a
  * memory context that will not disappear at transaction commit.
  */
 void
-vacuum(List *relations, const VacuumParams *params, BufferAccessStrategy bstrategy,
+vacuum(List *relations, const VacuumParams *params,
 	   MemoryContext vac_context, bool isTopLevel)
 {
 	static bool in_vacuum = false;
@@ -630,7 +528,7 @@ vacuum(List *relations, const VacuumParams *params, BufferAccessStrategy bstrate
 
 			if (params->options & VACOPT_VACUUM)
 			{
-				if (!vacuum_rel(vrel->oid, vrel->relation, *params, bstrategy,
+				if (!vacuum_rel(vrel->oid, vrel->relation, *params,
 								isTopLevel))
 					continue;
 			}
@@ -649,7 +547,7 @@ vacuum(List *relations, const VacuumParams *params, BufferAccessStrategy bstrate
 				}
 
 				analyze_rel(vrel->oid, vrel->relation, params,
-							vrel->va_cols, in_outer_xact, bstrategy);
+							vrel->va_cols, in_outer_xact);
 
 				if (use_own_xacts)
 				{
@@ -2010,7 +1908,7 @@ vac_truncate_clog(TransactionId frozenXID,
  */
 static bool
 vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
-		   BufferAccessStrategy bstrategy, bool isTopLevel)
+		   bool isTopLevel)
 {
 	LOCKMODE	lmode;
 	Relation	rel;
@@ -2307,7 +2205,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			rel = NULL;
 		}
 		else
-			table_relation_vacuum(rel, &params, bstrategy);
+			table_relation_vacuum(rel, &params);
 	}
 
 	/* Roll back any GUC changes executed by index functions */
@@ -2344,7 +2242,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_vacuum_params.options |= VACOPT_PROCESS_MAIN;
 		toast_vacuum_params.toast_parent = relid;
 
-		vacuum_rel(toast_relid, NULL, toast_vacuum_params, bstrategy,
+		vacuum_rel(toast_relid, NULL, toast_vacuum_params,
 				   isTopLevel);
 	}
 
diff --git a/src/backend/commands/vacuumparallel.c b/src/backend/commands/vacuumparallel.c
index 41cefcfde54..8b4b835db9b 100644
--- a/src/backend/commands/vacuumparallel.c
+++ b/src/backend/commands/vacuumparallel.c
@@ -125,12 +125,6 @@ typedef struct PVShared
 	 */
 	int			maintenance_work_mem_worker;
 
-	/*
-	 * The number of buffers each worker's Buffer Access Strategy ring should
-	 * contain.
-	 */
-	int			ring_nbuffers;
-
 	/*
 	 * Shared vacuum cost balance.  During parallel vacuum,
 	 * VacuumSharedCostBalance points to this value and it accumulates the
@@ -257,9 +251,6 @@ struct ParallelVacuumState
 	int			nindexes_parallel_cleanup;
 	int			nindexes_parallel_condcleanup;
 
-	/* Buffer access strategy used by leader process */
-	BufferAccessStrategy bstrategy;
-
 	/*
 	 * Error reporting state.  The error callback is set only for workers
 	 * processes during parallel index vacuum.
@@ -304,7 +295,7 @@ static void parallel_vacuum_dsm_detach(dsm_segment *seg, Datum arg);
 ParallelVacuumState *
 parallel_vacuum_init(Relation rel, Relation *indrels, int nindexes,
 					 int nrequested_workers, int vac_work_mem,
-					 int elevel, BufferAccessStrategy bstrategy)
+					 int elevel)
 {
 	ParallelVacuumState *pvs;
 	ParallelContext *pcxt;
@@ -345,7 +336,6 @@ parallel_vacuum_init(Relation rel, Relation *indrels, int nindexes,
 	pvs->indrels = indrels;
 	pvs->nindexes = nindexes;
 	pvs->will_parallel_vacuum = will_parallel_vacuum;
-	pvs->bstrategy = bstrategy;
 	pvs->heaprel = rel;
 
 	EnterParallelMode();
@@ -447,9 +437,6 @@ parallel_vacuum_init(Relation rel, Relation *indrels, int nindexes,
 	shared->dead_items_handle = TidStoreGetHandle(dead_items);
 	shared->dead_items_dsa_handle = dsa_get_handle(TidStoreGetDSA(dead_items));
 
-	/* Use the same buffer size for all workers */
-	shared->ring_nbuffers = GetAccessStrategyBufferCount(bstrategy);
-
 	pg_atomic_init_u32(&(shared->cost_balance), 0);
 	pg_atomic_init_u32(&(shared->active_nworkers), 0);
 	pg_atomic_init_u32(&(shared->idx), 0);
@@ -1091,7 +1078,6 @@ parallel_vacuum_process_one_index(ParallelVacuumState *pvs, Relation indrel,
 	ivinfo.message_level = DEBUG2;
 	ivinfo.estimated_count = pvs->shared->estimated_count;
 	ivinfo.num_heap_tuples = pvs->shared->reltuples;
-	ivinfo.strategy = pvs->bstrategy;
 
 	/* Update error traceback information */
 	pvs->indname = pstrdup(RelationGetRelationName(indrel));
@@ -1294,10 +1280,6 @@ parallel_vacuum_main(dsm_segment *seg, shm_toc *toc)
 	pvs.indname = NULL;
 	pvs.status = PARALLEL_INDVAC_STATUS_INITIAL;
 
-	/* Each parallel VACUUM worker gets its own access strategy. */
-	pvs.bstrategy = GetAccessStrategyWithSize(BAS_VACUUM,
-											  shared->ring_nbuffers * (BLCKSZ / 1024));
-
 	/* Setup error traceback support for ereport() */
 	errcallback.callback = parallel_vacuum_error_callback;
 	errcallback.arg = &pvs;
@@ -1328,7 +1310,6 @@ parallel_vacuum_main(dsm_segment *seg, shm_toc *toc)
 
 	vac_close_indexes(nindexes, indrels, RowExclusiveLock);
 	table_close(rel, ShareUpdateExclusiveLock);
-	FreeAccessStrategy(pvs.bstrategy);
 
 	if (shared->is_autovacuum)
 		pv_shared_cost_params = NULL;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 45abf48768a..e021e798805 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -386,8 +386,7 @@ static void relation_needs_vacanalyze(Oid relid, AutoVacOpts *relopts,
 									  bool *dovacuum, bool *doanalyze, bool *wraparound,
 									  AutoVacuumScores *scores);
 
-static void autovacuum_do_vac_analyze(autovac_table *tab,
-									  BufferAccessStrategy bstrategy);
+static void autovacuum_do_vac_analyze(autovac_table *tab);
 static AutoVacOpts *extract_autovac_opts(HeapTuple tup,
 										 TupleDesc pg_class_desc);
 static void perform_work_item(AutoVacuumWorkItem *workitem);
@@ -1936,7 +1935,6 @@ do_autovacuum(void)
 	HASHCTL		ctl;
 	HTAB	   *table_toast_map;
 	ListCell   *volatile cell;
-	BufferAccessStrategy bstrategy;
 	ScanKeyData key;
 	TupleDesc	pg_class_desc;
 	int			effective_multixact_freeze_max_age;
@@ -2322,23 +2320,6 @@ do_autovacuum(void)
 		autovacuum_analyze_score_weight != 0.0)
 		list_sort(tables_to_process, TableToProcessComparator);
 
-	/*
-	 * Optionally, create a buffer access strategy object for VACUUM to use.
-	 * We use the same BufferAccessStrategy object for all tables VACUUMed by
-	 * this worker to prevent autovacuum from blowing out shared buffers.
-	 *
-	 * VacuumBufferUsageLimit being set to 0 results in
-	 * GetAccessStrategyWithSize returning NULL, effectively meaning we can
-	 * use up to all of shared buffers.
-	 *
-	 * If we later enter failsafe mode on any of the tables being vacuumed, we
-	 * will cease use of the BufferAccessStrategy only for that table.
-	 *
-	 * XXX should we consider adding code to adjust the size of this if
-	 * VacuumBufferUsageLimit changes?
-	 */
-	bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, VacuumBufferUsageLimit);
-
 	/*
 	 * create a memory context to act as fake PortalContext, so that the
 	 * contexts created in the vacuum code are cleaned up for each table.
@@ -2516,7 +2497,7 @@ do_autovacuum(void)
 			MemoryContextSwitchTo(PortalContext);
 
 			/* have at it */
-			autovacuum_do_vac_analyze(tab, bstrategy);
+			autovacuum_do_vac_analyze(tab);
 
 			/*
 			 * Clear a possible query-cancel signal, to avoid a late reaction
@@ -2636,8 +2617,6 @@ deleted:
 #ifdef USE_VALGRIND
 	hash_destroy(table_toast_map);
 	FreeTupleDesc(pg_class_desc);
-	if (bstrategy)
-		pfree(bstrategy);
 #endif
 
 	/* Run the rest in xact context, mainly to avoid Valgrind leak warnings */
@@ -3348,7 +3327,7 @@ relation_needs_vacanalyze(Oid relid,
  * disappear at transaction commit.
  */
 static void
-autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
+autovacuum_do_vac_analyze(autovac_table *tab)
 {
 	RangeVar   *rangevar;
 	VacuumRelation *rel;
@@ -3371,7 +3350,7 @@ autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
 	rel_list = list_make1(rel);
 	MemoryContextSwitchTo(old_context);
 
-	vacuum(rel_list, &tab->at_params, bstrategy, vac_context, true);
+	vacuum(rel_list, &tab->at_params, vac_context, true);
 
 	MemoryContextDelete(vac_context);
 }
diff --git a/src/backend/postmaster/datachecksum_state.c b/src/backend/postmaster/datachecksum_state.c
index 68557c16cb9..52390fd5423 100644
--- a/src/backend/postmaster/datachecksum_state.c
+++ b/src/backend/postmaster/datachecksum_state.c
@@ -390,7 +390,7 @@ static List *BuildRelationList(bool temp_relations, bool include_shared);
 static void FreeDatabaseList(List *dblist);
 static DataChecksumsWorkerResult ProcessDatabase(DataChecksumsWorkerDatabase *db);
 static bool ProcessAllDatabases(void);
-static bool ProcessSingleRelationFork(Relation reln, ForkNumber forkNum, BufferAccessStrategy strategy);
+static bool ProcessSingleRelationFork(Relation reln, ForkNumber forkNum);
 static void launcher_cancel_handler(SIGNAL_ARGS);
 static void WaitForAllTransactionsToFinish(void);
 
@@ -686,7 +686,7 @@ StartDataChecksumsWorkerLauncher(DataChecksumsWorkerOperation op,
  * error is raised in the lower levels.
  */
 static bool
-ProcessSingleRelationFork(Relation reln, ForkNumber forkNum, BufferAccessStrategy strategy)
+ProcessSingleRelationFork(Relation reln, ForkNumber forkNum)
 {
 	BlockNumber numblocks = RelationGetNumberOfBlocksInFork(reln, forkNum);
 	char		activity[NAMEDATALEN * 2 + 128];
@@ -709,7 +709,7 @@ ProcessSingleRelationFork(Relation reln, ForkNumber forkNum, BufferAccessStrateg
 	 */
 	for (BlockNumber blknum = 0; blknum < numblocks; blknum++)
 	{
-		Buffer		buf = ReadBufferExtended(reln, forkNum, blknum, RBM_NORMAL, strategy);
+		Buffer		buf = ReadBufferExtended(reln, forkNum, blknum, RBM_NORMAL);
 
 		/* Need to get an exclusive lock to mark the buffer as dirty */
 		LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
@@ -772,7 +772,7 @@ ProcessSingleRelationFork(Relation reln, ForkNumber forkNum, BufferAccessStrateg
  * error is raised in the lower levels.
  */
 static bool
-ProcessSingleRelationByOid(Oid relationId, BufferAccessStrategy strategy)
+ProcessSingleRelationByOid(Oid relationId)
 {
 	Relation	rel;
 	bool		aborted = false;
@@ -799,7 +799,7 @@ ProcessSingleRelationByOid(Oid relationId, BufferAccessStrategy strategy)
 	{
 		if (smgrexists(rel->rd_smgr, fnum))
 		{
-			if (!ProcessSingleRelationFork(rel, fnum, strategy))
+			if (!ProcessSingleRelationFork(rel, fnum))
 			{
 				aborted = true;
 				break;
@@ -1546,7 +1546,6 @@ DataChecksumsWorkerMain(Datum arg)
 	Oid			dboid;
 	List	   *RelationList = NIL;
 	List	   *InitialTempTableList = NIL;
-	BufferAccessStrategy strategy;
 	bool		aborted = false;
 	int64		rels_done;
 	bool		process_shared;
@@ -1613,11 +1612,6 @@ DataChecksumsWorkerMain(Datum arg)
 	VacuumUpdateCosts();
 	VacuumCostBalance = 0;
 
-	/*
-	 * Create and set the vacuum strategy as our buffer strategy.
-	 */
-	strategy = GetAccessStrategy(BAS_VACUUM);
-
 	RelationList = BuildRelationList(false, process_shared);
 
 	/* Update the total number of relations to be processed in this DB. */
@@ -1639,9 +1633,7 @@ DataChecksumsWorkerMain(Datum arg)
 	rels_done = 0;
 	foreach_oid(reloid, RelationList)
 	{
-		bool		costs_updated = false;
-
-		if (!ProcessSingleRelationByOid(reloid, strategy))
+		if (!ProcessSingleRelationByOid(reloid))
 		{
 			aborted = true;
 			break;
@@ -1657,8 +1649,7 @@ DataChecksumsWorkerMain(Datum arg)
 
 		/*
 		 * Check if the cost settings changed during runtime and if so, update
-		 * to reflect the new values and signal that the access strategy needs
-		 * to be refreshed.
+		 * to reflect the new values.
 		 */
 		LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
 		if (DataChecksumState->worker_invocation != worker_invocation)
@@ -1669,7 +1660,6 @@ DataChecksumsWorkerMain(Datum arg)
 		if ((DataChecksumState->launch_cost_delay != DataChecksumState->cost_delay)
 			|| (DataChecksumState->launch_cost_limit != DataChecksumState->cost_limit))
 		{
-			costs_updated = true;
 			VacuumCostDelay = DataChecksumState->launch_cost_delay;
 			VacuumCostLimit = DataChecksumState->launch_cost_limit;
 			VacuumUpdateCosts();
@@ -1677,19 +1667,10 @@ DataChecksumsWorkerMain(Datum arg)
 			DataChecksumState->cost_delay = DataChecksumState->launch_cost_delay;
 			DataChecksumState->cost_limit = DataChecksumState->launch_cost_limit;
 		}
-		else
-			costs_updated = false;
 		LWLockRelease(DataChecksumsWorkerLock);
-
-		if (costs_updated)
-		{
-			FreeAccessStrategy(strategy);
-			strategy = GetAccessStrategy(BAS_VACUUM);
-		}
 	}
 
 	list_free(RelationList);
-	FreeAccessStrategy(strategy);
 
 	if (aborted || abort_requested)
 	{
diff --git a/src/backend/storage/aio/read_stream.c b/src/backend/storage/aio/read_stream.c
index a318539e56c..08d344f063d 100644
--- a/src/backend/storage/aio/read_stream.c
+++ b/src/backend/storage/aio/read_stream.c
@@ -757,7 +757,6 @@ read_stream_look_ahead(ReadStream *stream)
  */
 static ReadStream *
 read_stream_begin_impl(int flags,
-					   BufferAccessStrategy strategy,
 					   Relation rel,
 					   SMgrRelation smgr,
 					   char persistence,
@@ -771,7 +770,6 @@ read_stream_begin_impl(int flags,
 	int16		queue_size;
 	int16		queue_overflow;
 	int			max_ios;
-	int			strategy_pin_limit;
 	uint32		max_pinned_buffers;
 	uint32		max_possible_buffer_limit;
 	Oid			tablespace_id;
@@ -835,10 +833,6 @@ read_stream_begin_impl(int flags,
 	max_pinned_buffers = Min(max_pinned_buffers,
 							 PG_INT16_MAX - queue_overflow - 1);
 
-	/* Give the strategy a chance to limit the number of buffers we pin. */
-	strategy_pin_limit = GetAccessStrategyPinLimit(strategy);
-	max_pinned_buffers = Min(strategy_pin_limit, max_pinned_buffers);
-
 	/*
 	 * Also limit our queue to the maximum number of pins we could ever be
 	 * allowed to acquire according to the buffer manager.  We may not really
@@ -962,7 +956,6 @@ read_stream_begin_impl(int flags,
 		stream->ios[i].op.smgr = smgr;
 		stream->ios[i].op.persistence = persistence;
 		stream->ios[i].op.forknum = forknum;
-		stream->ios[i].op.strategy = strategy;
 	}
 
 	return stream;
@@ -974,7 +967,6 @@ read_stream_begin_impl(int flags,
  */
 ReadStream *
 read_stream_begin_relation(int flags,
-						   BufferAccessStrategy strategy,
 						   Relation rel,
 						   ForkNumber forknum,
 						   ReadStreamBlockNumberCB callback,
@@ -982,7 +974,6 @@ read_stream_begin_relation(int flags,
 						   size_t per_buffer_data_size)
 {
 	return read_stream_begin_impl(flags,
-								  strategy,
 								  rel,
 								  RelationGetSmgr(rel),
 								  rel->rd_rel->relpersistence,
@@ -998,7 +989,6 @@ read_stream_begin_relation(int flags,
  */
 ReadStream *
 read_stream_begin_smgr_relation(int flags,
-								BufferAccessStrategy strategy,
 								SMgrRelation smgr,
 								char smgr_persistence,
 								ForkNumber forknum,
@@ -1007,7 +997,6 @@ read_stream_begin_smgr_relation(int flags,
 								size_t per_buffer_data_size)
 {
 	return read_stream_begin_impl(flags,
-								  strategy,
 								  NULL,
 								  smgr,
 								  smgr_persistence,
@@ -1370,13 +1359,11 @@ read_stream_next_buffer(ReadStream *stream, void **per_buffer_data)
  * Transitional support for code that would like to perform or skip reads
  * itself, without using the stream.  Returns, and consumes, the next block
  * number that would be read by the stream's look-ahead algorithm, or
- * InvalidBlockNumber if the end of the stream is reached.  Also reports the
- * strategy that would be used to read it.
+ * InvalidBlockNumber if the end of the stream is reached.
  */
 BlockNumber
-read_stream_next_block(ReadStream *stream, BufferAccessStrategy *strategy)
+read_stream_next_block(ReadStream *stream)
 {
-	*strategy = stream->ios[0].op.strategy;
 	return read_stream_get_block(stream, NULL);
 }
 
diff --git a/src/backend/storage/buffer/README b/src/backend/storage/buffer/README
index b332e002ba1..58df6bd011a 100644
--- a/src/backend/storage/buffer/README
+++ b/src/backend/storage/buffer/README
@@ -203,50 +203,6 @@ have to give up and try another buffer.  This however is not a concern
 of the basic select-a-victim-buffer algorithm.)
 
 
-Buffer Ring Replacement Strategy
----------------------------------
-
-When running a query that needs to access a large number of pages just once,
-such as VACUUM or a large sequential scan, a different strategy is used.
-A page that has been touched only by such a scan is unlikely to be needed
-again soon, so instead of running the normal clock-sweep algorithm and
-blowing out the entire buffer cache, a small ring of buffers is allocated
-using the normal clock-sweep algorithm and those buffers are reused for the
-whole scan.  This also implies that much of the write traffic caused by such
-a statement will be done by the backend itself and not pushed off onto other
-processes.
-
-For sequential scans, a 256KB ring is used. That's small enough to fit in L2
-cache, which makes transferring pages from OS cache to shared buffer cache
-efficient.  Even less would often be enough, but the ring must be big enough
-to accommodate all pages in the scan that are pinned concurrently.  256KB
-should also be enough to leave a small cache trail for other backends to
-join in a synchronized seq scan.  If a ring buffer is dirtied and its LSN
-updated, we would normally have to write and flush WAL before we could
-re-use the buffer; in this case we instead discard the buffer from the ring
-and (later) choose a replacement using the normal clock-sweep algorithm.
-Hence this strategy works best for scans that are read-only (or at worst
-update hint bits).  In a scan that modifies every page in the scan, like a
-bulk UPDATE or DELETE, the buffers in the ring will always be dirtied and
-the ring strategy effectively degrades to the normal strategy.
-
-VACUUM uses a ring like sequential scans, however, the size of this ring is
-controlled by the vacuum_buffer_usage_limit GUC.  Dirty pages are not removed
-from the ring.  Instead, the WAL is flushed if needed to allow reuse of the
-buffers.  Before introducing the buffer ring strategy in 8.3, VACUUM's buffers
-were sent to the freelist, which was effectively a buffer ring of 1 buffer,
-resulting in excessive WAL flushing.
-
-Bulk writes work similarly to VACUUM.  Currently this applies only to
-COPY IN and CREATE TABLE AS SELECT.  (Might it be interesting to make
-seqscan UPDATE and DELETE use the bulkwrite strategy?)  For bulk writes
-we use a ring size of 16MB (but not more than 1/8th of shared_buffers).
-Smaller sizes have been shown to result in the COPY blocking too often
-for WAL flushes.  While it's okay for a background vacuum to be slowed by
-doing its own WAL flushing, we'd prefer that COPY not be subject to that,
-so we let it use up a bit more of the buffer arena.
-
-
 Background Writer's Processing
 ------------------------------
 
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 1173b50b7c1..30f2443e577 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -612,10 +612,9 @@ ForgetPrivateRefCountEntry(PrivateRefCountEntry *ref)
 static Buffer ReadBuffer_common(Relation rel,
 								SMgrRelation smgr, char smgr_persistence,
 								ForkNumber forkNum, BlockNumber blockNum,
-								ReadBufferMode mode, BufferAccessStrategy strategy);
+								ReadBufferMode mode);
 static BlockNumber ExtendBufferedRelCommon(BufferManagerRelation bmr,
 										   ForkNumber fork,
-										   BufferAccessStrategy strategy,
 										   uint32 flags,
 										   uint32 extend_by,
 										   BlockNumber extend_upto,
@@ -623,13 +622,12 @@ static BlockNumber ExtendBufferedRelCommon(BufferManagerRelation bmr,
 										   uint32 *extended_by);
 static BlockNumber ExtendBufferedRelShared(BufferManagerRelation bmr,
 										   ForkNumber fork,
-										   BufferAccessStrategy strategy,
 										   uint32 flags,
 										   uint32 extend_by,
 										   BlockNumber extend_upto,
 										   Buffer *buffers,
 										   uint32 *extended_by);
-static bool PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy,
+static bool PinBuffer(BufferDesc *buf,
 					  bool skip_if_not_valid);
 static void PinBuffer_Locked(BufferDesc *buf);
 static void UnpinBuffer(BufferDesc *buf);
@@ -645,7 +643,6 @@ static inline BufferDesc *BufferAlloc(SMgrRelation smgr,
 									  char relpersistence,
 									  ForkNumber forkNum,
 									  BlockNumber blockNum,
-									  BufferAccessStrategy strategy,
 									  bool *foundPtr, IOContext io_context);
 static bool AsyncReadBuffers(ReadBuffersOperation *operation, int *nblocks_progress);
 static void CheckReadBuffersOperation(ReadBuffersOperation *operation, bool is_complete);
@@ -654,7 +651,7 @@ static pg_attribute_always_inline void TrackBufferHit(IOObject io_object,
 													  IOContext io_context,
 													  Relation rel, char persistence, SMgrRelation smgr,
 													  ForkNumber forknum, BlockNumber blocknum);
-static Buffer GetVictimBuffer(BufferAccessStrategy strategy, IOContext io_context);
+static Buffer GetVictimBuffer(IOContext io_context);
 static void FlushUnlockedBuffer(BufferDesc *buf, SMgrRelation reln,
 								IOObject io_object, IOContext io_context);
 static void FlushBuffer(BufferDesc *buf, SMgrRelation reln,
@@ -858,7 +855,7 @@ ReadRecentBuffer(RelFileLocator rlocator, ForkNumber forkNum, BlockNumber blockN
 		 * pin.
 		 */
 		if (BufferTagsEqual(&tag, &bufHdr->tag) &&
-			PinBuffer(bufHdr, NULL, true))
+			PinBuffer(bufHdr, true))
 		{
 			if (BufferTagsEqual(&tag, &bufHdr->tag))
 			{
@@ -874,12 +871,12 @@ ReadRecentBuffer(RelFileLocator rlocator, ForkNumber forkNum, BlockNumber blockN
 
 /*
  * ReadBuffer -- a shorthand for ReadBufferExtended, for reading from main
- *		fork with RBM_NORMAL mode and default strategy.
+ *		fork with RBM_NORMAL mode.
  */
 Buffer
 ReadBuffer(Relation reln, BlockNumber blockNum)
 {
-	return ReadBufferExtended(reln, MAIN_FORKNUM, blockNum, RBM_NORMAL, NULL);
+	return ReadBufferExtended(reln, MAIN_FORKNUM, blockNum, RBM_NORMAL);
 }
 
 /*
@@ -919,13 +916,10 @@ ReadBuffer(Relation reln, BlockNumber blockNum)
  * a cleanup-strength lock on the page.
  *
  * RBM_NORMAL_NO_LOG mode is treated the same as RBM_NORMAL here.
- *
- * If strategy is not NULL, a nondefault buffer access strategy is used.
- * See buffer/README for details.
  */
 inline Buffer
 ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum,
-				   ReadBufferMode mode, BufferAccessStrategy strategy)
+				   ReadBufferMode mode)
 {
 	Buffer		buf;
 
@@ -935,7 +929,7 @@ ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum,
 	 * ReadBuffer_common().
 	 */
 	buf = ReadBuffer_common(reln, RelationGetSmgr(reln), 0,
-							forkNum, blockNum, mode, strategy);
+							forkNum, blockNum, mode);
 
 	return buf;
 }
@@ -954,14 +948,14 @@ ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum,
 Buffer
 ReadBufferWithoutRelcache(RelFileLocator rlocator, ForkNumber forkNum,
 						  BlockNumber blockNum, ReadBufferMode mode,
-						  BufferAccessStrategy strategy, bool permanent)
+						  bool permanent)
 {
 	SMgrRelation smgr = smgropen(rlocator, INVALID_PROC_NUMBER);
 
 	return ReadBuffer_common(NULL, smgr,
 							 permanent ? RELPERSISTENCE_PERMANENT : RELPERSISTENCE_UNLOGGED,
 							 forkNum, blockNum,
-							 mode, strategy);
+							 mode);
 }
 
 /*
@@ -970,13 +964,12 @@ ReadBufferWithoutRelcache(RelFileLocator rlocator, ForkNumber forkNum,
 Buffer
 ExtendBufferedRel(BufferManagerRelation bmr,
 				  ForkNumber forkNum,
-				  BufferAccessStrategy strategy,
 				  uint32 flags)
 {
 	Buffer		buf;
 	uint32		extend_by = 1;
 
-	ExtendBufferedRelBy(bmr, forkNum, strategy, flags, extend_by,
+	ExtendBufferedRelBy(bmr, forkNum, flags, extend_by,
 						&buf, &extend_by);
 
 	return buf;
@@ -1002,7 +995,6 @@ ExtendBufferedRel(BufferManagerRelation bmr,
 BlockNumber
 ExtendBufferedRelBy(BufferManagerRelation bmr,
 					ForkNumber fork,
-					BufferAccessStrategy strategy,
 					uint32 flags,
 					uint32 extend_by,
 					Buffer *buffers,
@@ -1015,7 +1007,7 @@ ExtendBufferedRelBy(BufferManagerRelation bmr,
 	if (bmr.relpersistence == '\0')
 		bmr.relpersistence = bmr.rel->rd_rel->relpersistence;
 
-	return ExtendBufferedRelCommon(bmr, fork, strategy, flags,
+	return ExtendBufferedRelCommon(bmr, fork, flags,
 								   extend_by, InvalidBlockNumber,
 								   buffers, extended_by);
 }
@@ -1031,7 +1023,6 @@ ExtendBufferedRelBy(BufferManagerRelation bmr,
 Buffer
 ExtendBufferedRelTo(BufferManagerRelation bmr,
 					ForkNumber fork,
-					BufferAccessStrategy strategy,
 					uint32 flags,
 					BlockNumber extend_to,
 					ReadBufferMode mode)
@@ -1097,7 +1088,7 @@ ExtendBufferedRelTo(BufferManagerRelation bmr,
 		if ((uint64) current_size + num_pages > extend_to)
 			num_pages = extend_to - current_size;
 
-		first_block = ExtendBufferedRelCommon(bmr, fork, strategy, flags,
+		first_block = ExtendBufferedRelCommon(bmr, fork, flags,
 											  num_pages, extend_to,
 											  buffers, &extended_by);
 
@@ -1123,7 +1114,7 @@ ExtendBufferedRelTo(BufferManagerRelation bmr,
 	{
 		Assert(extended_by == 0);
 		buffer = ReadBuffer_common(bmr.rel, BMR_GET_SMGR(bmr), bmr.relpersistence,
-								   fork, extend_to - 1, mode, strategy);
+								   fork, extend_to - 1, mode);
 	}
 
 	return buffer;
@@ -1226,7 +1217,6 @@ PinBufferForBlock(Relation rel,
 				  char persistence,
 				  ForkNumber forkNum,
 				  BlockNumber blockNum,
-				  BufferAccessStrategy strategy,
 				  IOObject io_object,
 				  IOContext io_context,
 				  bool *foundPtr)
@@ -1250,7 +1240,7 @@ PinBufferForBlock(Relation rel,
 		bufHdr = LocalBufferAlloc(smgr, forkNum, blockNum, foundPtr);
 	else
 		bufHdr = BufferAlloc(smgr, persistence, forkNum, blockNum,
-							 strategy, foundPtr, io_context);
+							 foundPtr, io_context);
 
 	if (*foundPtr)
 		TrackBufferHit(io_object, io_context, rel, persistence, smgr, forkNum, blockNum);
@@ -1276,8 +1266,7 @@ PinBufferForBlock(Relation rel,
 static pg_attribute_always_inline Buffer
 ReadBuffer_common(Relation rel, SMgrRelation smgr, char smgr_persistence,
 				  ForkNumber forkNum,
-				  BlockNumber blockNum, ReadBufferMode mode,
-				  BufferAccessStrategy strategy)
+				  BlockNumber blockNum, ReadBufferMode mode)
 {
 	ReadBuffersOperation operation;
 	Buffer		buffer;
@@ -1313,7 +1302,7 @@ ReadBuffer_common(Relation rel, SMgrRelation smgr, char smgr_persistence,
 		if (mode == RBM_ZERO_AND_LOCK || mode == RBM_ZERO_AND_CLEANUP_LOCK)
 			flags |= EB_LOCK_FIRST;
 
-		return ExtendBufferedRel(BMR_REL(rel), forkNum, strategy, flags);
+		return ExtendBufferedRel(BMR_REL(rel), forkNum, flags);
 	}
 
 	if (rel)
@@ -1335,12 +1324,12 @@ ReadBuffer_common(Relation rel, SMgrRelation smgr, char smgr_persistence,
 		}
 		else
 		{
-			io_context = IOContextForStrategy(strategy);
+			io_context = IOCONTEXT_NORMAL;
 			io_object = IOOBJECT_RELATION;
 		}
 
 		buffer = PinBufferForBlock(rel, smgr, persistence,
-								   forkNum, blockNum, strategy,
+								   forkNum, blockNum,
 								   io_object, io_context, &found);
 		ZeroAndLockBuffer(buffer, mode, found);
 		return buffer;
@@ -1358,7 +1347,6 @@ ReadBuffer_common(Relation rel, SMgrRelation smgr, char smgr_persistence,
 	operation.rel = rel;
 	operation.persistence = persistence;
 	operation.forknum = forkNum;
-	operation.strategy = strategy;
 	if (StartReadBuffer(&operation,
 						&buffer,
 						blockNum,
@@ -1399,7 +1387,7 @@ StartReadBuffersImpl(ReadBuffersOperation *operation,
 	}
 	else
 	{
-		io_context = IOContextForStrategy(operation->strategy);
+		io_context = IOCONTEXT_NORMAL;
 		io_object = IOOBJECT_RELATION;
 	}
 
@@ -1450,7 +1438,6 @@ StartReadBuffersImpl(ReadBuffersOperation *operation,
 										   operation->persistence,
 										   operation->forknum,
 										   blockNum + i,
-										   operation->strategy,
 										   io_object, io_context,
 										   &found);
 		}
@@ -1771,7 +1758,7 @@ WaitReadBuffers(ReadBuffersOperation *operation)
 	}
 	else
 	{
-		io_context = IOContextForStrategy(operation->strategy);
+		io_context = IOCONTEXT_NORMAL;
 		io_object = IOOBJECT_RELATION;
 	}
 
@@ -1961,7 +1948,7 @@ AsyncReadBuffers(ReadBuffersOperation *operation, int *nblocks_progress)
 	}
 	else
 	{
-		io_context = IOContextForStrategy(operation->strategy);
+		io_context = IOCONTEXT_NORMAL;
 		io_object = IOOBJECT_RELATION;
 	}
 
@@ -2180,24 +2167,18 @@ AsyncReadBuffers(ReadBuffersOperation *operation, int *nblocks_progress)
  *		buffer.  If no buffer exists already, selects a replacement victim and
  *		evicts the old page, but does NOT read in new page.
  *
- * "strategy" can be a buffer replacement strategy object, or NULL for
- * the default strategy.  The selected buffer's usage_count is advanced when
- * using the default strategy, but otherwise possibly not (see PinBuffer).
- *
  * The returned buffer is pinned and is already marked as holding the
  * desired page.  If it already did have the desired page, *foundPtr is
  * set true.  Otherwise, *foundPtr is set false.
  *
- * io_context is passed as an output parameter to avoid calling
- * IOContextForStrategy() when there is a shared buffers hit and no IO
- * statistics need be captured.
+ * io_context is passed as an output parameter to avoid capturing IO
+ * statistics when there is a shared buffers hit and no IO occurs.
  *
  * No locks are held either at entry or exit.
  */
 static pg_attribute_always_inline BufferDesc *
 BufferAlloc(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 			BlockNumber blockNum,
-			BufferAccessStrategy strategy,
 			bool *foundPtr, IOContext io_context)
 {
 	BufferTag	newTag;			/* identity of requested block */
@@ -2235,7 +2216,7 @@ BufferAlloc(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 		 */
 		buf = GetBufferDescriptor(existing_buf_id);
 
-		valid = PinBuffer(buf, strategy, false);
+		valid = PinBuffer(buf, false);
 
 		/* Can release the mapping lock as soon as we've pinned it */
 		LWLockRelease(newPartitionLock);
@@ -2266,7 +2247,7 @@ BufferAlloc(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 	 * don't hold any conflicting locks. If so we'll have to undo our work
 	 * later.
 	 */
-	victim_buffer = GetVictimBuffer(strategy, io_context);
+	victim_buffer = GetVictimBuffer(io_context);
 	victim_buf_hdr = GetBufferDescriptor(victim_buffer - 1);
 
 	/*
@@ -2297,7 +2278,7 @@ BufferAlloc(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 
 		existing_buf_hdr = GetBufferDescriptor(existing_buf_id);
 
-		valid = PinBuffer(existing_buf_hdr, strategy, false);
+		valid = PinBuffer(existing_buf_hdr, false);
 
 		/* Can release the mapping lock as soon as we've pinned it */
 		LWLockRelease(newPartitionLock);
@@ -2335,9 +2316,12 @@ BufferAlloc(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 	 * just like permanent relations.
 	 */
 	set_bits |= BM_TAG_VALID;
-	/* Admit the newly loaded page COOL (probation); a second access via
+
+	/*
+	 * Admit the newly loaded page COOL (probation); a second access via
 	 * PinBuffer promotes it to HOT.  This is what makes a one-touch scan
-	 * self-evicting -- see the cooling-state notes in buf_internals.h. */
+	 * self-evicting -- see the cooling-state notes in buf_internals.h.
+	 */
 	if (relpersistence == RELPERSISTENCE_PERMANENT || forkNum == INIT_FORKNUM)
 		set_bits |= BM_PERMANENT;
 
@@ -2549,12 +2533,11 @@ InvalidateVictimBuffer(BufferDesc *buf_hdr)
 }
 
 static Buffer
-GetVictimBuffer(BufferAccessStrategy strategy, IOContext io_context)
+GetVictimBuffer(IOContext io_context)
 {
 	BufferDesc *buf_hdr;
 	Buffer		buf;
 	uint64		buf_state;
-	bool		from_ring;
 
 	/*
 	 * Ensure, before we pin a victim buffer, that there's a free refcount
@@ -2570,7 +2553,7 @@ again:
 	 * Select a victim buffer.  The buffer is returned pinned and owned by
 	 * this backend.
 	 */
-	buf_hdr = StrategyGetBuffer(strategy, &buf_state, &from_ring);
+	buf_hdr = StrategyGetBuffer(&buf_state);
 	buf = BufferDescriptorGetBuffer(buf_hdr);
 
 	/*
@@ -2614,26 +2597,6 @@ again:
 			goto again;
 		}
 
-		/*
-		 * If using a nondefault strategy, and this victim came from the
-		 * strategy ring, let the strategy decide whether to reject it when
-		 * reusing it would require a WAL flush.  This only applies to
-		 * permanent buffers; unlogged buffers can have fake LSNs, so
-		 * XLogNeedsFlush() is not meaningful for them.
-		 *
-		 * We need to hold the content lock in at least share-exclusive mode
-		 * to safely inspect the page LSN, so this couldn't have been done
-		 * inside StrategyGetBuffer().
-		 */
-		if (strategy && from_ring &&
-			buf_state & BM_PERMANENT &&
-			XLogNeedsFlush(BufferGetLSN(buf_hdr)) &&
-			StrategyRejectBuffer(strategy, buf_hdr, from_ring))
-		{
-			UnlockReleaseBuffer(buf);
-			goto again;
-		}
-
 		/* OK, do the I/O */
 		FlushBuffer(buf_hdr, NULL, IOOBJECT_RELATION, io_context);
 		LockBuffer(buf, BUFFER_LOCK_UNLOCK);
@@ -2646,23 +2609,15 @@ again:
 	if (buf_state & BM_VALID)
 	{
 		/*
-		 * When a BufferAccessStrategy is in use, blocks evicted from shared
-		 * buffers are counted as IOOP_EVICT in the corresponding context
-		 * (e.g. IOCONTEXT_BULKWRITE). Shared buffers are evicted by a
-		 * strategy in two cases: 1) while initially claiming buffers for the
-		 * strategy ring 2) to replace an existing strategy ring buffer
-		 * because it is pinned or in use and cannot be reused.
+		 * Blocks evicted from shared buffers are counted as IOOP_EVICT.
 		 *
-		 * Blocks evicted from buffers already in the strategy ring are
-		 * counted as IOOP_REUSE in the corresponding strategy context.
-		 *
-		 * At this point, we can accurately count evictions and reuses,
-		 * because we have successfully claimed the valid buffer. Previously,
-		 * we may have been forced to release the buffer due to concurrent
-		 * pinners or erroring out.
+		 * At this point, we can accurately count evictions, because we have
+		 * successfully claimed the valid buffer. Previously, we may have been
+		 * forced to release the buffer due to concurrent pinners or erroring
+		 * out.
 		 */
 		pgstat_count_io_op(IOOBJECT_RELATION, io_context,
-						   from_ring ? IOOP_REUSE : IOOP_EVICT, 1, 0);
+						   IOOP_EVICT, 1, 0);
 	}
 
 	/*
@@ -2754,7 +2709,6 @@ LimitAdditionalPins(uint32 *additional_pins)
 static BlockNumber
 ExtendBufferedRelCommon(BufferManagerRelation bmr,
 						ForkNumber fork,
-						BufferAccessStrategy strategy,
 						uint32 flags,
 						uint32 extend_by,
 						BlockNumber extend_upto,
@@ -2789,7 +2743,7 @@ ExtendBufferedRelCommon(BufferManagerRelation bmr,
 											 buffers, &extend_by);
 	}
 	else
-		first_block = ExtendBufferedRelShared(bmr, fork, strategy, flags,
+		first_block = ExtendBufferedRelShared(bmr, fork, flags,
 											  extend_by, extend_upto,
 											  buffers, &extend_by);
 	*extended_by = extend_by;
@@ -2812,7 +2766,6 @@ ExtendBufferedRelCommon(BufferManagerRelation bmr,
 static BlockNumber
 ExtendBufferedRelShared(BufferManagerRelation bmr,
 						ForkNumber fork,
-						BufferAccessStrategy strategy,
 						uint32 flags,
 						uint32 extend_by,
 						BlockNumber extend_upto,
@@ -2820,7 +2773,7 @@ ExtendBufferedRelShared(BufferManagerRelation bmr,
 						uint32 *extended_by)
 {
 	BlockNumber first_block;
-	IOContext	io_context = IOContextForStrategy(strategy);
+	IOContext	io_context = IOCONTEXT_NORMAL;
 	instr_time	io_start;
 
 	LimitAdditionalPins(&extend_by);
@@ -2839,7 +2792,7 @@ ExtendBufferedRelShared(BufferManagerRelation bmr,
 	{
 		Block		buf_block;
 
-		buffers[i] = GetVictimBuffer(strategy, io_context);
+		buffers[i] = GetVictimBuffer(io_context);
 		buf_block = BufHdrGetBlock(GetBufferDescriptor(buffers[i] - 1));
 
 		/* new buffers are zero-filled */
@@ -2957,7 +2910,7 @@ ExtendBufferedRelShared(BufferManagerRelation bmr,
 			 * Pin the existing buffer before releasing the partition lock,
 			 * preventing it from being evicted.
 			 */
-			valid = PinBuffer(existing_hdr, strategy, false);
+			valid = PinBuffer(existing_hdr, false);
 
 			LWLockRelease(partition_lock);
 			UnpinBuffer(victim_buf_hdr);
@@ -3007,8 +2960,11 @@ ExtendBufferedRelShared(BufferManagerRelation bmr,
 			victim_buf_hdr->tag = tag;
 
 			set_bits |= BM_TAG_VALID;
-			/* Admit COOL (probation); see the comment at the other admission
-			 * site and the cooling-state notes in buf_internals.h. */
+
+			/*
+			 * Admit COOL (probation); see the comment at the other admission
+			 * site and the cooling-state notes in buf_internals.h.
+			 */
 			if (bmr.relpersistence == RELPERSISTENCE_PERMANENT || fork == INIT_FORKNUM)
 				set_bits |= BM_PERMANENT;
 
@@ -3275,13 +3231,8 @@ ReleaseAndReadBuffer(Buffer buffer,
 /*
  * PinBuffer -- make buffer unavailable for replacement.
  *
- * For the default access strategy, the buffer's usage_count is incremented
- * when we first pin it; for other strategies we just make sure the usage_count
- * isn't zero.  (The idea of the latter is that we don't want synchronized
- * heap scans to inflate the count, but we need it to not be zero to discourage
- * other backends from stealing buffers from our ring.  As long as we cycle
- * through the ring faster than the global clock-sweep cycles, buffers in
- * our ring won't be chosen as victims for replacement by other backends.)
+ * The buffer's cooling state is promoted to HOT (the 2Q rescue) when we pin
+ * it; see the cooling-state notes in buf_internals.h.
  *
  * This should be applied only to shared buffers, never local ones.
  *
@@ -3298,7 +3249,7 @@ ReleaseAndReadBuffer(Buffer buffer,
  * (recently) invalid and has not been pinned.
  */
 static bool
-PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy,
+PinBuffer(BufferDesc *buf,
 		  bool skip_if_not_valid)
 {
 	Buffer		b = BufferDescriptorGetBuffer(buf);
@@ -3396,7 +3347,7 @@ PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy,
  * ResourceOwnerEnlarge(CurrentResourceOwner);
  *
  * Currently, no callers of this function want to modify the buffer's
- * usage_count at all, so there's no need for a strategy parameter.
+ * cooling state at all.
  * Also we don't bother with a BM_VALID test (the caller could check that for
  * itself).
  *
@@ -4659,22 +4610,8 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln, IOObject io_object,
 			  false);
 
 	/*
-	 * When a strategy is in use, only flushes of dirty buffers already in the
-	 * strategy ring are counted as strategy writes (IOCONTEXT
-	 * [BULKREAD|BULKWRITE|VACUUM] IOOP_WRITE) for the purpose of IO
-	 * statistics tracking.
-	 *
-	 * If a shared buffer initially added to the ring must be flushed before
-	 * being used, this is counted as an IOCONTEXT_NORMAL IOOP_WRITE.
-	 *
-	 * If a shared buffer which was added to the ring later because the
-	 * current strategy buffer is pinned or in use or because all strategy
-	 * buffers were dirty and rejected (for BAS_BULKREAD operations only)
-	 * requires flushing, this is counted as an IOCONTEXT_NORMAL IOOP_WRITE
-	 * (from_ring will be false).
-	 *
-	 * When a strategy is not in use, the write can only be a "regular" write
-	 * of a dirty shared buffer (IOCONTEXT_NORMAL IOOP_WRITE).
+	 * All writes of a dirty shared buffer are counted as an IOCONTEXT_NORMAL
+	 * IOOP_WRITE for the purpose of IO statistics tracking.
 	 */
 	pgstat_count_io_op_time(io_object, io_context,
 							IOOP_WRITE, io_start, 1, BLCKSZ);
@@ -5435,8 +5372,6 @@ RelationCopyStorageUsingBuffer(RelFileLocator srclocator,
 	BlockNumber nblocks;
 	BlockNumber blkno;
 	PGIOAlignedBlock buf;
-	BufferAccessStrategy bstrategy_src;
-	BufferAccessStrategy bstrategy_dst;
 	BlockRangeReadStreamPrivate p;
 	ReadStream *src_stream;
 	SMgrRelation src_smgr;
@@ -5464,10 +5399,6 @@ RelationCopyStorageUsingBuffer(RelFileLocator srclocator,
 	smgrextend(smgropen(dstlocator, INVALID_PROC_NUMBER), forkNum, nblocks - 1,
 			   buf.data, true);
 
-	/* This is a bulk operation, so use buffer access strategies. */
-	bstrategy_src = GetAccessStrategy(BAS_BULKREAD);
-	bstrategy_dst = GetAccessStrategy(BAS_BULKWRITE);
-
 	/* Initialize streaming read */
 	p.current_blocknum = 0;
 	p.last_exclusive = nblocks;
@@ -5479,7 +5410,6 @@ RelationCopyStorageUsingBuffer(RelFileLocator srclocator,
 	 */
 	src_stream = read_stream_begin_smgr_relation(READ_STREAM_FULL |
 												 READ_STREAM_USE_BATCHING,
-												 bstrategy_src,
 												 src_smgr,
 												 permanent ? RELPERSISTENCE_PERMANENT : RELPERSISTENCE_UNLOGGED,
 												 forkNum,
@@ -5499,7 +5429,7 @@ RelationCopyStorageUsingBuffer(RelFileLocator srclocator,
 
 		dstBuf = ReadBufferWithoutRelcache(dstlocator, forkNum,
 										   BufferGetBlockNumber(srcBuf),
-										   RBM_ZERO_AND_LOCK, bstrategy_dst,
+										   RBM_ZERO_AND_LOCK,
 										   permanent);
 		dstPage = BufferGetPage(dstBuf);
 
@@ -5520,9 +5450,6 @@ RelationCopyStorageUsingBuffer(RelFileLocator srclocator,
 	}
 	Assert(read_stream_next_buffer(src_stream, NULL) == InvalidBuffer);
 	read_stream_end(src_stream);
-
-	FreeAccessStrategy(bstrategy_src);
-	FreeAccessStrategy(bstrategy_dst);
 }
 
 /* ---------------------------------------------------------------------
diff --git a/src/backend/storage/buffer/freelist.c b/src/backend/storage/buffer/freelist.c
index cc77144224c..63b2aa9b607 100644
--- a/src/backend/storage/buffer/freelist.c
+++ b/src/backend/storage/buffer/freelist.c
@@ -77,40 +77,6 @@ const ShmemCallbacks StrategyCtlShmemCallbacks = {
 	.init_fn = StrategyCtlShmemInit,
 };
 
-/*
- * Private (non-shared) state for managing a ring of shared buffers to re-use.
- * This is currently the only kind of BufferAccessStrategy object, but someday
- * we might have more kinds.
- */
-typedef struct BufferAccessStrategyData
-{
-	/* Overall strategy type */
-	BufferAccessStrategyType btype;
-	/* Number of elements in buffers[] array */
-	int			nbuffers;
-
-	/*
-	 * Index of the "current" slot in the ring, ie, the one most recently
-	 * returned by GetBufferFromRing.
-	 */
-	int			current;
-
-	/*
-	 * Array of buffer numbers.  InvalidBuffer (that is, zero) indicates we
-	 * have not yet selected a buffer for this ring slot.  For allocation
-	 * simplicity this is palloc'd together with the fixed fields of the
-	 * struct.
-	 */
-	Buffer		buffers[FLEXIBLE_ARRAY_MEMBER];
-}			BufferAccessStrategyData;
-
-
-/* Prototypes for internal functions */
-static BufferDesc *GetBufferFromRing(BufferAccessStrategy strategy,
-									 uint64 *buf_state);
-static void AddBufferToRing(BufferAccessStrategy strategy,
-							BufferDesc *buf);
-
 /*
  * Per-backend state for the batched clock sweep.  Each backend claims a run
  * of consecutive clock-hand values with a single atomic fetch-add and then
@@ -157,8 +123,8 @@ ClockSweepTick(void)
 			 * must hold the spinlock so StrategySyncStart() can read
 			 * nextVictimBuffer and completePasses consistently.
 			 *
-			 * With batching, multiple backends may each land a fetch-add
-			 * that returns a value past NBuffers in the same pass.  After
+			 * With batching, multiple backends may each land a fetch-add that
+			 * returns a value past NBuffers in the same pass.  After
 			 * acquiring the spinlock we re-read the counter: if another
 			 * backend already wrapped it below NBuffers we are done.
 			 */
@@ -196,8 +162,6 @@ ClockSweepTick(void)
  *	GetVictimBuffer(). The only hard requirement GetVictimBuffer() has is that
  *	the selected buffer must not currently be pinned by anyone.
  *
- *	strategy is a BufferAccessStrategy object, or NULL for default strategy.
- *
  *	It is the callers responsibility to ensure the buffer ownership can be
  *	tracked via TrackNewBufferPin().
  *
@@ -205,29 +169,13 @@ ClockSweepTick(void)
  *	before returning.
  */
 BufferDesc *
-StrategyGetBuffer(BufferAccessStrategy strategy, uint64 *buf_state, bool *from_ring)
+StrategyGetBuffer(uint64 *buf_state)
 {
 	BufferDesc *buf;
 	int			bgwprocno;
 	int			trycounter;
 	bool		force_cool;
 
-	*from_ring = false;
-
-	/*
-	 * If given a strategy object, see whether it can select a buffer. We
-	 * assume strategy objects don't need buffer_strategy_lock.
-	 */
-	if (strategy != NULL)
-	{
-		buf = GetBufferFromRing(strategy, buf_state);
-		if (buf != NULL)
-		{
-			*from_ring = true;
-			return buf;
-		}
-	}
-
 	/*
 	 * If asked, we need to waken the bgwriter. Since we don't want to rely on
 	 * a spinlock for this we force a read from shared memory once, and then
@@ -256,8 +204,7 @@ StrategyGetBuffer(BufferAccessStrategy strategy, uint64 *buf_state, bool *from_r
 
 	/*
 	 * We count buffer allocation requests so that the bgwriter can estimate
-	 * the rate of buffer consumption.  Note that buffers recycled by a
-	 * strategy object are intentionally not counted here.
+	 * the rate of buffer consumption.
 	 */
 	pg_atomic_fetch_add_u32(&StrategyControl->numBufferAllocs, 1);
 
@@ -323,7 +270,7 @@ StrategyGetBuffer(BufferAccessStrategy strategy, uint64 *buf_state, bool *from_r
 				if (!force_cool)
 				{
 					no_progress = true;
-					break;			/* advance the hand, look for COOL */
+					break;		/* advance the hand, look for COOL */
 				}
 
 				local_buf_state &= ~BUF_USAGECOUNT_MASK;	/* HOT -> COOL, clear ref bit */
@@ -356,8 +303,6 @@ StrategyGetBuffer(BufferAccessStrategy strategy, uint64 *buf_state, bool *from_r
 												   local_buf_state))
 				{
 					/* Found a usable buffer */
-					if (strategy != NULL)
-						AddBufferToRing(strategy, buf);
 					*buf_state = local_buf_state;
 
 					TrackNewBufferPin(BufferDescriptorGetBuffer(buf));
@@ -368,11 +313,12 @@ StrategyGetBuffer(BufferAccessStrategy strategy, uint64 *buf_state, bool *from_r
 		}
 
 		/*
-		 * A tick that made no progress toward a victim counts down trycounter.
-		 * A full unproductive pass escalates to force_cool (cool HOT buffers
-		 * into victims); a second unproductive full pass means everything is
-		 * pinned, so fail rather than spin forever.  (A failed CAS above is
-		 * neither progress nor a full miss: we simply retry the same buffer.)
+		 * A tick that made no progress toward a victim counts down
+		 * trycounter. A full unproductive pass escalates to force_cool (cool
+		 * HOT buffers into victims); a second unproductive full pass means
+		 * everything is pinned, so fail rather than spin forever.  (A failed
+		 * CAS above is neither progress nor a full miss: we simply retry the
+		 * same buffer.)
 		 */
 		if (no_progress && --trycounter == 0)
 		{
@@ -500,361 +446,3 @@ StrategyCtlShmemInit(void *arg)
 	else
 		StrategyControl->batchSize = 1;
 }
-
-
-/* ----------------------------------------------------------------
- *				Backend-private buffer ring management
- * ----------------------------------------------------------------
- */
-
-
-/*
- * GetAccessStrategy -- create a BufferAccessStrategy object
- *
- * The object is allocated in the current memory context.
- */
-BufferAccessStrategy
-GetAccessStrategy(BufferAccessStrategyType btype)
-{
-	int			ring_size_kb;
-
-	/*
-	 * Select ring size to use.  See buffer/README for rationales.
-	 *
-	 * Note: if you change the ring size for BAS_BULKREAD, see also
-	 * SYNC_SCAN_REPORT_INTERVAL in access/heap/syncscan.c.
-	 */
-	switch (btype)
-	{
-		case BAS_NORMAL:
-			/* if someone asks for NORMAL, just give 'em a "default" object */
-			return NULL;
-
-		case BAS_BULKREAD:
-			{
-				int			ring_max_kb;
-
-				/*
-				 * The ring always needs to be large enough to allow some
-				 * separation in time between providing a buffer to the user
-				 * of the strategy and that buffer being reused. Otherwise the
-				 * user's pin will prevent reuse of the buffer, even without
-				 * concurrent activity.
-				 *
-				 * We also need to ensure the ring always is large enough for
-				 * SYNC_SCAN_REPORT_INTERVAL, as noted above.
-				 *
-				 * Thus we start out a minimal size and increase the size
-				 * further if appropriate.
-				 */
-				ring_size_kb = 256;
-
-				/*
-				 * There's no point in a larger ring if we won't be allowed to
-				 * pin sufficiently many buffers.  But we never limit to less
-				 * than the minimal size above.
-				 */
-				ring_max_kb = GetPinLimit() * (BLCKSZ / 1024);
-				ring_max_kb = Max(ring_size_kb, ring_max_kb);
-
-				/*
-				 * We would like the ring to additionally have space for the
-				 * configured degree of IO concurrency. While being read in,
-				 * buffers can obviously not yet be reused.
-				 *
-				 * Each IO can be up to io_combine_limit blocks large, and we
-				 * want to start up to effective_io_concurrency IOs.
-				 *
-				 * Note that effective_io_concurrency may be 0, which disables
-				 * AIO.
-				 */
-				ring_size_kb += (BLCKSZ / 1024) *
-					io_combine_limit * effective_io_concurrency;
-
-				if (ring_size_kb > ring_max_kb)
-					ring_size_kb = ring_max_kb;
-				break;
-			}
-		case BAS_BULKWRITE:
-			ring_size_kb = 16 * 1024;
-			break;
-		case BAS_VACUUM:
-			ring_size_kb = 2048;
-			break;
-
-		default:
-			elog(ERROR, "unrecognized buffer access strategy: %d",
-				 (int) btype);
-			return NULL;		/* keep compiler quiet */
-	}
-
-	return GetAccessStrategyWithSize(btype, ring_size_kb);
-}
-
-/*
- * GetAccessStrategyWithSize -- create a BufferAccessStrategy object with a
- *		number of buffers equivalent to the passed in size.
- *
- * If the given ring size is 0, no BufferAccessStrategy will be created and
- * the function will return NULL.  ring_size_kb must not be negative.
- */
-BufferAccessStrategy
-GetAccessStrategyWithSize(BufferAccessStrategyType btype, int ring_size_kb)
-{
-	int			ring_buffers;
-	BufferAccessStrategy strategy;
-
-	Assert(ring_size_kb >= 0);
-
-	/* Figure out how many buffers ring_size_kb is */
-	ring_buffers = ring_size_kb / (BLCKSZ / 1024);
-
-	/* 0 means unlimited, so no BufferAccessStrategy required */
-	if (ring_buffers == 0)
-		return NULL;
-
-	/* Cap to 1/8th of shared_buffers */
-	ring_buffers = Min(NBuffers / 8, ring_buffers);
-
-	/* NBuffers should never be less than 16, so this shouldn't happen */
-	Assert(ring_buffers > 0);
-
-	/* Allocate the object and initialize all elements to zeroes */
-	strategy = (BufferAccessStrategy)
-		palloc0(offsetof(BufferAccessStrategyData, buffers) +
-				ring_buffers * sizeof(Buffer));
-
-	/* Set fields that don't start out zero */
-	strategy->btype = btype;
-	strategy->nbuffers = ring_buffers;
-
-	return strategy;
-}
-
-/*
- * GetAccessStrategyBufferCount -- an accessor for the number of buffers in
- *		the ring
- *
- * Returns 0 on NULL input to match behavior of GetAccessStrategyWithSize()
- * returning NULL with 0 size.
- */
-int
-GetAccessStrategyBufferCount(BufferAccessStrategy strategy)
-{
-	if (strategy == NULL)
-		return 0;
-
-	return strategy->nbuffers;
-}
-
-/*
- * GetAccessStrategyPinLimit -- get cap of number of buffers that should be pinned
- *
- * When pinning extra buffers to look ahead, users of a ring-based strategy are
- * in danger of pinning too much of the ring at once while performing look-ahead.
- * For some strategies, that means "escaping" from the ring, and in others it
- * means forcing dirty data to disk very frequently with associated WAL
- * flushing.  Since external code has no insight into any of that, allow
- * individual strategy types to expose a clamp that should be applied when
- * deciding on a maximum number of buffers to pin at once.
- *
- * Callers should combine this number with other relevant limits and take the
- * minimum.
- */
-int
-GetAccessStrategyPinLimit(BufferAccessStrategy strategy)
-{
-	if (strategy == NULL)
-		return NBuffers;
-
-	switch (strategy->btype)
-	{
-		case BAS_BULKREAD:
-
-			/*
-			 * Since BAS_BULKREAD uses StrategyRejectBuffer(), dirty buffers
-			 * shouldn't be a problem and the caller is free to pin up to the
-			 * entire ring at once.
-			 */
-			return strategy->nbuffers;
-
-		default:
-
-			/*
-			 * Tell caller not to pin more than half the buffers in the ring.
-			 * This is a trade-off between look ahead distance and deferring
-			 * writeback and associated WAL traffic.
-			 */
-			return strategy->nbuffers / 2;
-	}
-}
-
-/*
- * FreeAccessStrategy -- release a BufferAccessStrategy object
- *
- * A simple pfree would do at the moment, but we would prefer that callers
- * don't assume that much about the representation of BufferAccessStrategy.
- */
-void
-FreeAccessStrategy(BufferAccessStrategy strategy)
-{
-	/* don't crash if called on a "default" strategy */
-	if (strategy != NULL)
-		pfree(strategy);
-}
-
-/*
- * GetBufferFromRing -- returns a buffer from the ring, or NULL if the
- *		ring is empty / not usable.
- *
- * The buffer is pinned and marked as owned, using TrackNewBufferPin(), before
- * returning.
- */
-static BufferDesc *
-GetBufferFromRing(BufferAccessStrategy strategy, uint64 *buf_state)
-{
-	BufferDesc *buf;
-	Buffer		bufnum;
-	uint64		old_buf_state;
-	uint64		local_buf_state;	/* to avoid repeated (de-)referencing */
-
-
-	/* Advance to next ring slot */
-	if (++strategy->current >= strategy->nbuffers)
-		strategy->current = 0;
-
-	/*
-	 * If the slot hasn't been filled yet, tell the caller to allocate a new
-	 * buffer with the normal allocation strategy.  He will then fill this
-	 * slot by calling AddBufferToRing with the new buffer.
-	 */
-	bufnum = strategy->buffers[strategy->current];
-	if (bufnum == InvalidBuffer)
-		return NULL;
-
-	buf = GetBufferDescriptor(bufnum - 1);
-
-	/*
-	 * Check whether the buffer can be used and pin it if so. Do this using a
-	 * CAS loop, to avoid having to lock the buffer header.
-	 */
-	old_buf_state = pg_atomic_read_u64(&buf->state);
-	for (;;)
-	{
-		local_buf_state = old_buf_state;
-
-		/*
-		 * If the buffer is pinned we cannot use it under any circumstances.
-		 *
-		 * With the cooling-state replacement the field holds only COOL or HOT,
-		 * so the stock "usage_count > 1 means another backend touched it"
-		 * heuristic no longer applies: a ring element is reusable whenever it
-		 * is unpinned.  (The whole ring mechanism is removed in a later patch;
-		 * scan resistance is now intrinsic to the sweep.)
-		 */
-		if (BUF_STATE_GET_REFCOUNT(local_buf_state) != 0)
-			break;
-
-		/* See equivalent code in PinBuffer() */
-		if (unlikely(local_buf_state & BM_LOCKED))
-		{
-			old_buf_state = WaitBufHdrUnlocked(buf);
-			continue;
-		}
-
-		/* pin the buffer if the CAS succeeds */
-		local_buf_state += BUF_REFCOUNT_ONE;
-
-		if (pg_atomic_compare_exchange_u64(&buf->state, &old_buf_state,
-										   local_buf_state))
-		{
-			*buf_state = local_buf_state;
-
-			TrackNewBufferPin(BufferDescriptorGetBuffer(buf));
-			return buf;
-		}
-	}
-
-	/*
-	 * Tell caller to allocate a new buffer with the normal allocation
-	 * strategy.  He'll then replace this ring element via AddBufferToRing.
-	 */
-	return NULL;
-}
-
-/*
- * AddBufferToRing -- add a buffer to the buffer ring
- *
- * Caller must hold the buffer header spinlock on the buffer.  Since this
- * is called with the spinlock held, it had better be quite cheap.
- */
-static void
-AddBufferToRing(BufferAccessStrategy strategy, BufferDesc *buf)
-{
-	strategy->buffers[strategy->current] = BufferDescriptorGetBuffer(buf);
-}
-
-/*
- * Utility function returning the IOContext of a given BufferAccessStrategy's
- * strategy ring.
- */
-IOContext
-IOContextForStrategy(BufferAccessStrategy strategy)
-{
-	if (!strategy)
-		return IOCONTEXT_NORMAL;
-
-	switch (strategy->btype)
-	{
-		case BAS_NORMAL:
-
-			/*
-			 * Currently, GetAccessStrategy() returns NULL for
-			 * BufferAccessStrategyType BAS_NORMAL, so this case is
-			 * unreachable.
-			 */
-			pg_unreachable();
-			return IOCONTEXT_NORMAL;
-		case BAS_BULKREAD:
-			return IOCONTEXT_BULKREAD;
-		case BAS_BULKWRITE:
-			return IOCONTEXT_BULKWRITE;
-		case BAS_VACUUM:
-			return IOCONTEXT_VACUUM;
-	}
-
-	elog(ERROR, "unrecognized BufferAccessStrategyType: %d", strategy->btype);
-	pg_unreachable();
-}
-
-/*
- * StrategyRejectBuffer -- consider rejecting a dirty buffer
- *
- * When a nondefault strategy is used, the buffer manager calls this function
- * when it turns out that the buffer selected by StrategyGetBuffer needs to
- * be written out and doing so would require flushing WAL too.  This gives us
- * a chance to choose a different victim.
- *
- * Returns true if buffer manager should ask for a new victim, and false
- * if this buffer should be written and re-used.
- */
-bool
-StrategyRejectBuffer(BufferAccessStrategy strategy, BufferDesc *buf, bool from_ring)
-{
-	/* We only do this in bulkread mode */
-	if (strategy->btype != BAS_BULKREAD)
-		return false;
-
-	/* Don't muck with behavior of normal buffer-replacement strategy */
-	if (!from_ring ||
-		strategy->buffers[strategy->current] != BufferDescriptorGetBuffer(buf))
-		return false;
-
-	/*
-	 * Remove the dirty buffer from the ring; necessary to prevent infinite
-	 * loop if all ring members are dirty.
-	 */
-	strategy->buffers[strategy->current] = InvalidBuffer;
-
-	return true;
-}
diff --git a/src/backend/storage/freespace/freespace.c b/src/backend/storage/freespace/freespace.c
index 006edab9d77..29cb2d70f66 100644
--- a/src/backend/storage/freespace/freespace.c
+++ b/src/backend/storage/freespace/freespace.c
@@ -603,7 +603,7 @@ fsm_readbuf(Relation rel, FSMAddress addr, bool extend)
 			return InvalidBuffer;
 	}
 	else
-		buf = ReadBufferExtended(rel, FSM_FORKNUM, blkno, RBM_ZERO_ON_ERROR, NULL);
+		buf = ReadBufferExtended(rel, FSM_FORKNUM, blkno, RBM_ZERO_ON_ERROR);
 
 	/*
 	 * Initializing the page when needed is trickier than it looks, because of
@@ -638,7 +638,7 @@ fsm_readbuf(Relation rel, FSMAddress addr, bool extend)
 static Buffer
 fsm_extend(Relation rel, BlockNumber fsm_nblocks)
 {
-	return ExtendBufferedRelTo(BMR_REL(rel), FSM_FORKNUM, NULL,
+	return ExtendBufferedRelTo(BMR_REL(rel), FSM_FORKNUM,
 							   EB_CREATE_FORK_IF_NEEDED |
 							   EB_CLEAR_SIZE_CACHE,
 							   fsm_nblocks,
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index 718c1cfc0f9..3c3e35ab8a8 100644
--- a/src/backend/storage/smgr/md.c
+++ b/src/backend/storage/smgr/md.c
@@ -1540,15 +1540,8 @@ register_dirty_segment(SMgrRelation reln, ForkNumber forknum, MdfdVec *seg)
 							FilePathName(seg->mdfd_vfd))));
 
 		/*
-		 * We have no way of knowing if the current IOContext is
-		 * IOCONTEXT_NORMAL or IOCONTEXT_[BULKREAD, BULKWRITE, VACUUM] at this
-		 * point, so count the fsync as being in the IOCONTEXT_NORMAL
-		 * IOContext. This is probably okay, because the number of backend
-		 * fsyncs doesn't say anything about the efficacy of the
-		 * BufferAccessStrategy. And counting both fsyncs done in
-		 * IOCONTEXT_NORMAL and IOCONTEXT_[BULKREAD, BULKWRITE, VACUUM] under
-		 * IOCONTEXT_NORMAL is likely clearer when investigating the number of
-		 * backend fsyncs.
+		 * Count the fsync in the IOCONTEXT_NORMAL IOContext, the only
+		 * IOContext relations are read/written under.
 		 */
 		pgstat_count_io_op_time(IOOBJECT_RELATION, IOCONTEXT_NORMAL,
 								IOOP_FSYNC, io_start, 1, 0);
diff --git a/src/backend/utils/activity/pgstat_io.c b/src/backend/utils/activity/pgstat_io.c
index 38bae7b15d2..d66a23749d5 100644
--- a/src/backend/utils/activity/pgstat_io.c
+++ b/src/backend/utils/activity/pgstat_io.c
@@ -241,16 +241,10 @@ pgstat_get_io_context_name(IOContext io_context)
 {
 	switch (io_context)
 	{
-		case IOCONTEXT_BULKREAD:
-			return "bulkread";
-		case IOCONTEXT_BULKWRITE:
-			return "bulkwrite";
 		case IOCONTEXT_INIT:
 			return "init";
 		case IOCONTEXT_NORMAL:
 			return "normal";
-		case IOCONTEXT_VACUUM:
-			return "vacuum";
 	}
 
 	elog(ERROR, "unrecognized IOContext value: %d", io_context);
@@ -451,18 +445,6 @@ pgstat_tracks_io_object(BackendType bktype, IOObject io_object,
 	 * IOContexts, and, while it may not be inherently incorrect for them to
 	 * do so, excluding those rows from the view makes the view easier to use.
 	 */
-	if ((bktype == B_CHECKPOINTER || bktype == B_BG_WRITER) &&
-		(io_context == IOCONTEXT_BULKREAD ||
-		 io_context == IOCONTEXT_BULKWRITE ||
-		 io_context == IOCONTEXT_VACUUM))
-		return false;
-
-	if (bktype == B_AUTOVAC_LAUNCHER && io_context == IOCONTEXT_VACUUM)
-		return false;
-
-	if ((bktype == B_AUTOVAC_WORKER || bktype == B_AUTOVAC_LAUNCHER) &&
-		io_context == IOCONTEXT_BULKWRITE)
-		return false;
 
 	return true;
 }
@@ -479,8 +461,6 @@ bool
 pgstat_tracks_io_op(BackendType bktype, IOObject io_object,
 					IOContext io_context, IOOp io_op)
 {
-	bool		strategy_io_context;
-
 	/* if (io_context, io_object) will never collect stats, we're done */
 	if (!pgstat_tracks_io_object(bktype, io_object, io_context))
 		return false;
@@ -521,17 +501,13 @@ pgstat_tracks_io_op(BackendType bktype, IOObject io_object,
 	/*
 	 * Some IOOps are not valid in certain IOContexts and some IOOps are only
 	 * valid in certain contexts.
+	 *
+	 * IOOP_REUSE was only relevant when a BufferAccessStrategy was in use.
+	 * Buffer access strategies (ring buffers) have been removed -- scan
+	 * resistance is now intrinsic to the cooling-stage clock sweep -- so
+	 * IOOP_REUSE never occurs.
 	 */
-	if (io_context == IOCONTEXT_BULKREAD && io_op == IOOP_EXTEND)
-		return false;
-
-	strategy_io_context = io_context == IOCONTEXT_BULKREAD ||
-		io_context == IOCONTEXT_BULKWRITE || io_context == IOCONTEXT_VACUUM;
-
-	/*
-	 * IOOP_REUSE is only relevant when a BufferAccessStrategy is in use.
-	 */
-	if (!strategy_io_context && io_op == IOOP_REUSE)
+	if (io_op == IOOP_REUSE)
 		return false;
 
 	/*
@@ -545,14 +521,5 @@ pgstat_tracks_io_op(BackendType bktype, IOObject io_object,
 		!(io_op == IOOP_WRITE || io_op == IOOP_READ || io_op == IOOP_FSYNC))
 		return false;
 
-	/*
-	 * IOOP_FSYNC IOOps done by a backend using a BufferAccessStrategy are
-	 * counted in the IOCONTEXT_NORMAL IOContext. See comment in
-	 * register_dirty_segment() for more details.
-	 */
-	if (strategy_io_context && io_op == IOOP_FSYNC)
-		return false;
-
-
 	return true;
 }
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index bbd28d14d99..555c04272f5 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -149,7 +149,6 @@ int			autovacuum_max_parallel_workers = 0;
 int			MaxBackends = 0;
 
 /* GUC parameters for vacuum */
-int			VacuumBufferUsageLimit = 2048;
 
 int			VacuumCostPageHit = 1;
 int			VacuumCostPageMiss = 2;
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index dd799a5e70f..bcde0c1ee3e 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -3326,16 +3326,6 @@
   boot_val => 'DEFAULT_UPDATE_PROCESS_TITLE',
 },
 
-{ name => 'vacuum_buffer_usage_limit', type => 'int', context => 'PGC_USERSET', group => 'RESOURCES_MEM',
-  short_desc => 'Sets the buffer pool size for VACUUM, ANALYZE, and autovacuum.',
-  flags => 'GUC_UNIT_KB',
-  variable => 'VacuumBufferUsageLimit',
-  boot_val => '2048',
-  min => '0',
-  max => 'MAX_BAS_VAC_RING_SIZE_KB',
-  check_hook => 'check_vacuum_buffer_usage_limit',
-},
-
 { name => 'vacuum_cost_delay', type => 'real', context => 'PGC_USERSET', group => 'VACUUM_COST_DELAY',
   short_desc => 'Vacuum cost delay in milliseconds.',
   flags => 'GUC_UNIT_MS',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 47e1221f3c3..ba393a1fe28 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -164,9 +164,6 @@
                                         #   mmap
                                         # (change requires restart)
 #min_dynamic_shared_memory = 0MB        # (change requires restart)
-#vacuum_buffer_usage_limit = 2MB        # size of vacuum and analyze buffer access strategy ring;
-                                        # 0 to disable vacuum buffer access strategy;
-                                        # range 128kB to 16GB
 
 # SLRU buffers (change requires restart)
 #commit_timestamp_buffers = 0           # memory for pg_commit_ts (0 = auto)
diff --git a/src/bin/scripts/vacuumdb.c b/src/bin/scripts/vacuumdb.c
index f8158ca6a78..f78e57ff2c3 100644
--- a/src/bin/scripts/vacuumdb.c
+++ b/src/bin/scripts/vacuumdb.c
@@ -57,7 +57,6 @@ main(int argc, char *argv[])
 		{"no-truncate", no_argument, NULL, 10},
 		{"no-process-toast", no_argument, NULL, 11},
 		{"no-process-main", no_argument, NULL, 12},
-		{"buffer-usage-limit", required_argument, NULL, 13},
 		{"missing-stats-only", no_argument, NULL, 14},
 		{"dry-run", no_argument, NULL, 15},
 		{NULL, 0, NULL, 0}
@@ -202,9 +201,6 @@ main(int argc, char *argv[])
 			case 12:
 				vacopts.process_main = false;
 				break;
-			case 13:
-				vacopts.buffer_usage_limit = escape_quotes(optarg);
-				break;
 			case 14:
 				vacopts.missing_stats_only = true;
 				break;
@@ -290,14 +286,6 @@ main(int argc, char *argv[])
 		pg_fatal("cannot use the \"%s\" option with the \"%s\" option",
 				 "no-index-cleanup", "force-index-cleanup");
 
-	/*
-	 * buffer-usage-limit is not allowed with VACUUM FULL unless ANALYZE is
-	 * included too.
-	 */
-	if (vacopts.buffer_usage_limit && vacopts.full && !vacopts.and_analyze)
-		pg_fatal("cannot use the \"%s\" option with the \"%s\" option",
-				 "buffer-usage-limit", "full");
-
 	/*
 	 * Prohibit --missing-stats-only without --analyze-only or
 	 * --analyze-in-stages.
@@ -352,7 +340,6 @@ help(const char *progname)
 	printf(_("  %s [OPTION]... [DBNAME]\n"), progname);
 	printf(_("\nOptions:\n"));
 	printf(_("  -a, --all                       vacuum all databases\n"));
-	printf(_("      --buffer-usage-limit=SIZE   size of ring buffer used for vacuum\n"));
 	printf(_("  -d, --dbname=DBNAME             database to vacuum\n"));
 	printf(_("      --disable-page-skipping     disable all page-skipping behavior\n"));
 	printf(_("      --dry-run                   show the commands that would be sent to the server\n"));
diff --git a/src/bin/scripts/vacuuming.c b/src/bin/scripts/vacuuming.c
index 855a5754c98..078a3bbb516 100644
--- a/src/bin/scripts/vacuuming.c
+++ b/src/bin/scripts/vacuuming.c
@@ -264,13 +264,6 @@ vacuum_one_database(ConnParams *cparams,
 				 "--parallel", "13");
 	}
 
-	if (vacopts->buffer_usage_limit && PQserverVersion(conn) < 160000)
-	{
-		PQfinish(conn);
-		pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
-				 "--buffer-usage-limit", "16");
-	}
-
 	if (vacopts->missing_stats_only && PQserverVersion(conn) < 150000)
 	{
 		PQfinish(conn);
@@ -865,13 +858,6 @@ prepare_vacuum_command(PGconn *conn, PQExpBuffer sql,
 				appendPQExpBuffer(sql, "%sVERBOSE", sep);
 				sep = comma;
 			}
-			if (vacopts->buffer_usage_limit)
-			{
-				Assert(serverVersion >= 160000);
-				appendPQExpBuffer(sql, "%sBUFFER_USAGE_LIMIT '%s'", sep,
-								  vacopts->buffer_usage_limit);
-				sep = comma;
-			}
 			if (sep != paren)
 				appendPQExpBufferChar(sql, ')');
 		}
@@ -974,13 +960,6 @@ prepare_vacuum_command(PGconn *conn, PQExpBuffer sql,
 								  vacopts->parallel_workers);
 				sep = comma;
 			}
-			if (vacopts->buffer_usage_limit)
-			{
-				Assert(serverVersion >= 160000);
-				appendPQExpBuffer(sql, "%sBUFFER_USAGE_LIMIT '%s'", sep,
-								  vacopts->buffer_usage_limit);
-				sep = comma;
-			}
 			if (sep != paren)
 				appendPQExpBufferChar(sql, ')');
 		}
diff --git a/src/bin/scripts/vacuuming.h b/src/bin/scripts/vacuuming.h
index 5a491db2526..83a2469a6aa 100644
--- a/src/bin/scripts/vacuuming.h
+++ b/src/bin/scripts/vacuuming.h
@@ -49,7 +49,6 @@ typedef struct vacuumingOptions
 	bool		process_main;
 	bool		process_toast;
 	bool		skip_database_stats;
-	char	   *buffer_usage_limit;
 	bool		missing_stats_only;
 	bool		echo;
 	bool		quiet;
diff --git a/src/include/access/genam.h b/src/include/access/genam.h
index 68bfe405db3..bc87f6f7cc0 100644
--- a/src/include/access/genam.h
+++ b/src/include/access/genam.h
@@ -58,7 +58,6 @@ typedef struct IndexVacuumInfo
 	bool		estimated_count;	/* num_heap_tuples is an estimate */
 	int			message_level;	/* ereport level for progress messages */
 	double		num_heap_tuples;	/* tuples remaining in heap */
-	BufferAccessStrategy strategy;	/* access strategy for reads */
 } IndexVacuumInfo;
 
 /*
diff --git a/src/include/access/hash.h b/src/include/access/hash.h
index a8702f0e5ea..7c9268fe953 100644
--- a/src/include/access/hash.h
+++ b/src/include/access/hash.h
@@ -405,12 +405,11 @@ extern void _hash_pgaddmultitup(Relation rel, Buffer buf, IndexTuple *itups,
 extern Buffer _hash_addovflpage(Relation rel, Buffer metabuf, Buffer buf, bool retain_pin);
 extern BlockNumber _hash_freeovflpage(Relation rel, Buffer bucketbuf, Buffer ovflbuf,
 									  Buffer wbuf, IndexTuple *itups, OffsetNumber *itup_offsets,
-									  Size *tups_size, uint16 nitups, BufferAccessStrategy bstrategy);
+									  Size *tups_size, uint16 nitups);
 extern void _hash_initbitmapbuffer(Buffer buf, uint16 bmsize, bool initpage);
 extern void _hash_squeezebucket(Relation rel,
 								Bucket bucket, BlockNumber bucket_blkno,
-								Buffer bucket_buf,
-								BufferAccessStrategy bstrategy);
+								Buffer bucket_buf);
 extern uint32 _hash_ovflblkno_to_bitno(HashMetaPage metap, BlockNumber ovflblkno);
 
 /* hashpage.c */
@@ -428,9 +427,6 @@ extern void _hash_initbuf(Buffer buf, uint32 max_bucket, uint32 num_bucket,
 						  uint32 flag, bool initpage);
 extern Buffer _hash_getnewbuf(Relation rel, BlockNumber blkno,
 							  ForkNumber forkNum);
-extern Buffer _hash_getbuf_with_strategy(Relation rel, BlockNumber blkno,
-										 int access, int flags,
-										 BufferAccessStrategy bstrategy);
 extern void _hash_relbuf(Relation rel, Buffer buf);
 extern void _hash_dropbuf(Relation rel, Buffer buf);
 extern void _hash_dropscanbuf(Relation rel, HashScanOpaque so);
@@ -481,7 +477,6 @@ extern void _hash_kill_items(IndexScanDesc scan);
 /* hash.c */
 extern void hashbucketcleanup(Relation rel, Bucket cur_bucket,
 							  Buffer bucket_buf, BlockNumber bucket_blkno,
-							  BufferAccessStrategy bstrategy,
 							  uint32 maxbucket, uint32 highmask, uint32 lowmask,
 							  double *tuples_removed, double *num_index_tuples,
 							  bool split_cleanup,
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 5176478c295..0967b659794 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -72,8 +72,6 @@ typedef struct HeapScanDescData
 	Buffer		rs_cbuf;		/* current buffer in scan, if any */
 	/* NB: if rs_cbuf is not InvalidBuffer, we hold a pin on that buffer */
 
-	BufferAccessStrategy rs_strategy;	/* access strategy for reads */
-
 	HeapTupleData rs_ctup;		/* current tuple in scan, if any */
 
 	/* For scans that stream reads */
@@ -466,7 +464,7 @@ extern void log_heap_prune_and_freeze(Relation relation, Buffer buffer,
 
 /* in heap/vacuumlazy.c */
 extern void heap_vacuum_rel(Relation rel,
-							const VacuumParams *params, BufferAccessStrategy bstrategy);
+							const VacuumParams *params);
 #ifdef USE_ASSERT_CHECKING
 extern bool heap_page_is_all_visible(Relation rel, Buffer buf,
 									 GlobalVisState *vistest,
diff --git a/src/include/access/hio.h b/src/include/access/hio.h
index 60cfc375fd5..63bfcc884ee 100644
--- a/src/include/access/hio.h
+++ b/src/include/access/hio.h
@@ -28,7 +28,6 @@
  */
 typedef struct BulkInsertStateData
 {
-	BufferAccessStrategy strategy;	/* our BULKWRITE strategy object */
 	Buffer		current_buf;	/* current insertion target page */
 
 	/*
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index f2c36696bca..83ba0f3ca73 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -689,8 +689,7 @@ typedef struct TableAmRoutine
 	 * integrate with autovacuum's scheduling.
 	 */
 	void		(*relation_vacuum) (Relation rel,
-									const VacuumParams *params,
-									BufferAccessStrategy bstrategy);
+									const VacuumParams *params);
 
 	/*
 	 * Prepare to analyze block `blockno` of `scan`. The scan has been started
@@ -1774,10 +1773,9 @@ table_relation_copy_for_cluster(Relation OldTable, Relation NewTable,
  * routine, even if (for ANALYZE) it is part of the same VACUUM command.
  */
 static inline void
-table_relation_vacuum(Relation rel, const VacuumParams *params,
-					  BufferAccessStrategy bstrategy)
+table_relation_vacuum(Relation rel, const VacuumParams *params)
 {
-	rel->rd_tableam->relation_vacuum(rel, params, bstrategy);
+	rel->rd_tableam->relation_vacuum(rel, params);
 }
 
 /*
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 956d9cea36d..14a9b530bcf 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -363,7 +363,7 @@ extern PGDLLIMPORT int64 parallel_vacuum_worker_delay_ns;
 /* in commands/vacuum.c */
 extern void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel);
 extern void vacuum(List *relations, const VacuumParams *params,
-				   BufferAccessStrategy bstrategy, MemoryContext vac_context,
+				   MemoryContext vac_context,
 				   bool isTopLevel);
 extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
 							 int *nindexes, Relation **Irel);
@@ -407,8 +407,7 @@ extern void VacuumUpdateCosts(void);
 /* in commands/vacuumparallel.c */
 extern ParallelVacuumState *parallel_vacuum_init(Relation rel, Relation *indrels,
 												 int nindexes, int nrequested_workers,
-												 int vac_work_mem, int elevel,
-												 BufferAccessStrategy bstrategy);
+												 int vac_work_mem, int elevel);
 extern void parallel_vacuum_end(ParallelVacuumState *pvs, IndexBulkDeleteResult **istats);
 extern TidStore *parallel_vacuum_get_dead_items(ParallelVacuumState *pvs,
 												VacDeadItemsInfo **dead_items_info_p);
@@ -428,8 +427,7 @@ extern void parallel_vacuum_main(dsm_segment *seg, shm_toc *toc);
 
 /* in commands/analyze.c */
 extern void analyze_rel(Oid relid, RangeVar *relation,
-						const VacuumParams *params, List *va_cols, bool in_outer_xact,
-						BufferAccessStrategy bstrategy);
+						const VacuumParams *params, List *va_cols, bool in_outer_xact);
 extern bool attribute_is_analyzable(Relation onerel, int attnum, Form_pg_attribute attr,
 									int *p_attstattarget);
 extern bool std_typanalyze(VacAttrStats *stats);
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 7170a4bff98..c5d582b152b 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -273,15 +273,6 @@ extern PGDLLIMPORT double hash_mem_multiplier;
 extern PGDLLIMPORT int maintenance_work_mem;
 extern PGDLLIMPORT int max_parallel_maintenance_workers;
 
-/*
- * Upper and lower hard limits for the buffer access strategy ring size
- * specified by the VacuumBufferUsageLimit GUC and BUFFER_USAGE_LIMIT option
- * to VACUUM and ANALYZE.
- */
-#define MIN_BAS_VAC_RING_SIZE_KB 128
-#define MAX_BAS_VAC_RING_SIZE_KB (16 * 1024 * 1024)
-
-extern PGDLLIMPORT int VacuumBufferUsageLimit;
 extern PGDLLIMPORT int VacuumCostPageHit;
 extern PGDLLIMPORT int VacuumCostPageMiss;
 extern PGDLLIMPORT int VacuumCostPageDirty;
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 58a44857f13..68b4e5093a2 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -287,14 +287,11 @@ typedef enum IOObject
 
 typedef enum IOContext
 {
-	IOCONTEXT_BULKREAD,
-	IOCONTEXT_BULKWRITE,
 	IOCONTEXT_INIT,
 	IOCONTEXT_NORMAL,
-	IOCONTEXT_VACUUM,
 } IOContext;
 
-#define IOCONTEXT_NUM_TYPES (IOCONTEXT_VACUUM + 1)
+#define IOCONTEXT_NUM_TYPES (IOCONTEXT_NORMAL + 1)
 
 /*
  * Enumeration of IO operations.
diff --git a/src/include/storage/buf.h b/src/include/storage/buf.h
index b21445522b1..fa942a64f05 100644
--- a/src/include/storage/buf.h
+++ b/src/include/storage/buf.h
@@ -36,11 +36,4 @@ typedef int Buffer;
  */
 #define BufferIsLocal(buffer)	((buffer) < 0)
 
-/*
- * Buffer access strategy objects.
- *
- * BufferAccessStrategyData is private to freelist.c
- */
-typedef struct BufferAccessStrategyData *BufferAccessStrategy;
-
 #endif							/* BUF_H */
diff --git a/src/include/storage/buf_internals.h b/src/include/storage/buf_internals.h
index 89b11051577..de5139323c0 100644
--- a/src/include/storage/buf_internals.h
+++ b/src/include/storage/buf_internals.h
@@ -627,11 +627,7 @@ extern void TerminateBufferIO(BufferDesc *buf, bool clear_dirty, uint64 set_flag
 
 
 /* freelist.c */
-extern IOContext IOContextForStrategy(BufferAccessStrategy strategy);
-extern BufferDesc *StrategyGetBuffer(BufferAccessStrategy strategy,
-									 uint64 *buf_state, bool *from_ring);
-extern bool StrategyRejectBuffer(BufferAccessStrategy strategy,
-								 BufferDesc *buf, bool from_ring);
+extern BufferDesc *StrategyGetBuffer(uint64 *buf_state);
 
 extern int	StrategySyncStart(uint32 *complete_passes, uint32 *num_buf_alloc);
 extern void StrategyNotifyBgWriter(int bgwprocno);
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 6837b35fc6d..36023792f36 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -25,21 +25,6 @@
 
 typedef void *Block;
 
-/*
- * Possible arguments for GetAccessStrategy().
- *
- * If adding a new BufferAccessStrategyType, also add a new IOContext so
- * IO statistics using this strategy are tracked.
- */
-typedef enum BufferAccessStrategyType
-{
-	BAS_NORMAL,					/* Normal random access */
-	BAS_BULKREAD,				/* Large read-only scan (hint bit updates are
-								 * ok) */
-	BAS_BULKWRITE,				/* Large multi-block write (e.g. COPY IN) */
-	BAS_VACUUM,					/* VACUUM */
-} BufferAccessStrategyType;
-
 /* Possible modes for ReadBufferExtended() */
 typedef enum
 {
@@ -135,7 +120,6 @@ struct ReadBuffersOperation
 	SMgrRelation smgr;
 	char		persistence;
 	ForkNumber	forknum;
-	BufferAccessStrategy strategy;
 
 	/*
 	 * The following private members are private state for communication
@@ -235,11 +219,10 @@ extern bool ReadRecentBuffer(RelFileLocator rlocator, ForkNumber forkNum,
 							 BlockNumber blockNum, Buffer recent_buffer);
 extern Buffer ReadBuffer(Relation reln, BlockNumber blockNum);
 extern Buffer ReadBufferExtended(Relation reln, ForkNumber forkNum,
-								 BlockNumber blockNum, ReadBufferMode mode,
-								 BufferAccessStrategy strategy);
+								 BlockNumber blockNum, ReadBufferMode mode);
 extern Buffer ReadBufferWithoutRelcache(RelFileLocator rlocator,
 										ForkNumber forkNum, BlockNumber blockNum,
-										ReadBufferMode mode, BufferAccessStrategy strategy,
+										ReadBufferMode mode,
 										bool permanent);
 
 extern bool StartReadBuffer(ReadBuffersOperation *operation,
@@ -266,18 +249,15 @@ extern Buffer ReleaseAndReadBuffer(Buffer buffer, Relation relation,
 
 extern Buffer ExtendBufferedRel(BufferManagerRelation bmr,
 								ForkNumber forkNum,
-								BufferAccessStrategy strategy,
 								uint32 flags);
 extern BlockNumber ExtendBufferedRelBy(BufferManagerRelation bmr,
 									   ForkNumber fork,
-									   BufferAccessStrategy strategy,
 									   uint32 flags,
 									   uint32 extend_by,
 									   Buffer *buffers,
 									   uint32 *extended_by);
 extern Buffer ExtendBufferedRelTo(BufferManagerRelation bmr,
 								  ForkNumber fork,
-								  BufferAccessStrategy strategy,
 								  uint32 flags,
 								  BlockNumber extend_to,
 								  ReadBufferMode mode);
@@ -376,14 +356,6 @@ extern void AtProcExit_LocalBuffers(void);
 
 /* in freelist.c */
 
-extern BufferAccessStrategy GetAccessStrategy(BufferAccessStrategyType btype);
-extern BufferAccessStrategy GetAccessStrategyWithSize(BufferAccessStrategyType btype,
-													  int ring_size_kb);
-extern int	GetAccessStrategyBufferCount(BufferAccessStrategy strategy);
-extern int	GetAccessStrategyPinLimit(BufferAccessStrategy strategy);
-
-extern void FreeAccessStrategy(BufferAccessStrategy strategy);
-
 
 /* inline functions */
 
diff --git a/src/include/storage/read_stream.h b/src/include/storage/read_stream.h
index 48995c6d534..9e5fbbed9e8 100644
--- a/src/include/storage/read_stream.h
+++ b/src/include/storage/read_stream.h
@@ -83,17 +83,14 @@ extern BlockNumber block_range_read_stream_cb(ReadStream *stream,
 											  void *callback_private_data,
 											  void *per_buffer_data);
 extern ReadStream *read_stream_begin_relation(int flags,
-											  BufferAccessStrategy strategy,
 											  Relation rel,
 											  ForkNumber forknum,
 											  ReadStreamBlockNumberCB callback,
 											  void *callback_private_data,
 											  size_t per_buffer_data_size);
 extern Buffer read_stream_next_buffer(ReadStream *stream, void **per_buffer_data);
-extern BlockNumber read_stream_next_block(ReadStream *stream,
-										  BufferAccessStrategy *strategy);
+extern BlockNumber read_stream_next_block(ReadStream *stream);
 extern ReadStream *read_stream_begin_smgr_relation(int flags,
-												   BufferAccessStrategy strategy,
 												   SMgrRelation smgr,
 												   char smgr_persistence,
 												   ForkNumber forknum,
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 307f4fbaefe..3f28558d47c 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -31,8 +31,6 @@ extern void assign_application_name(const char *newval, void *extra);
 extern const char *show_archive_command(void);
 extern bool check_autovacuum_work_mem(int *newval, void **extra,
 									  GucSource source);
-extern bool check_vacuum_buffer_usage_limit(int *newval, void **extra,
-											GucSource source);
 extern bool check_backtrace_functions(char **newval, void **extra,
 									  GucSource source);
 extern void assign_backtrace_functions(const char *newval, void *extra);
diff --git a/src/test/modules/test_aio/test_aio.c b/src/test/modules/test_aio/test_aio.c
index 35efba1a5e3..f60153f801f 100644
--- a/src/test/modules/test_aio/test_aio.c
+++ b/src/test/modules/test_aio/test_aio.c
@@ -194,7 +194,6 @@ grow_rel(PG_FUNCTION_ARGS)
 
 		ExtendBufferedRelBy(BMR_REL(rel),
 							MAIN_FORKNUM,
-							NULL,
 							0,
 							extend_by_pages,
 							victim_buffers,
@@ -231,7 +230,7 @@ modify_rel_block(PG_FUNCTION_ARGS)
 	rel = relation_open(relid, AccessExclusiveLock);
 
 	buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno,
-							 RBM_ZERO_ON_ERROR, NULL);
+							 RBM_ZERO_ON_ERROR);
 
 	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
 
@@ -331,7 +330,7 @@ create_toy_buffer(Relation rel, BlockNumber blkno)
 	uint64		unset_bits = 0;
 
 	/* place buffer in shared buffers without erroring out */
-	buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_ZERO_AND_LOCK, NULL);
+	buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_ZERO_AND_LOCK);
 	LockBuffer(buf, BUFFER_LOCK_UNLOCK);
 
 	if (RelationUsesLocalBuffers(rel))
@@ -737,7 +736,6 @@ read_buffers(PG_FUNCTION_ARGS)
 		operation->rel = rel;
 		operation->smgr = smgr;
 		operation->persistence = rel->rd_rel->relpersistence;
-		operation->strategy = NULL;
 		operation->forknum = MAIN_FORKNUM;
 
 		io_reqds[nios] = StartReadBuffers(operation,
@@ -879,7 +877,6 @@ read_stream_for_blocks(PG_FUNCTION_ARGS)
 	rel = relation_open(relid, AccessShareLock);
 
 	stream = read_stream_begin_relation(READ_STREAM_FULL,
-										NULL,
 										rel,
 										MAIN_FORKNUM,
 										read_stream_for_blocks_cb,
diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out
index 03cbc1cdef5..e4b9de0b4a8 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -16,22 +16,16 @@ SHOW track_counts;  -- must be on
 SELECT backend_type, object, context FROM pg_stat_io
   ORDER BY backend_type COLLATE "C", object COLLATE "C", context COLLATE "C";
 backend_type|object|context
-autovacuum launcher|relation|bulkread
 autovacuum launcher|relation|init
 autovacuum launcher|relation|normal
 autovacuum launcher|wal|init
 autovacuum launcher|wal|normal
-autovacuum worker|relation|bulkread
 autovacuum worker|relation|init
 autovacuum worker|relation|normal
-autovacuum worker|relation|vacuum
 autovacuum worker|wal|init
 autovacuum worker|wal|normal
-background worker|relation|bulkread
-background worker|relation|bulkwrite
 background worker|relation|init
 background worker|relation|normal
-background worker|relation|vacuum
 background worker|temp relation|normal
 background worker|wal|init
 background worker|wal|normal
@@ -43,67 +37,43 @@ checkpointer|relation|init
 checkpointer|relation|normal
 checkpointer|wal|init
 checkpointer|wal|normal
-client backend|relation|bulkread
-client backend|relation|bulkwrite
 client backend|relation|init
 client backend|relation|normal
-client backend|relation|vacuum
 client backend|temp relation|normal
 client backend|wal|init
 client backend|wal|normal
-datachecksums launcher|relation|bulkread
-datachecksums launcher|relation|bulkwrite
 datachecksums launcher|relation|init
 datachecksums launcher|relation|normal
-datachecksums launcher|relation|vacuum
 datachecksums launcher|temp relation|normal
 datachecksums launcher|wal|init
 datachecksums launcher|wal|normal
-datachecksums worker|relation|bulkread
-datachecksums worker|relation|bulkwrite
 datachecksums worker|relation|init
 datachecksums worker|relation|normal
-datachecksums worker|relation|vacuum
 datachecksums worker|temp relation|normal
 datachecksums worker|wal|init
 datachecksums worker|wal|normal
-io worker|relation|bulkread
-io worker|relation|bulkwrite
 io worker|relation|init
 io worker|relation|normal
-io worker|relation|vacuum
 io worker|temp relation|normal
 io worker|wal|init
 io worker|wal|normal
-slotsync worker|relation|bulkread
-slotsync worker|relation|bulkwrite
 slotsync worker|relation|init
 slotsync worker|relation|normal
-slotsync worker|relation|vacuum
 slotsync worker|temp relation|normal
 slotsync worker|wal|init
 slotsync worker|wal|normal
-standalone backend|relation|bulkread
-standalone backend|relation|bulkwrite
 standalone backend|relation|init
 standalone backend|relation|normal
-standalone backend|relation|vacuum
 standalone backend|wal|init
 standalone backend|wal|normal
-startup|relation|bulkread
-startup|relation|bulkwrite
 startup|relation|init
 startup|relation|normal
-startup|relation|vacuum
 startup|wal|init
 startup|wal|normal
 walreceiver|wal|init
 walreceiver|wal|normal
-walsender|relation|bulkread
-walsender|relation|bulkwrite
 walsender|relation|init
 walsender|relation|normal
-walsender|relation|vacuum
 walsender|temp relation|normal
 walsender|wal|init
 walsender|wal|normal
@@ -111,7 +81,7 @@ walsummarizer|wal|init
 walsummarizer|wal|normal
 walwriter|wal|init
 walwriter|wal|normal
-(95 rows)
+(65 rows)
 \a
 -- List of registered statistics kinds.
 SELECT id, name, fixed_amount,
@@ -1757,70 +1727,6 @@ SELECT :io_sum_local_new_tblspc_writes > :io_sum_local_after_writes;
 (1 row)
 
 RESET temp_buffers;
--- Test that reuse of strategy buffers and reads of blocks into these reused
--- buffers while VACUUMing are tracked in pg_stat_io. If there is sufficient
--- demand for shared buffers from concurrent queries, some buffers may be
--- pinned by other backends before they can be reused. In such cases, the
--- backend will evict a buffer from outside the ring and add it to the
--- ring. This is considered an eviction and not a reuse.
--- Set wal_skip_threshold smaller than the expected size of
--- test_io_vac_strategy so that, even if wal_level is minimal, VACUUM FULL will
--- fsync the newly rewritten test_io_vac_strategy instead of writing it to WAL.
--- Writing it to WAL will result in the newly written relation pages being in
--- shared buffers -- preventing us from testing BAS_VACUUM BufferAccessStrategy
--- reads.
-SET wal_skip_threshold = '1 kB';
-SELECT sum(reuses) AS reuses, sum(reads) AS reads, sum(evictions) AS evictions
-  FROM pg_stat_io WHERE context = 'vacuum' \gset io_sum_vac_strategy_before_
-CREATE TABLE test_io_vac_strategy(a int, b int) WITH (autovacuum_enabled = 'false');
-INSERT INTO test_io_vac_strategy SELECT i, i from generate_series(1, 4500)i;
--- Ensure that the next VACUUM will need to perform IO by rewriting the table
--- first with VACUUM (FULL).
-VACUUM (FULL) test_io_vac_strategy;
--- Use the minimum BUFFER_USAGE_LIMIT to cause reuses or evictions with the
--- smallest table possible.
-VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128) test_io_vac_strategy;
-SELECT pg_stat_force_next_flush();
- pg_stat_force_next_flush 
---------------------------
- 
-(1 row)
-
-SELECT sum(reuses) AS reuses, sum(reads) AS reads, sum(evictions) AS evictions
-  FROM pg_stat_io WHERE context = 'vacuum' \gset io_sum_vac_strategy_after_
-SELECT :io_sum_vac_strategy_after_reads > :io_sum_vac_strategy_before_reads;
- ?column? 
-----------
- t
-(1 row)
-
-SELECT (:io_sum_vac_strategy_after_reuses + :io_sum_vac_strategy_after_evictions) >
-  (:io_sum_vac_strategy_before_reuses + :io_sum_vac_strategy_before_evictions);
- ?column? 
-----------
- t
-(1 row)
-
-RESET wal_skip_threshold;
--- Test that extends done by a CTAS, which uses a BAS_BULKWRITE
--- BufferAccessStrategy, are tracked in pg_stat_io.
-SELECT sum(extends) AS io_sum_bulkwrite_strategy_extends_before
-  FROM pg_stat_io WHERE context = 'bulkwrite' \gset
-CREATE TABLE test_io_bulkwrite_strategy AS SELECT i FROM generate_series(1,100)i;
-SELECT pg_stat_force_next_flush();
- pg_stat_force_next_flush 
---------------------------
- 
-(1 row)
-
-SELECT sum(extends) AS io_sum_bulkwrite_strategy_extends_after
-  FROM pg_stat_io WHERE context = 'bulkwrite' \gset
-SELECT :io_sum_bulkwrite_strategy_extends_after > :io_sum_bulkwrite_strategy_extends_before;
- ?column? 
-----------
- t
-(1 row)
-
 -- Test IO stats reset
 SELECT pg_stat_have_stats('io', 0, 0);
  pg_stat_have_stats 
@@ -1841,20 +1747,16 @@ SELECT pg_stat_reset_shared('io');
 SELECT sum(evictions) + sum(reuses) + sum(extends) + sum(fsyncs) + sum(reads) + sum(writes) + sum(writebacks) + sum(hits) AS io_stats_post_reset
   FROM pg_stat_io \gset
 SELECT :io_stats_post_reset < :io_stats_pre_reset;
- ?column? 
-----------
- t
-(1 row)
-
+ERROR:  syntax error at or near ":"
+LINE 1: SELECT :io_stats_post_reset < :io_stats_pre_reset;
+               ^
 SELECT sum(evictions) + sum(reuses) + sum(extends) + sum(fsyncs) + sum(reads) + sum(writes) + sum(writebacks) + sum(hits) AS my_io_stats_post_reset
   FROM pg_stat_get_backend_io(pg_backend_pid()) \gset
 -- pg_stat_reset_shared() did not reset backend IO stats
 SELECT :my_io_stats_pre_reset <= :my_io_stats_post_reset;
- ?column? 
-----------
- t
-(1 row)
-
+ERROR:  syntax error at or near ":"
+LINE 1: SELECT :my_io_stats_pre_reset <= :my_io_stats_post_reset;
+               ^
 -- but pg_stat_reset_backend_stats() does
 SELECT pg_stat_reset_backend_stats(pg_backend_pid());
  pg_stat_reset_backend_stats 
@@ -1865,11 +1767,9 @@ SELECT pg_stat_reset_backend_stats(pg_backend_pid());
 SELECT sum(evictions) + sum(reuses) + sum(extends) + sum(fsyncs) + sum(reads) + sum(writes) + sum(writebacks) + sum(hits) AS my_io_stats_post_backend_reset
   FROM pg_stat_get_backend_io(pg_backend_pid()) \gset
 SELECT :my_io_stats_pre_reset > :my_io_stats_post_backend_reset;
- ?column? 
-----------
- t
-(1 row)
-
+ERROR:  syntax error at or near ":"
+LINE 1: SELECT :my_io_stats_pre_reset > :my_io_stats_post_backend_re...
+               ^
 -- Check invalid input for pg_stat_get_backend_io()
 SELECT pg_stat_get_backend_io(NULL);
  pg_stat_get_backend_io 
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index d4696bc3325..85d06907cec 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -526,25 +526,6 @@ SELECT t.relfilenode = :toast_filenode AS is_same_toast_filenode
  f
 (1 row)
 
--- BUFFER_USAGE_LIMIT option
-VACUUM (BUFFER_USAGE_LIMIT '512 kB') vac_option_tab;
-ANALYZE (BUFFER_USAGE_LIMIT '512 kB') vac_option_tab;
--- try disabling the buffer usage limit
-VACUUM (BUFFER_USAGE_LIMIT 0) vac_option_tab;
-ANALYZE (BUFFER_USAGE_LIMIT 0) vac_option_tab;
--- value exceeds max size error
-VACUUM (BUFFER_USAGE_LIMIT 16777220) vac_option_tab;
-ERROR:  BUFFER_USAGE_LIMIT option must be 0 or between 128 kB and 16777216 kB
--- value is less than min size error
-VACUUM (BUFFER_USAGE_LIMIT 120) vac_option_tab;
-ERROR:  BUFFER_USAGE_LIMIT option must be 0 or between 128 kB and 16777216 kB
--- integer overflow error
-VACUUM (BUFFER_USAGE_LIMIT 10000000000) vac_option_tab;
-ERROR:  BUFFER_USAGE_LIMIT option must be 0 or between 128 kB and 16777216 kB
-HINT:  Value exceeds integer range.
--- incompatible with VACUUM FULL error
-VACUUM (BUFFER_USAGE_LIMIT '512 kB', FULL) vac_option_tab;
-ERROR:  BUFFER_USAGE_LIMIT cannot be specified for VACUUM FULL
 -- SKIP_DATABASE_STATS option
 VACUUM (SKIP_DATABASE_STATS) vactst;
 -- ONLY_DATABASE_STATS option
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 4c265d1245c..7a049d88a77 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -814,48 +814,6 @@ SELECT sum(writes) AS io_sum_local_new_tblspc_writes
 SELECT :io_sum_local_new_tblspc_writes > :io_sum_local_after_writes;
 RESET temp_buffers;
 
--- Test that reuse of strategy buffers and reads of blocks into these reused
--- buffers while VACUUMing are tracked in pg_stat_io. If there is sufficient
--- demand for shared buffers from concurrent queries, some buffers may be
--- pinned by other backends before they can be reused. In such cases, the
--- backend will evict a buffer from outside the ring and add it to the
--- ring. This is considered an eviction and not a reuse.
-
--- Set wal_skip_threshold smaller than the expected size of
--- test_io_vac_strategy so that, even if wal_level is minimal, VACUUM FULL will
--- fsync the newly rewritten test_io_vac_strategy instead of writing it to WAL.
--- Writing it to WAL will result in the newly written relation pages being in
--- shared buffers -- preventing us from testing BAS_VACUUM BufferAccessStrategy
--- reads.
-SET wal_skip_threshold = '1 kB';
-SELECT sum(reuses) AS reuses, sum(reads) AS reads, sum(evictions) AS evictions
-  FROM pg_stat_io WHERE context = 'vacuum' \gset io_sum_vac_strategy_before_
-CREATE TABLE test_io_vac_strategy(a int, b int) WITH (autovacuum_enabled = 'false');
-INSERT INTO test_io_vac_strategy SELECT i, i from generate_series(1, 4500)i;
--- Ensure that the next VACUUM will need to perform IO by rewriting the table
--- first with VACUUM (FULL).
-VACUUM (FULL) test_io_vac_strategy;
--- Use the minimum BUFFER_USAGE_LIMIT to cause reuses or evictions with the
--- smallest table possible.
-VACUUM (PARALLEL 0, BUFFER_USAGE_LIMIT 128) test_io_vac_strategy;
-SELECT pg_stat_force_next_flush();
-SELECT sum(reuses) AS reuses, sum(reads) AS reads, sum(evictions) AS evictions
-  FROM pg_stat_io WHERE context = 'vacuum' \gset io_sum_vac_strategy_after_
-SELECT :io_sum_vac_strategy_after_reads > :io_sum_vac_strategy_before_reads;
-SELECT (:io_sum_vac_strategy_after_reuses + :io_sum_vac_strategy_after_evictions) >
-  (:io_sum_vac_strategy_before_reuses + :io_sum_vac_strategy_before_evictions);
-RESET wal_skip_threshold;
-
--- Test that extends done by a CTAS, which uses a BAS_BULKWRITE
--- BufferAccessStrategy, are tracked in pg_stat_io.
-SELECT sum(extends) AS io_sum_bulkwrite_strategy_extends_before
-  FROM pg_stat_io WHERE context = 'bulkwrite' \gset
-CREATE TABLE test_io_bulkwrite_strategy AS SELECT i FROM generate_series(1,100)i;
-SELECT pg_stat_force_next_flush();
-SELECT sum(extends) AS io_sum_bulkwrite_strategy_extends_after
-  FROM pg_stat_io WHERE context = 'bulkwrite' \gset
-SELECT :io_sum_bulkwrite_strategy_extends_after > :io_sum_bulkwrite_strategy_extends_before;
-
 -- Test IO stats reset
 SELECT pg_stat_have_stats('io', 0, 0);
 SELECT sum(evictions) + sum(reuses) + sum(extends) + sum(fsyncs) + sum(reads) + sum(writes) + sum(writebacks) + sum(hits) AS io_stats_pre_reset
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index 247b8e23b23..506c4389765 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -390,21 +390,6 @@ SELECT t.relfilenode = :toast_filenode AS is_same_toast_filenode
   FROM pg_class c, pg_class t
   WHERE c.reltoastrelid = t.oid AND c.relname = 'vac_option_tab';
 
--- BUFFER_USAGE_LIMIT option
-VACUUM (BUFFER_USAGE_LIMIT '512 kB') vac_option_tab;
-ANALYZE (BUFFER_USAGE_LIMIT '512 kB') vac_option_tab;
--- try disabling the buffer usage limit
-VACUUM (BUFFER_USAGE_LIMIT 0) vac_option_tab;
-ANALYZE (BUFFER_USAGE_LIMIT 0) vac_option_tab;
--- value exceeds max size error
-VACUUM (BUFFER_USAGE_LIMIT 16777220) vac_option_tab;
--- value is less than min size error
-VACUUM (BUFFER_USAGE_LIMIT 120) vac_option_tab;
--- integer overflow error
-VACUUM (BUFFER_USAGE_LIMIT 10000000000) vac_option_tab;
--- incompatible with VACUUM FULL error
-VACUUM (BUFFER_USAGE_LIMIT '512 kB', FULL) vac_option_tab;
-
 -- SKIP_DATABASE_STATS option
 VACUUM (SKIP_DATABASE_STATS) vactst;
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index f6e87d04b0e..a08f9c3ce29 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -355,8 +355,6 @@ BtreeLevel
 Bucket
 BufFile
 Buffer
-BufferAccessStrategy
-BufferAccessStrategyType
 BufferCacheOsPagesContext
 BufferCacheOsPagesRec
 BufferDesc
-- 
2.51.2



^ permalink  raw  reply  [nested|flat] 4+ messages in thread


end of thread, other threads:[~2026-07-10 14:00 UTC | newest]

Thread overview: 4+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2023-06-30 21:33 Re: pg_upgrade instructions involving "rsync --size-only" might lead to standby corruption? Bruce Momjian <[email protected]>
2023-06-30 22:18 ` Nikolay Samokhvalov <[email protected]>
2023-07-07 13:31   ` Stephen Frost <[email protected]>
2026-07-10 14:00 Re: [PATCH] Batched clock sweep to reduce cross-socket atomic contention Greg Burd <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox