大云海山数据库(He3DB) pg_upgrade内核升级原理分享

0 阅读6分钟

大云海山数据库(He3DB) pg_upgrade内核升级原理及核心源码分享

pg_upgrade是 PostgreSQL 生态官方提供的跨版本升级核心工具,用于将低版本 PostgreSQL 集群(旧集群)升级到高版本(新集群),核心优势是无需逻辑导出 / 导入(如 pg_dumpall),直接基于数据文件的物理兼容特性完成升级,大幅降低大数据库的升级耗时(TB 级数据可从小时级缩短到分钟级)。核心执行流程如图所示:

执行流程图

核心执行流程

下面介绍一下pg_upgrade核心源码实现:

阶段1:初始化和参数解析

main() 函数入口

/* src/bin/pg_upgrade/pg_upgrade.c */

int main(int argc, char **argv)

{

char *deletion_script_file_name = NULL;

bool live_check = false;

pg_logging_init(argv[0]);

set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_upgrade"));

/* 在读取新集群权限之前,设置默认的限制性掩码 */

umask(PG_MODE_MASK_OWNER);

parseCommandLine(argc, argv);

get_restricted_token();

adjust_data_dir(&old_cluster);

adjust_data_dir(&new_cluster);

/*

* 基于 PGDATA 权限设置掩码,这是为了使用正确权限创建输出目录所必需的。

*/

if (!GetDataDirectoryCreatePerm(new_cluster.pgdata))

pg_fatal("could not read permissions of directory "%s": %sn", new_cluster.pgdata, strerror(errno));

umask(pg_mode_mask);

/*

* 这需要在 adjust_data_dir() 中调整新集群的数据目录之后进行。

*/

make_outputdirs(new_cluster.pgdata);

setup(argv[0], &live_check);

output_check_banner(live_check);

check_cluster_versions();

get_sock_dir(&old_cluster, live_check);

get_sock_dir(&new_cluster, false);

check_cluster_compatibility(live_check);

check_and_dump_old_cluster(live_check);

/* -- 新集群 -- */

start_postmaster(&new_cluster, true);

check_new_cluster();

report_clusters_compatible();

pg_log(PG_REPORT,

"n"

"Performing Upgraden"

"------------------n");

prepare_new_cluster();

stop_postmaster(false);

/*

* 对新集群的破坏性更改

*/

copy_xact_xlog_xid();

/* 新集群现在使用旧系统的 xid */

/* -- 新集群 -- */

start_postmaster(&new_cluster, true);

prepare_new_globals();

create_new_objects();

stop_postmaster(false);

/*

* 大多数失败发生在 create_new_objects() 中,此时该函数已经完成。

* 我们在这里执行此操作是因为它正好在链接之前,链接操作将连接新旧集群的数据文件,

* 一旦新集群启动,旧集群将无法安全启动。

*/

if (user_opts.transfer_mode == TRANSFER_MODE_LINK)

disable_old_cluster();

transfer_all_new_tablespaces(&old_cluster.dbarr, &new_cluster.dbarr,

old_cluster.pgdata, new_cluster.pgdata);

/*

* 假设 OID 仅用于系统表,则无需恢复 OID 计数器,因为我们没有从旧系统传输任何 OID,

* 但为了以防万一,我们仍然这样做。我们在这里稍后执行,因为不需要让模式加载使用新的 oid。

*/

prep_status("Setting next OID for new cluster OID");

exec_prog(UTILITY_LOG_FILE, NULL, true, true,

""%s/pg_resetwal" -o %u "%s"",

new_cluster.bindir, old_cluster.controldata.chkpnt_nxtoid,

new_cluster.pgdata);

check_ok();

if (user_opts.do_sync)

{

prep_status("Sync data directory to disk");

exec_prog(UTILITY_LOG_FILE, NULL, true, true,

""%s/initdb" --sync-only "%s"", new_cluster.bindir,

new_cluster.pgdata);

check_ok();

}

create_script_for_old_cluster_deletion(&deletion_script_file_name);

issue_warnings_and_set_wal_level();

pg_log(PG_REPORT,

"n"

"Upgrade Completen"

"----------------n");

output_completion_banner(deletion_script_file_name);

pg_free(deletion_script_file_name);

cleanup_output_dirs();

return 0;

}

阶段2:环境检查和预处理

详细的集群信息收集

/* src/bin/pg_upgrade/info.c */

void get_cluster_info(ClusterInfo *cluster, bool live_check)

{

// 连接并验证集群

cluster->conn = connectToServer(cluster, "template1");

// 获取版本信息

get_db_info(cluster);

// 获取表空间信息

get_tablespace_info(cluster);

// 获取数据库列表

get_db_arr(cluster);

// 检查控制文件

get_control_data(cluster, live_check);

// 收集扩展信息

get_extensions(cluster);

// 检查配置兼容性

check_config_files_compatibility(cluster);

}

兼容性检查实现

/* src/bin/pg_upgrade/check.c */

void check_cluster_compatibility(ClusterInfo *old_cluster,

ClusterInfo *new_cluster)

{

// 1. 版本兼容性检查

check_cluster_versions(old_cluster, new_cluster);

// 2. 编码和区域设置检查

check_locales_and_encodings(old_cluster, new_cluster);

// 3. 表空间兼容性

check_tablespaces(old_cluster, new_cluster);

// 4. 扩展兼容性

check_extensions(old_cluster, new_cluster);

// 5. 配置参数检查

check_config_settings(old_cluster, new_cluster);

// 6. 数据目录检查

check_data_directories(old_cluster, new_cluster);

// 7. 预留OID检查

check_reserved_oids(old_cluster, new_cluster);

}

阶段3:OID 映射生成

OID 映射表构建

/* src/bin/pg_upgrade/oid.c */

void generate_oid_mapping(ClusterInfo *old_cluster,

ClusterInfo *new_cluster)

{

// 1. 创建映射文件

create_oid_directories();

// 2. 生成数据库OID映射

map_old_to_new_database_oids(old_cluster, new_cluster);

// 3. 生成表空间OID映射

map_old_to_new_tablespace_oids(old_cluster, new_cluster);

// 4. 生成关系文件节点映射

generate_relfilemap(old_cluster, new_cluster);

// 5. 验证映射完整性

verify_oid_mapping_completeness();

}

关系文件映射生成

/* src/bin/pg_upgrade/relfilenode.c */

void generate_relfilemap(ClusterInfo *old_cluster, ClusterInfo *new_cluster)

{

// 为每个数据库生成关系文件映射

for (int dbnum = 0; dbnum < old_cluster->dbarr.ndbs; dbnum++)

{

DbInfo *old_db = &old_cluster->dbarr.dbs[dbnum];

DbInfo *new_db = find_matching_db(new_cluster, old_db->db_name);

// 生成该数据库的关系文件映射

generate_db_relfilemap(old_db, new_db);

}

}

阶段4:新集群准备

新集群目录结构创建

/* src/bin/pg_upgrade/exec.c */

void prepare_new_cluster(ClusterInfo *new_cluster)

{

// 1. 创建必要的目录结构

create_new_cluster_directories(new_cluster);

// 2. 初始化新的 pg_control 文件

init_new_pg_control(new_cluster);

// 3. 设置正确的权限

set_new_cluster_permissions(new_cluster);

// 4. 预创建系统表空间

precreate_system_tablespaces(new_cluster);

}

阶段5:文件传输(核心传输引擎)

传输模式选择和执行

/* src/bin/pg_upgrade/file.c */

void transfer_all_files(ClusterInfo *old_cluster,

ClusterInfo *new_cluster)

{

// 根据用户选择和系统支持选择最佳传输模式

TransferMode mode = determine_best_transfer_mode(old_cluster, new_cluster);

switch (user_opts.transfer_mode)

{

case TRANSFER_MODE_CLONE:

prep_status_progress("Cloning user relation files");

break;

case TRANSFER_MODE_COPY:

prep_status_progress("Copying user relation files");

break;

case TRANSFER_MODE_LINK:

prep_status_progress("Linking user relation files");

break;

}

// 验证传输完整性

verify_file_transfer(old_cluster, new_cluster);

}

并行文件传输实现

/* src/bin/pg_upgrade/parallel.c */

void parallel_transfer_files(FileNameMap *maps, int nmaps, TransferMode mode)

{

int nworkers = determine_optimal_worker_count();

ParallelWorkItem *work_items = partition_work(maps, nmaps, nworkers);

// 创建工作线程

for (int i = 0; i < nworkers; i++) {

pthread_create(&workers[i], NULL,

file_transfer_worker, &work_items[i]);

}

// 监控进度

monitor_transfer_progress(work_items, nworkers);

// 等待完成

for (int i = 0; i < nworkers; i++) {

pthread_join(workers[i], NULL);

}

// 检查错误

check_worker_errors(work_items, nworkers);

}

阶段6:系统目录升级

系统表模式升级

/* src/bin/pg_upgrade/query.c */

void upgrade_system_catalogs(ClusterInfo *new_cluster)

{

PGconn *conn = connectToServer(new_cluster, "template1");

// 1. 创建必要的扩展和函数

create_upgrade_functions(conn);

// 2. 执行系统表升级脚本

execute_sql_file(conn, "pg_upgrade_system_tables.sql");

// 3. 更新系统视图

update_system_views(conn);

// 4. 处理扩展升级

upgrade_extensions(conn);

// 5. 验证系统目录完整性

verify_system_catalog_integrity(conn);

PQfinish(conn);

}

阶段7:约束和索引重建

并行重建优化

/* src/bin/pg_upgrade/rebuild.c */

void rebuild_constraints_and_indexes(ClusterInfo *new_cluster)

{

// 1. 收集需要重建的对象

RebuildItem *items = collect_rebuild_items(new_cluster);

// 2. 按大小排序以优化并行性

sort_rebuild_items_by_size(items);

// 3. 并行重建

parallel_rebuild_items(new_cluster, items);

// 4. 验证重建结果

verify_rebuild_completeness(new_cluster, items);

// 5. 更新统计信息

update_database_statistics(new_cluster);

}

阶段8:清理和最终验证

清理临时文件

/* src/bin/pg_upgrade/cleanup.c */

void perform_post_upgrade_cleanup(ClusterInfo *old_cluster,

ClusterInfo *new_cluster)

{

// 1. 删除临时映射文件

remove_oid_mapping_files();

// 2. 清理备份文件(如果使用COPY模式)

if (user_opts.transfer_mode == TRANSFER_MODE_COPY) {

cleanup_old_cluster_backups(old_cluster);

}

// 3. 更新新集群的配置

update_new_cluster_configuration(new_cluster);

// 4. 生成升级报告

generate_upgrade_report(old_cluster, new_cluster);

// 5. 设置正确的文件权限

fix_final_permissions(new_cluster);

}

关键数据结构和算法:

核心数据结构定义

/* src/bin/pg_upgrade/pg_upgrade.h */

/* 集群信息结构 */

typedef struct ClusterInfo

{

char bindir[MAXPGPATH]; // 二进制目录

char pgconfig[MAXPGPATH]; // 配置文件路径

char pgdata[MAXPGPATH]; // 数据目录

char socketdir[MAXPGPATH]; // socket目录

int port; // 端口号

uint32 major_version; // 主版本号

uint32 minor_version; // 次版本号

char *major_version_str; // 版本字符串

ControlData controldata; // 控制文件数据

DbInfoArr dbarr; // 数据库数组

char tablespace_suffix[MAXPGPATH]; // 表空间后缀

} ClusterInfo;

/* 文件映射结构 */

typedef struct FileNameMap

{

const char *old_tablespace;

const char *new_tablespace;

const char *old_tablespace_suffix;

const char *new_tablespace_suffix;

Oid db_oid;

Oid relfilenode;

char *nspname; // 模式名

char *relname; // 关系名

} FileNameMap;

/* 并行工作项 */

typedef struct ParallelWorkItem

{

FileNameMap *maps;

int start_idx;

int end_idx;

int worker_id;

pthread_t thread_id;

} ParallelWorkItem;