大云海山数据库(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; |