咱們知道,PostgreSQL數據庫中的信息,最終是要寫入持久設備的。那麼PostgreSQL是怎麼將信息組織存儲在磁盤上的呢? Bruce Momjian有一個slide 《Insider PostgreSQL shared memory》,裏面的圖片很是直觀的描述了,shared buffer,page ,磁盤文件之間的關係,請看下圖。 接下來幾篇博客,從不一樣層面講述PostgreSQL存儲相關的的內存:
上圖中左下角是page的組織形式。PostgreSQL 8K爲一個頁面,從share buffer寫入relation 對應的磁盤文件,或者從relation對應的磁盤文件讀入8K到shared buffer。shared buffers是一組8K的頁面,做爲緩存。對於數據庫的relation而言,一條記錄(Item或者叫Tuple),大小不一,不會剛好佔據8K的空間,可能只有幾十個字節,因此,如何將多條記錄存放進8K的shared buffer,這就是page的組織形式了,我會在另外一篇博文介紹。
對於Linux 咱們知道,讀文件,會首先將磁盤上的內容讀入內存,寫文件會首先寫入cache,將cache標記成dirty,在合適的時機寫入磁盤。對於這個不太熟悉的,能夠閱讀我前面的一篇博文 file 和page cache的一些事,PostgreSQL中shared buffers 之於relation file in disk 就至關於Linux 中page cache之於file in disk。
查看/設置 shared buffers大小:
首當其衝的是,PostgreSQL中shared buffers有多大,多少個8KB的buffers,固然這是能夠配置的,咱們經過以下方法查看配置:css
或者:html
- select name,unit,setting,current_setting(name) from pg_settings where name = 'shared_buffers' ;
上面講述的是查看,如何修改呢?須要修改配置文件postgresql.conf :node
- root@manu:/usr/pgdata# cat postgresql.conf | grep ^shared_buffers
- shared_buffers = 24MB # min 128kB
咱們能夠將shared_buffers改爲一個其餘的值,至於改爲多大的值是合理的,則取決與你的硬件環境,好比你的硬件很強悍,16GB內存,那麼這個值設置成24MB就太摳門了。至於shared buffers多大才合理,網上有不少的說法,有的說內存總量的10%~15%,有的說內存總量的25%,幸虧PostgreSQL提供了一些performance measure的工具,讓咱們可以監測PostgreSQL運行的performance,咱們實際狀況能夠根據PostgreSQL的性能統計信息,調大或者調小這個shared buffers的大小。
可是又有個問題,shared buffer是以共享內存的形式分配的,若是在配置文件中配置的值超過操做系統對share memory的最大限制,會導PostgreSQL初始化失敗。以下圖,我將postgresql.conf中shared_buffers = 64MB,就致使了啓動失敗以下圖所示:
緣由是kernel的SHMMAX最大隻有32MB,下面我查看而且修改爲512MB
改過以後,就能夠啓動PostgreSQL了,咱們能夠查看shared_buffers已經變成了64MB:sql
- manu_db=# show shared_buffers ;
- shared_buffers
- ----------------
- 64MB
- (1 row)
簡單的內容結束了,咱們須要深刻代碼分析shared buffers的原理了,如何組織內存,如何分配,如何page replacement,都在源碼之中查找答案。詳細的內容,我打算在下一篇博文裏面介紹,由於原理部分自己就會內容有不少,必然會致使我這篇文章比較長。我本文剩下的內容想介紹內存中的shared buffer 如何得知對應的磁盤的文件。由於shared buffer中的8K內容,最終會sync到磁盤文件。PostgreSQL是將內存中的shared buffer和磁盤上的某個文件對應起來的呢。
shared buffer與relation的磁盤文件的對應關係
本文的第一個圖,上半部分講述的是shared buffer的結構,分兩部分
1 赤果果的buffer,N個8K塊,每一個塊存放從relation對應磁盤文件讀上來的某個8K的內容。
2 管理buffer的結構,也是N個,有幾個buffer,就有幾個管理結構。Of Course,管理結構佔用的內存空間要遠小於赤果果的buffer,不然內存利用率過低了。
這是初始化的時候,爲這兩個部分分配空間:數據庫
- BufferDescriptors = (BufferDesc *)
- ShmemInitStruct("Buffer Descriptors",
- NBuffers * sizeof(BufferDesc), &foundDescs);
- BufferBlocks = (char *)
- ShmemInitStruct("Buffer Blocks",
- NBuffers * (Size) BLCKSZ, &foundBufs);
這個管理buffer的結構體叫BufferDesc,我智商不高,也知道確定也知道會記錄對應的buffer有沒有被使用,對應的是哪一個磁盤文件的第幾個8K block,爲了應對併發,確定會有鎖。咱們看下這個結構體的定義:緩存
- typedef struct sbufdesc
- {
- BufferTag tag; /* ID of page contained in buffer */
- BufFlags flags; /* see bit definitions above */
- uint16 usage_count; /* usage counter for clock sweep code */
- unsigned refcount; /* # of backends holding pins on buffer */
- int wait_backend_pid; /* backend PID of pin-count waiter */
- slock_t buf_hdr_lock; /* protects the above fields */
- int buf_id; /* buffer's index number (from 0) */
- int freeNext; /* link in freelist chain */
- LWLockId io_in_progress_lock; /* to wait for I/O to complete */
- LWLockId content_lock; /* to lock access to buffer contents */
- } BufferDesc;
OK,咱們回到咱們最初關係的問題,當前這個shared buffer和which db ,which table,which type(後面解釋type),which file的which 8KB block對應。第一個 BUfferTag類型的tag字段就是肯定這個對應關係的:併發
- typedef enum ForkNumber
- {
- InvalidForkNumber = -1,
- MAIN_FORKNUM = 0,
- FSM_FORKNUM,
- VISIBILITYMAP_FORKNUM,
- INIT_FORKNUM
- /*
- * NOTE: if you add a new fork, change MAX_FORKNUM below and update the
- * forkNames array in catalog.c
- */
- } ForkNumber;
- typedef struct RelFileNode
- {
- Oid spcNode; /* tablespace */
- Oid dbNode; /* database */
- Oid relNode; /* relation */
- } RelFileNode;
- /*
- * Buffer tag identifies which disk block the buffer contains.
- *
- * Note: the BufferTag data must be sufficient to determine where to write the
- * block, without reference to pg_class or pg_tablespace entries. It's
- * possible that the backend flushing the buffer doesn't even believe the
- * relation is visible yet (its xact may have started before the xact that
- * created the rel). The storage manager must be able to cope anyway.
- *
- * Note: if there's any pad bytes in the struct, INIT_BUFFERTAG will have
- * to be fixed to zero them, since this struct is used as a hash key.
- */
- typedef struct buftag
- {
- RelFileNode rnode; /* physical relation identifier */
- ForkNumber forkNum;
- BlockNumber blockNum; /* blknum relative to begin of reln */
- } BufferTag;
咱們能夠看到BufferTag中的rnode,表徵的是which relation。這個rnode的類型是RelFileNode類型,包括數據庫空間/database/relation,從上到下三級結構,惟一肯定了PostgreSQL的一個relation。對於relation而言並非只有一種類型的磁盤文件,less
- -rw------- 1 manu manu 270336 6月 3 21:31 11785
- -rw------- 1 manu manu 24576 6月 3 21:31 11785_fsm
- -rw------- 1 manu manu 8192 6月 3 21:31 11785_vm
如上圖所示11785對應某relation,但磁盤空間中有三種,包括fsm和vm後綴的兩個文件。咱們看下ForkNumber的註釋:ide
- /*
- * The physical storage of a relation consists of one or more forks. The
- * main fork is always created, but in addition to that there can be
- * additional forks for storing various metadata. ForkNumber is used when
- * we need to refer to a specific fork in a relation.
- */
MAIN_FORKNUM type的老是存在,可是某些relation還存在FSM_FORKNUM和VISIBILITYMAP_FORKNUM兩種文件,這兩種我目前知之不詳,我就不瞎說了。
咱們慢慢來,先放下blockNum這個成員變量,步子太大容易扯蛋,咱們先根據rnode+forkNum找到磁盤對應的文件?
這個尋找磁盤文件的事兒是relpath這個宏經過調用relpathbackend實現的:工具
- char *
- relpathbackend(RelFileNode rnode, BackendId backend, ForkNumber forknum)
- {
- if (rnode.spcNode == GLOBALTABLESPACE_OID)
- {
- ...
- }
- else if (rnode.spcNode ==DEFAULTTABLESPACE_OID)
- {
- pathlen = 5 + OIDCHARS + 1 + OIDCHARS + 1 + FORKNAMECHARS + 1;
- path = (char *) palloc(pathlen);
- if (forknum != MAIN_FORKNUM)
- snprintf(path, pathlen, "base/%u/%u_%s",
- rnode.dbNode, rnode.relNode,
- forkNames[forknum]);
- else
- snprintf(path, pathlen, "base/%u/%u",
- rnode.dbNode, rnode.relNode);
- }
- else
- {
- ...
- }
- }
由於咱們是pg_default,因此咱們走DEFAULTTABLESPACE_OID這個分支。決定了咱們在base目錄下,db的oid(即BufferTag->rnode->dbNode)是16384決定了base/16384/,BufferTag->rnode->relNode + BufferTag->forkNum 決定了是base/16384/16385仍是 base/16384/16385_fsm or base/16384/16385_vm。
查找文件基本結束,不過,某些某些relation比較大,記錄比較多,會致使磁盤文件超大,爲了防止文件系統對磁盤文件大小的限制而致使的寫入失敗,PostgreSQL作了分段的機制。以個人friends爲例,若是隨着記錄的不斷插入,最後friends對應的磁盤文件16385愈來愈大,當超過1G的時候,PostgreSQL就會新建一個磁盤文件叫16385.1,超過2G的時候PostgreSQL再次分段,新建文件16385.2 。這個1G就是有Block size = 8KB和blockS per segment of large relation=128K(個)共同決定的。
源碼中的定義上面有註釋,解釋了不少內容:
- /* RELSEG_SIZE is the maximum number of blocks allowed in one disk file. Thus,
- the maximum size of a single file is RELSEG_SIZE * BLCKSZ; relations bigger
- than that are divided into multiple files. RELSEG_SIZE * BLCKSZ must be
- less than your OS' limit on file size. This is often 2 GB or 4GB in a
- 32-bit operating system, unless you have large file support enabled. By
- default, we make the limit 1 GB to avoid any possible integer-overflow
- problems within the OS. A limit smaller than necessary only means we divide
- a large relation into more chunks than necessary, so it seems best to err
- in the direction of a small limit. A power-of-2 value is recommended to
- save a few cycles in md.c, but is not absolutely required. Changing
- RELSEG_SIZE requires an initdb. */
- #define RELSEG_SIZE 131072
固然了這個128K的值是默認值,咱們編譯PostgreSQL的階段 configure的時候,能夠經過--with-segsize 指定其餘的值,不過這個我沒有try過。
考慮上segment,真正的磁盤文件名fullpath就呼之欲出了:
若是分段了,在relpath獲取的名字後面加上段號segno,若是段號是0,那麼fullpath就是前面講的relpath。
- static char *
- _mdfd_segpath(SMgrRelation reln, ForkNumber forknum, BlockNumber segno)
- {
- char *path,
- *fullpath;
- path = relpath(reln->smgr_rnode, forknum);
- if (segno > 0)
- {
- /* be sure we have enough space for the '.segno' */
- fullpath = (char *) palloc(strlen(path) + 12);
- sprintf(fullpath, "%s.%u", path, segno);
- pfree(path);
- }
- else
- fullpath = path;
- return fullpath;
- }
怎麼判斷segno是幾?這個太easy了,(BufferTag->rnode->blockNum/RELSEG_SIZE)。 OK,講過這個shared buffer中的8K塊和relation 的磁盤文件的對應關係,咱們就能夠安心講述 shared buffer的一些內容了。悲劇啊,文章寫了很久。參考文獻:1 PostgreSQL 性能調校2 PostgreSQL 9.1.9 Source Code3 Bruce Momjian的Insider PostgreSQL shared memory