hydradatabase / hydra

Hydra: Column-oriented Postgres. Add scalable analytics to your project in minutes.
https://www.hydra.so
Apache License 2.0
2.83k stars 76 forks source link

[Bug]: Columnar tables cause txid wraparound #192

Closed amihajlovic closed 12 months ago

amihajlovic commented 12 months ago

What's wrong?

(auto)vacuuming columnar tables leave pg_class.relfrozenxid as is and doesn't overwrite it with newer txid value. This eventually causes txid wraparound issue and needs to be resolved by setting a database to single user mode and running vacuum(full) on the problematic tables. Vacuum (full) recreates the table and resets pg_class.relfrozenxid value. Vacuum (freeze) nor columnar.vacuum don't work in this case.

Heap tables always update pg_class.relfrozenxid when vacuum (freeze) is run.

Tested using docker ghcr.io/hydradatabase/hydra:latest

select extversion  from pg_catalog.pg_extension where extname = 'columnar';
/*
extversion|
----------+
11.1-10   |
*/

Repro steps:

create table x () using columnar;

SELECT c.relfrozenxid
FROM pg_class AS c 
WHERE oid::regclass::text  = 'x'
;
/*
relfrozenxid|
------------+
737         |
*/
select txid_current(); --increase txid counter
/*
txid_current|
------------+
         738|
*/
vacuum(verbose, freeze) x;
/*
statistics for "x":
storage id: 10000000000
total file size: 16384, total data size: 0
compression rate: 1.00x
total row count: 0, stripe count: 0, average rows per stripe: 0
chunk count: 0, containing data for dropped columns: 0

"x": stopping vacuum due to zero column table
*/
SELECT c.relfrozenxid
FROM pg_class AS c 
WHERE oid::regclass::text  = 'x'
;
/*
relfrozenxid|
------------+
737         |
*/
------------------------------------------------------------
create table y () using heap;

SELECT c.relfrozenxid
FROM pg_class AS c 
WHERE oid::regclass::text  = 'y'
;
/*
relfrozenxid|
------------+
740         |
*/

select txid_current(); --increase txid counter
/*
txid_current|
------------+
         741|
*/
vacuum(verbose, freeze) y;
/*
aggressively vacuuming "public.y"
table "y": found 0 removable, 0 nonremovable row versions in 0 out of 0 pages
*/

SELECT c.relfrozenxid
FROM pg_class AS c 
WHERE oid::regclass::text  = 'y'
;
/*
relfrozenxid|
------------+
742         |
*/

------------------------------------------------------------

create table x1 (i int) using columnar;

SELECT c.relfrozenxid
FROM pg_class AS c 
WHERE oid::regclass::text  = 'x1'
;
/*
relfrozenxid|
------------+
742         |
*/
select txid_current(); --increase txid counter
/*
txid_current|
------------+
         743|
*/
vacuum(verbose, freeze) x1;
/*
statistics for "x1":
storage id: 10000000001
total file size: 16384, total data size: 0
compression rate: 1.00x
total row count: 0, stripe count: 0, average rows per stripe: 0
chunk count: 0, containing data for dropped columns: 0

"x1": stopping vacuum due to empty table
*/
SELECT c.relfrozenxid
FROM pg_class AS c 
WHERE oid::regclass::text  = 'x1'
;
/*
relfrozenxid|
------------+
742         |
*/
------------------------------------------------------------
create table y1 (i int) using heap;

SELECT c.relfrozenxid
FROM pg_class AS c 
WHERE oid::regclass::text  = 'y1'
;
/*
relfrozenxid|
------------+
740         |
*/

select txid_current(); --increase txid counter
/*
relfrozenxid|
------------+
745         |
*/
vacuum(verbose, freeze) y1;
/*
aggressively vacuuming "public.y1"
table "y1": found 0 removable, 0 nonremovable row versions in 0 out of 0 pages
*/

SELECT c.relfrozenxid
FROM pg_class AS c 
WHERE oid::regclass::text  = 'y1'
;
/*
relfrozenxid|
------------+
746         |
*/

------------------------------------------------------------

create table x2 (i int) using columnar;
insert into x2(i) values(1);

SELECT c.relfrozenxid
FROM pg_class AS c 
WHERE oid::regclass::text  = 'x2'
;
/*
relfrozenxid|
------------+
746         |
*/
select txid_current(); --increase txid counter
/*
txid_current|
------------+
         748|
*/
vacuum(verbose, freeze) x2;
/*
statistics for "x2":
storage id: 10000000002
total file size: 24576, total data size: 14
compression rate: 0.36x
total row count: 1, stripe count: 1, average rows per stripe: 1
chunk count: 1, containing data for dropped columns: 0, zstd compressed: 1
*/
SELECT c.relfrozenxid
FROM pg_class AS c 
WHERE oid::regclass::text  = 'x2'
;
/*
relfrozenxid|
------------+
746         |
*/
------------------------------------------------------------

------------------------------------------------------------

create table x3 (i int) using columnar;
insert into x3(i) values(1);

SELECT c.relfrozenxid
FROM pg_class AS c 
WHERE oid::regclass::text  = 'x3'
;
/*
relfrozenxid|
------------+
750         |
*/
select txid_current(); --increase txid counter
/*
txid_current|
------------+
         752|
*/
select columnar.vacuum('x3'::regclass); 
/*
vacuum|
------+
     0|
*/
SELECT c.relfrozenxid
FROM pg_class AS c 
WHERE oid::regclass::text  = 'x3'
;
/*
relfrozenxid|
------------+
750         |
*/
------------------------------------------------------------