我们在数据库的磁盘文件中可以发现以fsm后缀的文件名,这些文件是干啥的,另外这些文件最小是24KB三个block的大小,从未见过8KB的fsm文件,这又是为何,这篇文章将要讲述这些问题。
- manu@manu:/usr/pgdata/base/16384$ ll....
- -rw------- 1 manu manu 8192 6月 10 13:05 11882
- -rw------- 1 manu manu 24576 6月 3 21:31 11882_fsm
- ...
- BlockNumber
- smgrnblocks(SMgrRelation reln, ForkNumber forknum)
- {
- return (*(smgrsw[reln->smgr_which].smgr_nblocks)) (reln, forknum);
- }
- typedef unit32 BlockNumber ;
FSM存储的并不是真是的剩余空间,而是近似值,用一个字节表示剩余空间的大小,也就是说
- 0 0~ 31
- 1 32~ 63
- 2 64~ 95
- ...
- 255 8163~8192
如果让你来管理relation所有block的的剩余空间,你如何做?最容易想到的方法就是以数组的方法表示各个block的剩余空间,fsm[0]表示第0个block的剩余空间,fsm[1]表示第一个block的剩余空间,然后把这个信息存入fsm文件即可。比如我想知道第800个block(0-based)还剩下多少空间,只需要将对应的fsm文件的第800个字节(从0开始计数)读出来即可。方便可以扩展,比如relation新增一个block,只需要fsm文件新增一个字节即可。
目前看起来很完美,可是数组有致命的缺陷,我要查找剩余空间的值最小为40的,那数组就傻了眼,因为他不得不挨个的比较,知道遇到值大于40的才能停下,如果block特别多,数组长度非常大,这个问题就会恶化。运气好很快就找到了,运气不好可能遍历了整个数组,效率太低。所以不能采用这种方法。
PostgreSQL采用的方法是分层+堆的思想。想想看假如有100万人需要你管理,你如何管理?只能分层,每100人选出一个百夫长,每100个百夫长选出一个万夫长,我来管理100个万夫长。这就是分层管理的思想。
PostgreSQL源码中有个比较有意思的常数:
- /*
- * Depth of the on-disk tree. We need to be able to address 2^32-1 blocks,
- * and 1626 is the smallest number that satisfies X^3 >= 2^32-1. Likewise,
- * 216 is the smallest number that satisfies X^4 >= 2^32-1. In practice,
- * this means that 4096 bytes is the smallest BLCKSZ that we can get away
- * with a 3-level tree, and 512 is the smallest we support.
- */
- #define FSM_TREE_DEPTH ((SlotsPerFSMPage >= 1626) ? 3 : 4)
- manu@manu:~$ echo 1626*1626*1626|bc
- 4298942376
- manu@manu:~$ echo 4*1024*1024*1024 |bc
- 4294967296
- manu@manu:~$ echo 1625*1625*1625 |bc
- 4291015625
- manu@manu:~$
- #define NodesPerPage (BLCKSZ - MAXALIGN(SizeOfPageHeaderData) -
- offsetof(FSMPageData, fp_nodes))
- #define NonLeafNodesPerPage (BLCKSZ / 2 - 1)
- #define LeafNodesPerPage (NodesPerPage - NonLeafNodesPerPage)
这就要用到堆的思想了。我有4000个下属,管理颇为不便,毕竟我不能挨个去问,你有没有超过20的剩余空间。那怎么办能,这就是heap的思想了:
- 4
- 4 2
- 3 4 0 2
上图解释了分层管理,每个FSM的block有个逻辑块号,也有个物理块号。逻辑块号采用如下的方式表示:
- typedef struct
- {
- int level; /* level */
- int logpageno; /* page number within the level */
- } FSMAddress;
- /* Address of the root page. */
- static const FSMAddress FSM_ROOT_ADDRESS = {FSM_ROOT_LEVEL, 0};
除了逻辑号,还有物理块号,这些管理信息是以block的形式存放在FSM文件中。比如最顶层的root(2,0),对应fsm文件的第0个块号,就是上图中椭圆中的数。请注意,relation文件比较小的时候,比如只有一个block,那么其实只可能有三个fsm文件的block。因为(0,0)本身就可以管理4000个block,而(1,0)暂时管理(0,0)一个下属,而(2,0)管理(1,0)一个下属,其他的fsm block暂时不存在。虽然只有1个relation的block,但是需要个FSM block去管理。当然了(0~3999)个relaiton的block,也只需要这三个FSM block 块管理就够了。这也就解释了为什么FSM文件最少也是24KB,因为三层管理结构,哪怕只有1个block需要管理,也需要3个FSM blcok。
原理都讲清楚了,剩下的都是细节了。比较典型的应用是:
- BlockNumber
- GetPageWithFreeSpace(Relation rel, Size spaceNeeded)
- {
- uint8 min_cat = fsm_space_needed_to_cat(spaceNeeded);
- return fsm_search(rel, min_cat);
- }
- static BlockNumber
- fsm_search(Relation rel, uint8 min_cat)
- {
- int restarts = 0;
- FSMAddress addr = FSM_ROOT_ADDRESS;
- for (;;)
- {
- int slot;
- Buffer buf;
- uint8 max_avail = 0;
- /* Read the FSM page. */
- buf = fsm_readbuf(rel, addr, false); //第一次读取的是FSM 文件 ROOT block,逻辑块号为(2,0),
-
- /* Search within the page */
- if (BufferIsValid(buf))
- {
- LockBuffer(buf, BUFFER_LOCK_SHARE);
- slot = fsm_search_avail(buf, min_cat,
- (addr.level == FSM_BOTTOM_LEVEL), //自己的哪个下属满足要求。
- false);
- if (slot == -1)
- max_avail = fsm_get_max_avail(BufferGetPage(buf));
- UnlockReleaseBuffer(buf);
- }
- else
- slot = -1;
- if (slot != -1)
- {
- /*
- * Descend the tree, or return the found block if we're at the
- * bottom.
- */
- if (addr.level == FSM_BOTTOM_LEVEL)
- return fsm_get_heap_blk(addr, slot); //到达了最底层,直接可以计算出relation的哪个块满足要求
- addr = fsm_get_child(addr, slot); //未到底层,需要再次获取自己的那个下属满足要求
- }
- else if (addr.level == FSM_ROOT_LEVEL) //最高层表示,自己没有一个下属满足要求,可以直接return失败了
- {
- /*
- * At the root, failure means there's no page with enough free
- * space in the FSM. Give up.
- */
- return InvalidBlockNumber;
- }
- else //最高层表示有自己的下属可以满足,可是往下找的过程中却找不到满足要求的,
- //这表示高层掌握的信息和下属的实际情况不符合,需要重新整理信息。
- {
- uint16 parentslot;
- FSMAddress parent;
- /*
- * At lower level, failure can happen if the value in the upper-
- * level node didn't reflect the value on the lower page. Update
- * the upper node, to avoid falling into the same trap again, and
- * start over.
- *
- * There's a race condition here, if another backend updates this
- * page right after we release it, and gets the lock on the parent
- * page before us. We'll then update the parent page with the now
- * stale information we had. It's OK, because it should happen
- * rarely, and will be fixed by the next vacuum.
- */
- parent = fsm_get_parent(addr, &parentslot);
- fsm_set_and_search(rel, parent, parentslot, max_avail, 0);
- /*
- * If the upper pages are badly out of date, we might need to loop
- * quite a few times, updating them as we go. Any inconsistencies
- * should eventually be corrected and the loop should end. Looping
- * indefinitely is nevertheless scary, so provide an emergency
- * valve.
- */
- if (restarts++ > 10000)
- return InvalidBlockNumber;
- /* Start search all over from the root */
- addr = FSM_ROOT_ADDRESS;
- }
- }
- }
我就不展开讲解细节了。太晚了。
参考文献:
1 PostgreSQL数据库内核分析
2 PostgreSQL 9.1.9源码