Postgresql源码(158)pg_filenode.map文件作用relmap和redo流程(RM_RELMAP_ID = 7)

pg_filenode.map文件作用

global和base下每个db都会带一个pg_filenode.map文件,用来保存基础系统表 【oid→文件名】的映射关系。因为普通表可以通过pg_class的relfilenode找到表文件,但是启动时pg_class显然无法通过查找自己来找到表文件。还有一些共享系统表,例如pg_authid,如果在一个库中执行了vacuum full,auth表的relfilenode变了,就需要更新所有库中的pg_class,这里需要跨库并且还要在一个事务内,现在实现做不到这一点,所以基础表的 【oid→文件名】映射关系,是用文件来保存的,并且会写WAL日志保证持久化和一致性。

relmapper.c 给出了解释:

For most tables, the physical file underlying the table is specified by

pg_class.relfilenode. However, that obviously won't work for pg_class

itself, nor for the other "nailed" catalogs for which we have to be able

to set up working Relation entries without access to pg_class. It also

does not work for shared catalogs, since there is no practical way to

update other databases' pg_class entries when relocating a shared catalog.

Therefore, for these special catalogs (henceforth referred to as "mapped

catalogs") we rely on a separately maintained file that shows the mapping

from catalog OIDs to filenumbers.

查看哪些表是mapped的

复制代码
SELECT relname, relfilenode FROM pg_class WHERE relfilenode = 0 ORDER BY relname;

pg_filenode.map文件读取

文件结构比较简单,可以用shell做查询:

复制代码
#define RELMAPPER_FILEMAGIC  0x592717
#define MAX_MAPPINGS         64

typedef struct RelMapping {
    Oid            mapoid;          /* 4 bytes: 系统表 OID */
    RelFileNumber  mapfilenumber;   /* 4 bytes: 对应的 filenumber */
} RelMapping;                       /* 每条 8 bytes */

typedef struct RelMapFile {
    int32       magic;              /* 4 bytes: 固定值 0x592717 */
    int32       num_mappings;       /* 4 bytes: 有效映射条数 */
    RelMapping  mappings[64];       /* 64 × 8 = 512 bytes (固定大小) */
    pg_crc32c   crc;                /* 4 bytes: CRC32C 校验 */
} RelMapFile;                       /* 总计: 4+4+512+4 = 524 bytes */

脚本

复制代码
MAP_FILE="${1:-$PGDATA/global/pg_filenode.map}"

# ========== 读 magic number (前4字节, little-endian) 
#   -A n    : Address radix = none,不显示左侧的地址偏移量
#   -t x4   : Type = hex, 4 bytes 一组,即以 4 字节十六进制整数输出
#   -N 4    : 只读取 4 个字节
#   -j 0    : Jump,跳过前 0 个字节(即从文件开头开始读)
# tr -d ' ' : 删除输出中的空格
magic=$(od -An -tx4 -N4 -j0 "$MAP_FILE" | tr -d ' ')
echo "Magic: 0x$magic"

# ========== 读 num_mappings (第5-8字节) 
#   -t d4   : Type = decimal, 4 bytes 一组,即以 4 字节有符号十进制整数输出
#   -N 4    : 只读取 4 个字节
#   -j 4    : Jump 4,跳过前 4 个字节(跳过 magic),从第 5 字节开始读
num=$(od -An -td4 -N4 -j4 "$MAP_FILE" | tr -d ' ')
echo "Mappings count: $num"
echo "-----------------------------------"
# printf 格式化输出:%-12s 表示左对齐、占 12 字符宽度的字符串
printf "%-12s -> %s\n" "OID" "FileNumber"
echo "-----------------------------------"

# ========== 逐条读取 mappings ===
# 每条 mapping = 8 bytes (oid 4字节 + filenumber 4字节)
# 数据从文件的第 8 字节(offset=8)开始,前面是 magic(4) + num(4)
for ((i=0; i<num; i++)); do
    # 计算当前第 i 条 mapping 的文件偏移量
    # offset = 8 (header大小) + i * 8 (每条8字节)
    offset=$((8 + i * 8))

    # 读 OID:从 offset 处读 4 字节,输出为十进制
    #   -j$offset : 跳到当前 mapping 的起始位置
    oid=$(od -An -td4 -N4 -j$offset "$MAP_FILE" | tr -d ' ')

    # 读 FileNumber:从 offset+4 处读 4 字节(OID后面紧跟的4字节)
    fnum=$(od -An -td4 -N4 -j$((offset+4)) "$MAP_FILE" | tr -d ' ')

    # printf 格式化输出:%-12d 表示左对齐、占12字符宽的十进制整数
    printf "%-12d -> %d\n" "$oid" "$fnum"
done

redo流程

关键数据结构

c 复制代码
/* src/include/utils/relmapper.h */

/*
 * xl_relmap_update: 关系映射更新的 WAL 记录数据
 * 包含目标数据库信息和完整的新映射文件内容(整体替换)
 */
typedef struct xl_relmap_update
{
    Oid         dbid;       /* 数据库 ID; 0 表示共享映射(shared catalog) */
    Oid         tsid;       /* 数据库所在的表空间; 或 pg_global */
    int32       nbytes;     /* 映射数据的字节数(= sizeof(RelMapFile)) */
    char        data[FLEXIBLE_ARRAY_MEMBER];  /* 变长: 完整的 RelMapFile 内容 */
} xl_relmap_update;

#define MinSizeOfRelmapUpdate offsetof(xl_relmap_update, data)

代码

c 复制代码
/* src/backend/utils/cache/relmapper.c:1096-1142 */

/*
 * RELMAP 资源管理器的 redo 入口函数
 */
void
relmap_redo(XLogReaderState *record)
{
    uint8       info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;

    /* relmap 记录不使用 backup block */
    Assert(!XLogRecHasAnyBlockRefs(record));

    if (info == XLOG_RELMAP_UPDATE)
    {
        /*
         * ===== 处理 RELMAP_UPDATE: 重写映射文件 =====
         */
        xl_relmap_update *xlrec = (xl_relmap_update *) XLogRecGetData(record);
        RelMapFile  newmap;
        char       *dbpath;

        /*
         * 安全性检查: WAL 中的 nbytes 必须等于 sizeof(RelMapFile)
         * 如果不匹配, 说明 WAL 记录损坏, 必须 PANIC
         */
        if (xlrec->nbytes != sizeof(RelMapFile))
            elog(PANIC, "relmap_redo: wrong size %u in relmap update record",
                 xlrec->nbytes);

        /* 从 WAL 记录的 data 区域复制出完整的新映射文件内容 */
        memcpy(&newmap, xlrec->data, sizeof(newmap));

        /*
         * 构建目标数据库的路径
         * 例如: base/12345 (对于普通数据库)
         *       global     (对于共享系统表, dbid=0)
         */
        dbpath = GetDatabasePath(xlrec->dbid, xlrec->tsid);

        /*
         * 将新映射写入磁盘并发送 sinval 消息:
         * - 第2参数 false: 不写新 WAL(因为我们正在回放 WAL)
         * - 第3参数 true:  发送 sinval 失效消息(通知其他后端刷新缓存)
         * - 第4参数 false: 不保留文件(非事务上下文)
         *
         * 注意: 对于 CREATE DATABASE 场景, 发送 sinval 消息是不必要但无害的。
         * 如果想避免, 可以在 WAL 记录中加一个标志位区分两种场景,
         * 但当前实现选择了简洁性。
         */
        LWLockAcquire(RelationMappingLock, LW_EXCLUSIVE);
        write_relmap_file(&newmap, false, true, false,
                          xlrec->dbid, xlrec->tsid, dbpath);
        LWLockRelease(RelationMappingLock);

        pfree(dbpath);
    }
    else
        elog(PANIC, "relmap_redo: unknown op code %u", info);
}

gdb分析redo

主库执行vacuum full pg_authid;,oid是1260。

备库redo时把完整的relmap更新到文件中了:

相关推荐
小尔¥6 分钟前
MySQL故障排查与优化
运维·数据库·mysql
rrrjqy11 分钟前
Redis常见问题(一)
数据库·redis·缓存
Humbunklung12 分钟前
WMO 天气代码(Code Table 4677)深度解析与应用报告
开发语言·数据库·python
道清茗22 分钟前
【MySQL知识点问答题】锁机制、索引优化与数据库恢复方法
数据库·mysql
hero.fei34 分钟前
排查redis出现报错ERR redis temporary failure
数据库·redis·缓存
野犬寒鸦43 分钟前
MySQL复习记录Day01
数据库·后端
ward RINL1 小时前
Spring boot启动原理及相关组件
数据库·spring boot·后端
RisunJan1 小时前
Linux命令-mysqldump(MySQL数据库中备份工具)
linux·数据库·mysql
DolphinDB智臾科技1 小时前
直播回顾 | 物联网时序数据库如何驱动电力场景智能调度?
数据库·物联网·时序数据库
郝学胜-神的一滴1 小时前
解锁CS数据存储的核心逻辑:从结构选择到表单设计的全解析
linux·服务器·数据库·c++·后端·oracle