12/08/2018, 16:23

How PostgreSQL organizes data

Như bạn đã viết trong PostgreSQL, data được chứa trong các tables, các tables lại được gộp với nhau trong 1 database. Ở tầng cao nhất database sẽ được lưu trữ với nhau tại các clusters. Chúng ta có thể xem được cấu trúc của việc lưu trữ này trên disk. postgres = # SELECT datname, oid FROM ...

Như bạn đã viết trong PostgreSQL, data được chứa trong các tables, các tables lại được gộp với nhau trong 1 database. Ở tầng cao nhất database sẽ được lưu trữ với nhau tại các clusters. Chúng ta có thể xem được cấu trúc của việc lưu trữ này trên disk.

postgres=# SELECT datname, oid FROM pg_database;
  datname  |  oid  
-----------+-------
perf       | 16556
template1  |     1
template0  | 16555

Ta thấy rằng có tất cả 3 databases đang được lưu trong cluster. Ta có thể tìm ra vị trí lưu chúng trên disk.

$ cd $PGDATA
$ ls 
base    pg_clog      pg_ident.conf  pg_xlog          postmaster.opts

global  pg_hba.conf  PG_VERSION     postgresql.conf  postmaster.pid

Thư mục $PGDATA có chứa một subdirectory khác là base. Thư mục này chính là nơi chứa databases.

$ cd ./base
$ ls -l
total 12

drwx------    2 postgres pgadmin      4096 Jan 01 20:53 1

drwx------    2 postgres pgadmin      4096 Jan 01 20:53 16555

drwx------    3 postgres pgadmin      4096 Jan 01 22:38 16556

Chú ý rằng chúng ta có 3 thư mục con nằm tron $PGDATA/base. Quay lại với kết quả của câu query hồi nãy:

SELECT datname, oid FROM pg_database;

Ta nhận thấy rằng tên của 3 thư mục này chính là giá trị oid tương ứng với đừng database. Thư mục 16556 chứa database perf, thư mục 16555 chứa database template0.

Tiến vào sâu hơn, ta thấy.

$ cd ./1
$ ls
1247   16392  16408  16421  16429  16441  16449  16460  16472

1249   16394  16410  16422  16432  16442  16452  16462  16474

1255   16396  16412  16423  16435  16443  16453  16463  16475

1259   16398  16414  16424  16436  16444  16454  16465  16477

16384  16400  16416  16425  16437  16445  16455  16466  pg_internal.init

16386  16402  16418  16426  16438  16446  16456  16468  PG_VERSION

16388  16404  16419  16427  16439  16447  16457  16469

16390  16406  16420  16428  16440  16448  16458  16471

Lại một đống file tên là số, có thể lờ mờ đoán ra đây là các oids, nhưn g là oids gì thì chịu. Trong database có chứa các table, nên có thể đây là các oids của table. Kiểm chứng:

$ psql -q -d template1

template1=# SELECT relname, oid FROM pg_class;

template1=# SELECT oid, relname FROM pg_class ORDER BY oid;

  oid  |             relname
-------+---------------------------------
  1247 | pg_type

  1249 | pg_attribute

  1255 | pg_proc

  1259 | pg_class

  1260 | pg_shadow

  1261 | pg_group

  1262 | pg_database

 16384 | pg_attrdef

 16386 | pg_relcheck

  ...  |    ...

Và như vậy mỗi file tương ứng với mỗi table trong database. Và mỗi table sẽ tương ứng với một oid trong bảng pg_class.

Trong bảng pg_class còn có 2 column khác, qua đó ta có thể thấy cấu trúc của PostgreSQL.

perf=# SELECT relname, oid, relpages, reltuples FROM pg_class

perf-#   ORDER BY oid

   relname    | oid  | reltuples | relpages
--------------+------+-----------+----------
 pg_type      | 1247 |       143 |        2

 pg_attribute | 1249 |       795 |       11

 pg_proc      | 1255 |      1263 |       31

 pg_class     | 1259 |       101 |        2

 pg_shadow    | 1260 |         1 |        1

 pg_group     | 1261 |         0 |        0

     ...      |  ... |       ... |      ...

Gía trị reltuples cho ta số lượng tuples trong mỗi table. relpages cho ta số lượng page cần để lưu lại toàn bộ contents của table đó. Vậy giá trị này phản ánh giá trị thực tế lưu trên disk như thế nào? Nếu bạn xem kĩ hơn một số table, bạn sẽ thấy mối quan hệ đó.

$ ls -l 1247 1249

-rw-------    1 postgres pgadmin     16384 Jan 01 20:53 1247

-rw-------    1 postgres pgadmin     90112 Jan 01 20:53 1249

File 1247 (pg_type) có kích thước 16384 bytes, có được chưa trong 2 pages, file 1249 (pg_attribute) có kích thước 90112 bytes chiếm 11 pages. Vậy mỗi page sẽ chiếm 8192(bytes) = 8k bytes. Trong PostgreSQL, toàn bộ quá trình I/O được thực thực hiện page-by-page. Vì vậy khi thực hiện query trên một row của table, PostgreSQL sẽ đọc tí ít là 1 page, có thể là nhiều page nếu như dât trên row. Khi update một dòng, PostgreSQL sẽ tạo ra một version mới của dòng đó ở cuối table và đánh dấu hàng được update là invalid.Size

Size của mỗi page sẽ được fix là 8192 bytes. Bạn có thể thay đổi config, nhưng tất cả các page sẽ cùng kích thước. Nhưng kích thước của từng row là không có định, nó tùy thuộc vào data trong mỗi row. Vì vậy, số lượng row trong 1 page là không thể tính toán được.

Nếu trong trường hợp, kích thước của 1 row vượt quá giới hạn 8k bytes, PostgreSQL sẽ lưu data vào TOAST (The Oversized Attribute Storage Technique) table. TOAST table này được xem như là một extension của table bình thường. Nó chứa data mà không chứa vừa vào table bình thường.

Indexes cũng sẽ được trong 1 pages, page mà chứa data được gọi là heap page, còn page chứa index được gọi là index page. Nó cũng giống như một page bình thường, không thể biết số lượng row trong 1 page, nếu quá lớn nó cũng được lưu trong TOAST.

Page Caching

Có 2 nguyên tắc cơ bảng mà tất cả các Database System đều tuân thủ theo chính là:

  • Truy cập RAM thì nhanh hơn trên Disk
  • Không gian của RAM thì ít hơn Disk

Theo đó, PostgreSQL tối giản truy cập I/O trên Disk bằng cách lưu data được truy cập nhiều lần trên RAM. Khi server được start, PostgreSQL tạo ra một cấu trúc dữ liệu gọi là Buffer Cache. Buffer Cache được tổ chức dưới dạng như một tập hợp của các page 8k bytes. Mỗi page trong Buffer Cache tương ứng với một trong số nhiều page trong database. Buffer Cache này được share cho toàn bộ các tiến trình sử dụng database.

Khi bạn thực hiện query một row trong database, PostgreSQL sẽ đọc heap block chứa data, và đưa heap block này vào Buffer Cache này. Nếu không có chỗ chứa, PostgreSQL sẽ remove một số block ra khỏi Buffer Cache. Index block cũng sẽ được đưa vào buffer giống như heap block.

To be continued...

0