2022年3月10日 星期四

Postgres OID 系統限制

近期在某個系統上發現看到各個系統表的 oid 增長到了超過 20億,因此針對 Postgres 生成 OID 相關的限制與風險進行了研究。


誰會使用到 oid ?

1. 系統表

oid 主要為系統表在使用,所有物件Table 、Veiw、Procedure 等等皆會使用一定數量的 oid 。

2. TABLE WITH (OID) - before Postgres 11

在 Postgres 11 版以前,建立表格時可以指定 CREATE TABLE ... WITH (OID),來為每筆資料指定 OID,但此選項在 12 版開始已經被移除。

3. Toasted Table

Postgres在儲存資料上一個 Block 預設為 8KB ,若是單筆資料超過此限制時則會塞不下,因此針對較大的文本資料(約2KB),會被移到 Toasted Table 進行壓縮/儲存,而 Toated Table 在存資料時每筆資料皆會占用一個 oid。



oid 大小限制

在 Postgres 我們熟知 TransactionID (TXID) 有 40 億的限制,若是使用超過這限制則會造成資料庫出現嚴重異常損毀,因此必須要定期維護進行 Vacuum Freeze 的清理。

而 oid 也有 40 億的限制,但與 TXID 不同的是,oid 並無清理機制,若是使用到超過 40億限制,則會從頭開始計算(跳過系統保留區段),而一般預期 oid 主要使用為系統表,因此大部分使用情境是不會超過此 40 億限制的。

由於 oid 超過 40 億會從頭開始取號,因此 oid 有一個特性是,oid 只保證單一表格內不重複,但不同系統表的 oid 有可能會重複。

在取號的過程,系統會檢查對應的表格內是否已經有使用過此 oid,若沒有則指定此值,若已經有含此 oid 的資料,則會繼續往下一個找,直到找到表格內無重複 oid 才指定 oid 並記錄為最新的位置,

接續前面所提到的,當 oid 沒有超過 40 億限制前,每次皆可取得不重複 oid,當 oid 超過 40 億限制從頭開始取號後,則開始會有遇到重複 oid 的問題,此問題不會有資料損毀的風險,但會造成資料庫效能下降。



Toasted Table

最一開始提到,最近看到了一個系統的 oid 抽號超過了 20 億,一開始想了一些可能性。

1.  Temp Table

過度頻繁的迴圈建立 Temp Table,需要注意 pg_attribute 表格膨脹的問題。

2.  Vacuum Full

實質上為建立新表替換舊表,因此可預期會用到新的 oid。

3.  DB Crash Recovery

不確定具體原因,但有觀察到一定程度的 oid 增長。


上面這些行為都會導致一定程度的 oid 使用,但都不至於到使用上億個 oid ,因此繼續往尋找別的可能原因。

後續發現問題主要在 Toasted Table,這邊簡單建一個表來進行測試,透過 lpad 產生長文本測試資料,在 pg_toast.pg_toast_1505300 表可以檢視到,此表格透過 chunk_id 來記錄資料位置,並且型態為 oid,每一筆 toasted 資料產生,同時也會有一個對應的 chunk_id 

edb=# create table t1 ( a text );
CREATE TABLE
edb=# insert into t1(a) select lpad('a',1000000,'a') from generate_series(1,1000);
INSERT 0 1000
edb=# select reltoastrelid from pg_class where relname in ('t1');
 reltoastrelid
---------------
       1505303
(1 row)

edb=# select oid,relname from pg_class where oid in (1505303);
   oid   |     relname
---------+------------------
 1505303 | pg_toast_1505300
(1 row)

edb=# \d+ pg_toast.pg_toast_1505300
TOAST table "pg_toast.pg_toast_1505300"
   Column   |  Type   | Storage
------------+---------+---------
 chunk_id   | oid     | plain
 chunk_seq  | integer | plain
 chunk_data | bytea   | plain
Owning table: "public.t1"
Indexes:
    "pg_toast_1505300_index" PRIMARY KEY, btree (chunk_id, chunk_seq)
Access method: heap

edb=# insert into t1(a) select lpad('a',1000000,'a');
INSERT 0 1

edb=# select chunk_id, chunk_seq from pg_toast.pg_toast_1505300 order by chunk_id desc;
 chunk_id | chunk_seq
----------+-----------
  1506312 |         5
  1506312 |         4
  1506312 |         3
  1506312 |         2
  1506312 |         1
  1506312 |         0
(6 rows)

一般系統除非有大量文資料在進行異動不然不太有機會遇到 oid 超過四十億的限制,就算遇到了也不會直接損毀資料庫,但可能會需要開始注意,或許有可能部分效能瓶頸會跟這狀況有關。


參考資料:

https://wiki.postgresql.org/wiki/TOAST

https://xiaowing.github.io/post/20171117_pg_knowhow_oid/

https://postgrespro.com/list/thread-id/2210931

https://wiki.postgresql.org/wiki/TOAST

https://blog.anayrat.info/en/2022/02/14/postgresql-toast-compression-and-toast_tuple_target/

https://fluca1978.github.io/2021/02/08/PostgreSQLToastCorruption.html

https://pganalyze.com/blog/5mins-postgres-jsonb-toast