Skip to content

Commit 7db0cd2

Browse files
committed
Set PD_ALL_VISIBLE and visibility map bits in COPY FREEZE
Make sure COPY FREEZE marks the pages as PD_ALL_VISIBLE and updates the visibility map. Until now we only marked individual tuples as frozen, but page-level flags were not updated, so the first VACUUM after the COPY FREEZE had to rewrite the whole table. This is a fairly old patch, and multiple people worked on it. The first version was written by Jeff Janes, and then reworked by Pavan Deolasee and Anastasia Lubennikova. Author: Anastasia Lubennikova, Pavan Deolasee, Jeff Janes Reviewed-by: Kuntal Ghosh, Jeff Janes, Tomas Vondra, Masahiko Sawada, Andres Freund, Ibrar Ahmed, Robert Haas, Tatsuro Ishii, Darafei Praliaskouski Discussion: https://postgr.es/m/CABOikdN-ptGv0mZntrK2Q8OtfUuAjqaYMGmkdU1dCKFtUxVLrg@mail.gmail.com Discussion: https://postgr.es/m/CAMkU%3D1w3osJJ2FneELhhNRLxfZitDgp9FPHee08NT2FQFmz_pQ%40mail.gmail.com
1 parent 0c7d3bb commit 7db0cd2

File tree

5 files changed

+230
-8
lines changed

5 files changed

+230
-8
lines changed

contrib/pg_visibility/expected/pg_visibility.out

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,69 @@ select pg_truncate_visibility_map('test_partition');
179179

180180
(1 row)
181181

182+
-- test copy freeze
183+
create table copyfreeze (a int, b char(1500));
184+
-- load all rows via COPY FREEZE and ensure that all pages are set all-visible
185+
-- and all-frozen.
186+
begin;
187+
truncate copyfreeze;
188+
copy copyfreeze from stdin freeze;
189+
commit;
190+
select * from pg_visibility_map('copyfreeze');
191+
blkno | all_visible | all_frozen
192+
-------+-------------+------------
193+
0 | t | t
194+
1 | t | t
195+
2 | t | t
196+
(3 rows)
197+
198+
select * from pg_check_frozen('copyfreeze');
199+
t_ctid
200+
--------
201+
(0 rows)
202+
203+
-- load half the rows via regular COPY and rest via COPY FREEZE. The pages
204+
-- which are touched by regular COPY must not be set all-visible/all-frozen. On
205+
-- the other hand, pages allocated by COPY FREEZE should be marked
206+
-- all-frozen/all-visible.
207+
begin;
208+
truncate copyfreeze;
209+
copy copyfreeze from stdin;
210+
copy copyfreeze from stdin freeze;
211+
commit;
212+
select * from pg_visibility_map('copyfreeze');
213+
blkno | all_visible | all_frozen
214+
-------+-------------+------------
215+
0 | f | f
216+
1 | f | f
217+
2 | t | t
218+
(3 rows)
219+
220+
select * from pg_check_frozen('copyfreeze');
221+
t_ctid
222+
--------
223+
(0 rows)
224+
225+
-- Try a mix of regular COPY and COPY FREEZE.
226+
begin;
227+
truncate copyfreeze;
228+
copy copyfreeze from stdin freeze;
229+
copy copyfreeze from stdin;
230+
copy copyfreeze from stdin freeze;
231+
commit;
232+
select * from pg_visibility_map('copyfreeze');
233+
blkno | all_visible | all_frozen
234+
-------+-------------+------------
235+
0 | t | t
236+
1 | f | f
237+
2 | t | t
238+
(3 rows)
239+
240+
select * from pg_check_frozen('copyfreeze');
241+
t_ctid
242+
--------
243+
(0 rows)
244+
182245
-- cleanup
183246
drop table test_partitioned;
184247
drop view test_view;
@@ -188,3 +251,4 @@ drop server dummy_server;
188251
drop foreign data wrapper dummy;
189252
drop materialized view matview_visibility_test;
190253
drop table regular_table;
254+
drop table copyfreeze;

contrib/pg_visibility/sql/pg_visibility.sql

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,82 @@ select count(*) > 0 from pg_visibility_map_summary('test_partition');
9494
select * from pg_check_frozen('test_partition'); -- hopefully none
9595
select pg_truncate_visibility_map('test_partition');
9696

97+
-- test copy freeze
98+
create table copyfreeze (a int, b char(1500));
99+
100+
-- load all rows via COPY FREEZE and ensure that all pages are set all-visible
101+
-- and all-frozen.
102+
begin;
103+
truncate copyfreeze;
104+
copy copyfreeze from stdin freeze;
105+
1 '1'
106+
2 '2'
107+
3 '3'
108+
4 '4'
109+
5 '5'
110+
6 '6'
111+
7 '7'
112+
8 '8'
113+
9 '9'
114+
10 '10'
115+
11 '11'
116+
12 '12'
117+
\.
118+
commit;
119+
select * from pg_visibility_map('copyfreeze');
120+
select * from pg_check_frozen('copyfreeze');
121+
122+
-- load half the rows via regular COPY and rest via COPY FREEZE. The pages
123+
-- which are touched by regular COPY must not be set all-visible/all-frozen. On
124+
-- the other hand, pages allocated by COPY FREEZE should be marked
125+
-- all-frozen/all-visible.
126+
begin;
127+
truncate copyfreeze;
128+
copy copyfreeze from stdin;
129+
1 '1'
130+
2 '2'
131+
3 '3'
132+
4 '4'
133+
5 '5'
134+
6 '6'
135+
\.
136+
copy copyfreeze from stdin freeze;
137+
7 '7'
138+
8 '8'
139+
9 '9'
140+
10 '10'
141+
11 '11'
142+
12 '12'
143+
\.
144+
commit;
145+
select * from pg_visibility_map('copyfreeze');
146+
select * from pg_check_frozen('copyfreeze');
147+
148+
-- Try a mix of regular COPY and COPY FREEZE.
149+
begin;
150+
truncate copyfreeze;
151+
copy copyfreeze from stdin freeze;
152+
1 '1'
153+
2 '2'
154+
3 '3'
155+
4 '4'
156+
5 '5'
157+
\.
158+
copy copyfreeze from stdin;
159+
6 '6'
160+
\.
161+
copy copyfreeze from stdin freeze;
162+
7 '7'
163+
8 '8'
164+
9 '9'
165+
10 '10'
166+
11 '11'
167+
12 '12'
168+
\.
169+
commit;
170+
select * from pg_visibility_map('copyfreeze');
171+
select * from pg_check_frozen('copyfreeze');
172+
97173
-- cleanup
98174
drop table test_partitioned;
99175
drop view test_view;
@@ -103,3 +179,4 @@ drop server dummy_server;
103179
drop foreign data wrapper dummy;
104180
drop materialized view matview_visibility_test;
105181
drop table regular_table;
182+
drop table copyfreeze;

src/backend/access/heap/heapam.c

Lines changed: 69 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2121,6 +2121,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
21212121
int ndone;
21222122
PGAlignedBlock scratch;
21232123
Page page;
2124+
Buffer vmbuffer = InvalidBuffer;
21242125
bool needwal;
21252126
Size saveFreeSpace;
21262127
bool need_tuple_data = RelationIsLogicallyLogged(relation);
@@ -2175,21 +2176,30 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
21752176
while (ndone < ntuples)
21762177
{
21772178
Buffer buffer;
2178-
Buffer vmbuffer = InvalidBuffer;
2179+
bool starting_with_empty_page;
21792180
bool all_visible_cleared = false;
2181+
bool all_frozen_set = false;
21802182
int nthispage;
21812183

21822184
CHECK_FOR_INTERRUPTS();
21832185

21842186
/*
21852187
* Find buffer where at least the next tuple will fit. If the page is
21862188
* all-visible, this will also pin the requisite visibility map page.
2189+
*
2190+
* Also pin visibility map page if COPY FREEZE inserts tuples into an
2191+
* empty page. See all_frozen_set below.
21872192
*/
21882193
buffer = RelationGetBufferForTuple(relation, heaptuples[ndone]->t_len,
21892194
InvalidBuffer, options, bistate,
21902195
&vmbuffer, NULL);
21912196
page = BufferGetPage(buffer);
21922197

2198+
starting_with_empty_page = PageGetMaxOffsetNumber(page) == 0;
2199+
2200+
if (starting_with_empty_page && (options & HEAP_INSERT_FROZEN))
2201+
all_frozen_set = true;
2202+
21932203
/* NO EREPORT(ERROR) from here till changes are logged */
21942204
START_CRIT_SECTION();
21952205

@@ -2223,14 +2233,23 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
22232233
log_heap_new_cid(relation, heaptup);
22242234
}
22252235

2226-
if (PageIsAllVisible(page))
2236+
/*
2237+
* If the page is all visible, need to clear that, unless we're only
2238+
* going to add further frozen rows to it.
2239+
*
2240+
* If we're only adding already frozen rows to a previously empty
2241+
* page, mark it as all-visible.
2242+
*/
2243+
if (PageIsAllVisible(page) && !(options & HEAP_INSERT_FROZEN))
22272244
{
22282245
all_visible_cleared = true;
22292246
PageClearAllVisible(page);
22302247
visibilitymap_clear(relation,
22312248
BufferGetBlockNumber(buffer),
22322249
vmbuffer, VISIBILITYMAP_VALID_BITS);
22332250
}
2251+
else if (all_frozen_set)
2252+
PageSetAllVisible(page);
22342253

22352254
/*
22362255
* XXX Should we set PageSetPrunable on this page ? See heap_insert()
@@ -2254,8 +2273,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
22542273
* If the page was previously empty, we can reinit the page
22552274
* instead of restoring the whole thing.
22562275
*/
2257-
init = (ItemPointerGetOffsetNumber(&(heaptuples[ndone]->t_self)) == FirstOffsetNumber &&
2258-
PageGetMaxOffsetNumber(page) == FirstOffsetNumber + nthispage - 1);
2276+
init = starting_with_empty_page;
22592277

22602278
/* allocate xl_heap_multi_insert struct from the scratch area */
22612279
xlrec = (xl_heap_multi_insert *) scratchptr;
@@ -2273,7 +2291,15 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
22732291
/* the rest of the scratch space is used for tuple data */
22742292
tupledata = scratchptr;
22752293

2276-
xlrec->flags = all_visible_cleared ? XLH_INSERT_ALL_VISIBLE_CLEARED : 0;
2294+
/* check that the mutually exclusive flags are not both set */
2295+
Assert (!(all_visible_cleared && all_frozen_set));
2296+
2297+
xlrec->flags = 0;
2298+
if (all_visible_cleared)
2299+
xlrec->flags = XLH_INSERT_ALL_VISIBLE_CLEARED;
2300+
if (all_frozen_set)
2301+
xlrec->flags = XLH_INSERT_ALL_FROZEN_SET;
2302+
22772303
xlrec->ntuples = nthispage;
22782304

22792305
/*
@@ -2347,13 +2373,40 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
23472373

23482374
END_CRIT_SECTION();
23492375

2350-
UnlockReleaseBuffer(buffer);
2351-
if (vmbuffer != InvalidBuffer)
2352-
ReleaseBuffer(vmbuffer);
2376+
/*
2377+
* If we've frozen everything on the page, update the visibilitymap.
2378+
* We're already holding pin on the vmbuffer.
2379+
*/
2380+
if (all_frozen_set)
2381+
{
2382+
Assert(PageIsAllVisible(page));
2383+
Assert(visibilitymap_pin_ok(BufferGetBlockNumber(buffer), vmbuffer));
23532384

2385+
/*
2386+
* It's fine to use InvalidTransactionId here - this is only used
2387+
* when HEAP_INSERT_FROZEN is specified, which intentionally
2388+
* violates visibility rules.
2389+
*/
2390+
visibilitymap_set(relation, BufferGetBlockNumber(buffer), buffer,
2391+
InvalidXLogRecPtr, vmbuffer,
2392+
InvalidTransactionId,
2393+
VISIBILITYMAP_ALL_VISIBLE | VISIBILITYMAP_ALL_FROZEN);
2394+
}
2395+
2396+
UnlockReleaseBuffer(buffer);
23542397
ndone += nthispage;
2398+
2399+
/*
2400+
* NB: Only release vmbuffer after inserting all tuples - it's fairly
2401+
* likely that we'll insert into subsequent heap pages that are likely
2402+
* to use the same vm page.
2403+
*/
23552404
}
23562405

2406+
/* We're done with inserting all tuples, so release the last vmbuffer. */
2407+
if (vmbuffer != InvalidBuffer)
2408+
ReleaseBuffer(vmbuffer);
2409+
23572410
/*
23582411
* We're done with the actual inserts. Check for conflicts again, to
23592412
* ensure that all rw-conflicts in to these inserts are detected. Without
@@ -8725,6 +8778,10 @@ heap_xlog_insert(XLogReaderState *record)
87258778
if (xlrec->flags & XLH_INSERT_ALL_VISIBLE_CLEARED)
87268779
PageClearAllVisible(page);
87278780

8781+
/* XLH_INSERT_ALL_FROZEN_SET implies that all tuples are visible */
8782+
if (xlrec->flags & XLH_INSERT_ALL_FROZEN_SET)
8783+
PageSetAllVisible(page);
8784+
87288785
MarkBufferDirty(buffer);
87298786
}
87308787
if (BufferIsValid(buffer))
@@ -8775,6 +8832,10 @@ heap_xlog_multi_insert(XLogReaderState *record)
87758832

87768833
XLogRecGetBlockTag(record, 0, &rnode, NULL, &blkno);
87778834

8835+
/* check that the mutually exclusive flags are not both set */
8836+
Assert (!((xlrec->flags & XLH_INSERT_ALL_VISIBLE_CLEARED) &&
8837+
(xlrec->flags & XLH_INSERT_ALL_FROZEN_SET)));
8838+
87788839
/*
87798840
* The visibility map may need to be fixed even if the heap page is
87808841
* already up-to-date.

src/backend/access/heap/hio.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,14 @@ RelationGetBufferForTuple(Relation relation, Size len,
433433
buffer = ReadBufferBI(relation, targetBlock, RBM_NORMAL, bistate);
434434
if (PageIsAllVisible(BufferGetPage(buffer)))
435435
visibilitymap_pin(relation, targetBlock, vmbuffer);
436+
437+
/*
438+
* If the page is empty, pin vmbuffer to set all_frozen bit later.
439+
*/
440+
if ((options & HEAP_INSERT_FROZEN) &&
441+
(PageGetMaxOffsetNumber(BufferGetPage(buffer)) == 0))
442+
visibilitymap_pin(relation, targetBlock, vmbuffer);
443+
436444
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
437445
}
438446
else if (otherBlock == targetBlock)
@@ -619,6 +627,15 @@ RelationGetBufferForTuple(Relation relation, Size len,
619627
PageInit(page, BufferGetPageSize(buffer), 0);
620628
MarkBufferDirty(buffer);
621629

630+
/*
631+
* The page is empty, pin vmbuffer to set all_frozen bit.
632+
*/
633+
if (options & HEAP_INSERT_FROZEN)
634+
{
635+
Assert(PageGetMaxOffsetNumber(BufferGetPage(buffer)) == 0);
636+
visibilitymap_pin(relation, BufferGetBlockNumber(buffer), vmbuffer);
637+
}
638+
622639
/*
623640
* Release the file-extension lock; it's now OK for someone else to extend
624641
* the relation some more.

src/include/access/heapam_xlog.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@
6969
#define XLH_INSERT_CONTAINS_NEW_TUPLE (1<<3)
7070
#define XLH_INSERT_ON_TOAST_RELATION (1<<4)
7171

72+
/* all_frozen_set always implies all_visible_set */
73+
#define XLH_INSERT_ALL_FROZEN_SET (1<<5)
74+
7275
/*
7376
* xl_heap_update flag values, 8 bits are available.
7477
*/

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy