Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
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

+64
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

+77
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

+69-8
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

+17
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

+3
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)