diff --git a/CN/modules/ROOT/nav.adoc b/CN/modules/ROOT/nav.adoc index 66bc54e6..97b5afa3 100644 --- a/CN/modules/ROOT/nav.adoc +++ b/CN/modules/ROOT/nav.adoc @@ -117,7 +117,8 @@ ** xref:master/getting-started/daily_maintenance.adoc[日常维护] ** xref:master/operation_guide.adoc[运维管理指南] * 数据迁移 -** xref:master/migration_guide.adoc[迁移指南] +** xref:master/migration_guide.adoc[迁移指南] +** xref:master/pg_upgrade_guide.adoc[PostgreSQL 升级指南] * IvorySQL开发者 ** xref:master/contribution/contribution_guide.adoc[贡献指南] ** xref:master/developer_guide.adoc[开发者指南] diff --git a/CN/modules/ROOT/pages/master/pg_upgrade_guide.adoc b/CN/modules/ROOT/pages/master/pg_upgrade_guide.adoc new file mode 100644 index 00000000..68c150d0 --- /dev/null +++ b/CN/modules/ROOT/pages/master/pg_upgrade_guide.adoc @@ -0,0 +1,333 @@ +:sectnums: +:sectnumlevels: 5 + += PostgreSQL 至 IvorySQL 升级指南 + +== 概述 + +IvorySQL 基于 PostgreSQL 最新内核构建,并在此基础上提供 Oracle 兼容能力。对于已经在使用 PostgreSQL 的业务,可以按照本指南将数据平滑升级到 IvorySQL,并按需启用 Oracle 兼容模式。 + +本文介绍三种逻辑迁移路径,并给出升级前后的检查清单: + +[cols="2,2,3,2"] +|=== +|路径 |适用场景 |优点 |限制 + +|pg_dump / pg_restore 逻辑迁移 +|跨版本、跨平台迁移,数据量中小 +|最安全,兼容性最好,支持版本跨度大 +|停机窗口取决于导出导入耗时 + +|pg_dumpall 全库迁移 +|整实例(含全局对象)一次性搬迁 +|保留角色、表空间等全局对象 +|同样需要停机窗口 + +|逻辑复制 +|追求最小停机时间的在线迁移 +|源库持续可用,可随时切换 +|需 PostgreSQL 10+,配置相对复杂 +|=== + +[NOTE] +如果希望采用物理升级(就地升级、停机时间更短),IvorySQL 也支持使用 `pg_upgrade` 将原生 PostgreSQL 集群升级到 IvorySQL。该路径必须使用**目标 IvorySQL 版本自带的** `pg_upgrade`,并指定 `-g`(`--using-ora-pg`)参数以表示源端为原生 PostgreSQL。升级前应先执行兼容性检查(`-c`),升级后按业务需要完成验证(统计信息、扩展、Oracle 兼容行为等)。具体步骤、参数与注意事项请参阅 xref:master/operation_guide.adoc[运维管理指南]。 + +本文聚焦上述三种逻辑迁移路径,适用于跨版本、跨平台以及需要精细控制迁移对象范围的场景;物理升级路径请以运维管理指南为准。 + +== 升级前准备 + +=== 确认 IvorySQL 与 PostgreSQL 版本 + +IvorySQL 5.x 基于 PostgreSQL 18。可通过以下命令确认两端版本: + +[source,shell] +---- +# 源端 PostgreSQL +psql -U postgres -c "SELECT version();" + +# 目标端 IvorySQL +psql -U postgres -p 5432 -c "SELECT version();" +---- + +=== 安装 IvorySQL + +参考 xref:master/installation_guide.adoc[安装指南] 完成安装。初始化实例时,可按业务需要选择兼容模式与大小写不敏感(CI)选项: + +[source,shell] +---- +/usr/local/ivorysql/ivorysql-5/bin/initdb -D /data/ivorysql -U postgres +---- + +初始化完成后,确认双端口监听与各自的兼容模式: + +[source,shell] +---- +psql -U postgres -p 5432 -c "SHOW ivorysql.compatible_mode;" +psql -U postgres -p 1521 -c "SHOW ivorysql.compatible_mode;" +---- + +IvorySQL 同一实例同时监听 PostgreSQL 模式端口(5432)与 Oracle 兼容模式端口(1521)。兼容行为由**连接端口**决定:`ivorysql.compatible_mode` 由系统根据端口自动设置,不允许在 `ivorysql.conf` 中手工配置。升级与校验阶段先通过 5432 端口验证 PostgreSQL 行为,割接后再切换到 1521 端口使用 Oracle 兼容特性。 + +=== 源端检查清单 + +* 记录源库版本、`encoding`、`lc_collate`/`lc_ctype`,目标端 initdb 时保持一致。 +* 使用 `pg_dump --schema-only` 提前演练建表过程,标记依赖 Oracle 兼容特性才可创建的对象。 +* 检查是否存在使用 `EMPTY_STRING` 与 `NULL` 语义差异的数据:PostgreSQL 中空字符串 `''` 与 `NULL` 不同,而 IvorySQL 的 Oracle 兼容模式遵循 Oracle 语义,将空字符串视为 `NULL`。涉及此语义的查询结果会发生变化,需要业务侧评估。 +* 统计大表行数,作为迁移后的数据校验基准。 + +== 路径一:pg_dump / pg_restore 逻辑迁移 + +=== 迁移全局对象与表空间(先于数据恢复) + +`pg_dump` 不包含角色与表空间,目标端必须先就绪这些全局对象,再恢复数据:使用自定义表空间的表在恢复时要求对应表空间已存在,否则 `pg_restore` 直接失败。 + +[source,shell] +---- +# 源端导出全局对象 +pg_dumpall -U postgres --globals-only -f globals.sql + +# 目标端导入前先编辑 globals.sql: +# 1) 去除超级用户相关语句 +# 2) 核对 CREATE TABLESPACE 语句中的目录路径,并提前在 IvorySQL 主机上创建这些目录 +# 3) 如需跳过已存在的角色等冲突,可删除对应 CREATE ROLE 语句 +# (-v ON_ERROR_STOP=1 遇错即停,确保表空间等关键对象创建成功) +psql -U postgres -p 5432 -v ON_ERROR_STOP=1 -f globals.sql +---- + +[NOTE] +`psql -f` 默认在遇到 SQL 错误时不会中止执行,而是继续处理后续语句并可能返回退出码 0。若表空间目录缺失,`CREATE TABLESPACE` 会报错但脚本继续执行,最终表可能被创建到默认表空间而非预期位置。使用 `-v ON_ERROR_STOP=1` 可在遇到首个错误时立即停止,便于发现并修复问题。 + +若计划将所有表恢复到默认表空间(不保留源端表空间布局),可跳过表空间相关检查,并在下一步恢复时使用 `--no-tablespaces`。 + +=== 导出源库 + +[IMPORTANT] +逻辑导出基于某一时刻的一致性快照。执行最终导出前必须**停止源端业务写入**,并保持停止直到数据校验完成、业务切换到 IvorySQL;否则导出快照之后的写入不会包含在迁移数据中。 + +在源端 PostgreSQL 上导出指定数据库(建议使用与源库同大版本的 `pg_dump`): + +[source,shell] +---- +pg_dump -U postgres -Fc -d appdb -f appdb.dump +---- + +如需排除某些对象,可使用 `-N`/`-n` 与 `-T`/`-t` 选项精细控制。 + +=== 恢复到 IvorySQL + +将转储文件恢复到 IvorySQL 实例(通过 5432 端口,保持 PostgreSQL 行为): + +[source,shell] +---- +# 先创建目标数据库 +psql -U postgres -p 5432 -c "CREATE DATABASE appdb;" + +# 恢复数据(默认将所有对象放入默认表空间) +pg_restore -U postgres -p 5432 -d appdb --no-owner --no-privileges --no-tablespaces appdb.dump +---- + +`--no-owner` 与 `--no-privileges` 用于跳过源端的角色与授权信息;如目标端已重建同名角色,可去掉这两个选项。`--no-tablespaces` 忽略转储中的表空间设置,将所有对象放入默认表空间;如需保留源端表空间布局,请确认前置步骤中的表空间已在目标端创建,再省略该选项。 + +== 路径二:pg_dumpall 全库迁移 + +如果源实例规模不大,或需要连同全局对象一起搬迁,可直接使用 `pg_dumpall`: + +[source,shell] +---- +# 源端全库导出(纯 SQL 文本) +pg_dumpall -U postgres -f full_cluster.sql +---- + +[IMPORTANT] +最终导出前必须停止源端业务写入,并保持停止直到割接完成;导出快照之后的写入不会包含在转储文件中。 + +=== 导入前处理角色冲突 + +`pg_dumpall` 的转储文件开头是全局对象定义,其中每个角色都会生成一条 `CREATE ROLE` 语句,包括源端的 `postgres` 角色。IvorySQL 目标实例在 `initdb` 时已经创建了 `postgres` 角色,因此直接导入会在这条语句处失败: + +[literal] +---- +ERROR: role "postgres" already exists +---- + +配合 `-v ON_ERROR_STOP=1` 时,`psql` 会在该错误处立即退出(退出码 3),后续的建库与数据恢复均不会执行。导入前请先检查转储文件,并删除或注释掉目标端已存在角色的 `CREATE ROLE` 语句: + +[source,shell] +---- +# 1. 列出转储中的全部角色创建语句(位于文件开头的全局对象部分) +grep -n '^CREATE ROLE' full_cluster.sql + +# 2. 列出目标端已存在的角色 +psql -U postgres -p 5432 -c '\du' + +# 3. 对比上述结果,删除或注释目标端已有角色对应的 CREATE ROLE 行 +# 例如目标端必然存在 postgres 角色: +sed -i 's/^CREATE ROLE postgres;/-- CREATE ROLE postgres;/' full_cluster.sql +---- + +只需处理冲突的 `CREATE ROLE` 语句:紧随其后的 `ALTER ROLE` 仍会执行,用于对齐角色属性,请保留。若目标端已存在多个源端角色,按同样方式逐一注释。 + +=== 导入 + +[source,shell] +---- +# 目标端导入(-v ON_ERROR_STOP=1 遇错即停,避免错误被静默跳过) +psql -U postgres -p 5432 -v ON_ERROR_STOP=1 -f full_cluster.sql +---- + +[NOTE] +`pg_dumpall` 生成的纯文本 SQL 不支持并行恢复,大实例建议优先使用路径一。若源端使用了自定义表空间,请先在 IvorySQL 主机创建对应目录。`psql -f` 默认遇错不中止,`CREATE TABLESPACE` 失败后脚本会继续,相关表最终可能落在默认表空间而非预期位置;`-v ON_ERROR_STOP=1` 会让此类错误立即暴露,请在完成上述角色冲突处理后保留该选项。 + +== 路径三:逻辑复制在线迁移 + +对于不允许长时间停机的业务,可以使用逻辑复制保持源库与 IvorySQL 的数据同步,最后短暂切换。 + +=== 源端配置 + +在源端 PostgreSQL 的 `postgresql.conf` 中调整参数并重启: + +[source,ini] +---- +wal_level = logical +max_replication_slots = 10 +max_wal_senders = 10 +---- + +确认 `pg_hba.conf` 允许 IvorySQL 所在主机以复制方式连接。 + +=== 目标端预建结构 + +在 IvorySQL 上预先创建与源端一致的表结构(可由 `pg_dump --schema-only` 生成)。建议目标端表与源端保持相同的复制标识设置,以提升增量应用的执行效率。 + +=== 源端设置复制标识 + +逻辑复制由 publisher 端按表的复制标识查找待复制的行:无主键的表默认没有复制标识,其 UPDATE/DELETE 无法发布。请在源端为所有无主键的待复制表设置复制标识: + +[source,sql] +---- +-- 有主键的表无需额外操作(主键即复制标识) +-- 无主键的表在源端设置 REPLICA IDENTITY FULL +ALTER TABLE app_table REPLICA IDENTITY FULL; + +-- 检查源端缺少复制标识的用户表(relreplident = 'd' 且无主键) +SELECT n.nspname AS schema_name, c.relname AS table_name +FROM pg_class c +JOIN pg_namespace n ON n.oid = c.relnamespace +WHERE c.relkind = 'r' + AND c.relreplident = 'd' + AND NOT EXISTS ( + SELECT 1 FROM pg_index i + WHERE i.indrelid = c.oid AND i.indisprimary + ) + AND n.nspname NOT IN ('pg_catalog', 'information_schema'); +---- + +=== 创建发布与订阅 + +[source,sql] +---- +-- 源端创建发布 +CREATE PUBLICATION app_pub FOR ALL TABLES; + +-- IvorySQL 端创建订阅 +CREATE SUBSCRIPTION app_sub + CONNECTION 'host=src_host port=5432 dbname=appdb user=replica password=***' + PUBLICATION app_pub; +---- + +订阅创建后,IvorySQL 会先完成全量同步,随后持续应用增量变更。 + +=== 割接切换 + +1. 停止源端业务写入,此后源库不再产生新的变更。 + +2. 确认初始同步完成。在 IvorySQL 端检查所有订阅表的状态均为 `r`(进入正常复制): + +[source,sql] +---- +-- IvorySQL 端执行 +SELECT s.subname AS subscription, + r.srrelid::regclass AS table_name, + r.srsubstate +FROM pg_subscription_rel r +JOIN pg_subscription s ON s.oid = r.srsubid +ORDER BY 1, 2; + +-- srsubstate:'d' 初始拷贝进行中;'s' 已同步数据; +-- 'r' 拷贝完成并进入正常复制(割接前所有表必须为 'r') +---- + +3. 确认停写后的最终增量已全部应用:停写后源端不再产生新事务,但 CHECKPOINT 等内部操作会使 WAL 位置继续前进,因此不应要求 LSN 精确相等,而应确认订阅端已追平停写时的位置且不再前进。 + +[source,sql] +---- +-- 源端:停写后执行 CHECKPOINT,将已提交事务全部刷入 WAL +CHECKPOINT; + +-- 源端:记录当前 WAL 位置 L1 +SELECT pg_current_wal_lsn() AS source_lsn; + +-- IvorySQL 端:等待 latest_end_lsn >= L1(追平或超过即可) +-- 并观察一段时间 latest_end_lsn 不再前进,说明增量已全部应用 +SELECT subname, received_lsn, latest_end_lsn, latest_end_time +FROM pg_stat_subscription; +---- + +4. 对关键表执行行数与抽样校验(见"升级后验证")。 + +5. 校验通过后,在 IvorySQL 端停止订阅:`ALTER SUBSCRIPTION app_sub DISABLE;` + +6. 将业务连接串切换到 IvorySQL,按需使用 1521 端口启用 Oracle 兼容行为。 + +7. 观察一段时间稳定后,删除订阅并下线源库。 + +== 升级后验证 + +=== 基础校验 + +[source,sql] +---- +-- 对比关键表行数(源端与目标端各执行一次) +SELECT count(*) FROM app_table; + +-- 检查序列当前值,避免割接后主键冲突 +SELECT last_value FROM app_id_seq; +---- + +逻辑复制不复制序列值,割接前必须手动同步序列: + +[source,shell] +---- +# 源端导出序列状态(覆盖除系统 schema 外的所有 schema,生成 schema 限定的序列名) +psql -U postgres -d appdb -Atc \ + "SELECT 'SELECT setval(' || quote_literal(format('%I.%I', schemaname, sequencename)) || ',' || last_value || ');' \ + FROM pg_sequences \ + WHERE last_value IS NOT NULL \ + AND schemaname NOT IN ('pg_catalog', 'information_schema')" > sync_sequences.sql + +# IvorySQL 端执行 +psql -U postgres -p 5432 -d appdb -f sync_sequences.sql +---- + +脚本说明:`format('%I.%I', ...)` 生成带引号限定的完整序列名,多 schema 场景同样适用;`last_value IS NOT NULL` 过滤掉尚未使用的序列(其当前值即起始值,无需同步)。 + +=== 启用 Oracle 兼容模式 + +数据校验通过后,通过 1521 端口连接验证 Oracle 兼容行为: + +[source,sql] +---- +-- 确认兼容模式 +SHOW ivorysql.compatible_mode; + +-- 验证 Oracle 风格对象 +SELECT SYSDATE FROM DUAL; +SELECT NVL(NULL, 'N/A') FROM DUAL; +---- + +== 常见问题 + +* **空字符串语义差异**:Oracle 兼容模式下 `'' = NULL`。若业务依赖 PostgreSQL 中空字符串与 NULL 的区别,需改造相关查询或数据。 +* **标识符大小写**:Oracle 兼容模式遵循 Oracle 的大小写折叠规则,未加双引号的标识符视为大写。从 PostgreSQL 迁移的小写对象名在 Oracle 模式下仍可通过双引号精确访问,建议在切换前统一评估命名规范。 +* **扩展兼容性**:源端使用的 PostgreSQL 扩展需确认 IvorySQL 是否已适配,适配列表见 xref:master/ecosystem_components/ecosystem_overview.adoc[生态组件适配]。 +* **只读对象**:`--no-owner` 恢复后对象属主为执行恢复的用户,如需保持原有权限模型,请在目标端提前创建角色。 diff --git a/EN/modules/ROOT/nav.adoc b/EN/modules/ROOT/nav.adoc index b2a82c50..acaacaef 100644 --- a/EN/modules/ROOT/nav.adoc +++ b/EN/modules/ROOT/nav.adoc @@ -117,6 +117,7 @@ ** xref:master/operation_guide.adoc[Operation Management] * Data Migration ** xref:master/migration_guide.adoc[Migration] +** xref:master/pg_upgrade_guide.adoc[Upgrading from PostgreSQL] * IvorySQL Developers ** xref:master/contribution/contribution_guide.adoc[Contribution Guide] ** xref:master/developer_guide.adoc[Developer] diff --git a/EN/modules/ROOT/pages/master/pg_upgrade_guide.adoc b/EN/modules/ROOT/pages/master/pg_upgrade_guide.adoc new file mode 100644 index 00000000..bf1e686a --- /dev/null +++ b/EN/modules/ROOT/pages/master/pg_upgrade_guide.adoc @@ -0,0 +1,340 @@ +:sectnums: +:sectnumlevels: 5 + += Upgrading from PostgreSQL to IvorySQL + +== Overview + +IvorySQL is built on the latest PostgreSQL kernel and adds Oracle compatibility on top of it. If you are already running PostgreSQL, this guide helps you upgrade your data to IvorySQL smoothly and enable Oracle-compatible mode as needed. + +This guide describes three logical migration paths and provides pre/post-migration checklists: + +[cols="2,2,3,2"] +|=== +|Path |Use case |Advantages |Limitations + +|pg_dump / pg_restore logical migration +|Cross-version, cross-platform migration with small to medium data volumes +|Safest, best compatibility, supports large version gaps +|Downtime depends on export/import duration + +|pg_dumpall full-cluster migration +|Moving an entire instance (including global objects) at once +|Preserves roles, tablespaces, and other global objects +|Also requires a downtime window + +|Logical replication +|Online migration with minimal downtime +|Source stays available; switch at any time +|Requires PostgreSQL 10+, more complex configuration +|=== + +[NOTE] +If you prefer a physical (in-place) upgrade with a shorter downtime window, IvorySQL also supports upgrading a native PostgreSQL cluster to IvorySQL with `pg_upgrade`. This path requires the `pg_upgrade` binary shipped with the **target IvorySQL version** and the `-g` (`--using-ora-pg`) option to indicate that the source is a native PostgreSQL cluster. Run the compatibility check (`-c`) before upgrading, and validate the result afterwards as required by your workload (statistics, extensions, Oracle-compatible behavior, and so on). For the detailed steps, options, and caveats, see xref:master/operation_guide.adoc[Operation and Maintenance Guide]. + +This guide focuses on the three logical migration paths above, which suit cross-version, cross-platform migrations and scenarios that need fine-grained control over the objects being migrated. For the physical upgrade path, refer to the operation guide. + +== Pre-upgrade preparation + +=== Confirm IvorySQL and PostgreSQL versions + +IvorySQL 5.x is based on PostgreSQL 18. Confirm both versions: + +[source,shell] +---- +# Source PostgreSQL +psql -U postgres -c "SELECT version();" + +# Target IvorySQL +psql -U postgres -p 5432 -c "SELECT version();" +---- + +=== Install IvorySQL + +Install IvorySQL by following the xref:master/installation_guide.adoc[installation guide]. When initializing the instance, choose the compatibility mode and case-insensitive (CI) options according to your business needs: + +[source,shell] +---- +/usr/local/ivorysql/ivorysql-5/bin/initdb -D /data/ivorysql -U postgres +---- + +After initialization, verify the dual-port listeners and their compatibility modes: + +[source,shell] +---- +psql -U postgres -p 5432 -c "SHOW ivorysql.compatible_mode;" +psql -U postgres -p 1521 -c "SHOW ivorysql.compatible_mode;" +---- + +IvorySQL listens on both the PostgreSQL-mode port (5432) and the Oracle-compatible-mode port (1521) in a single instance. Compatibility behavior is determined by the **connection port**: `ivorysql.compatible_mode` is set automatically by the system according to the port and cannot be configured manually in `ivorysql.conf`. During the upgrade you can verify PostgreSQL behavior through port 5432 first, then switch to port 1521 to use Oracle compatibility features after cutover. + +=== Source-side checklist + +* Record the source version, `encoding`, and `lc_collate`/`lc_ctype`, and keep them consistent when running initdb on the target. +* Rehearse table creation in advance with `pg_dump --schema-only` and flag any objects that require Oracle compatibility features. +* Check whether your data relies on the difference between empty strings and `NULL`: in PostgreSQL, `''` and `NULL` are distinct, while IvorySQL's Oracle-compatible mode follows Oracle semantics and treats empty strings as `NULL`. Query results that depend on this semantic change and need business-side evaluation. +* Capture row counts of large tables as the baseline for post-migration validation. + +== Path 1: pg_dump / pg_restore logical migration + +=== Migrate global objects and tablespaces (before data restore) + +`pg_dump` does not include roles or tablespaces. The target must have these global objects ready **before** restoring data: tables that use custom tablespaces fail during restore unless the corresponding tablespaces already exist. + +[source,shell] +---- +# Export globals from the source +pg_dumpall -U postgres --globals-only -f globals.sql + +# Before importing on the target, edit globals.sql: +# 1) Remove superuser-related statements +# 2) Verify the directory paths in CREATE TABLESPACE statements +# and create these directories on the IvorySQL host in advance +# 3) To skip conflicts such as pre-existing roles, remove the corresponding +# CREATE ROLE statements (-v ON_ERROR_STOP=1 stops at the first error, +# ensuring critical objects like tablespaces are created successfully) +psql -U postgres -p 5432 -v ON_ERROR_STOP=1 -f globals.sql +---- + +[NOTE] +By default, `psql -f` does not stop on SQL errors: it continues with subsequent statements and may return exit code 0. If a tablespace directory is missing, `CREATE TABLESPACE` fails but the script keeps running, and affected tables may end up in the default tablespace instead of the intended one. Adding `-v ON_ERROR_STOP=1` stops at the first error so problems are surfaced immediately. + +If you plan to restore all tables into the default tablespace (without preserving the source tablespace layout), you can skip the tablespace checks and use `--no-tablespaces` in the restore step below. + +=== Export the source database + +[IMPORTANT] +Logical dumps are based on a consistent snapshot taken at a point in time. You must **stop writes on the source** before the final export and keep them stopped until validation completes and the business switches to IvorySQL; otherwise writes made after the snapshot are lost. + +Dump the database on the source PostgreSQL (prefer a `pg_dump` of the same major version as the source): + +[source,shell] +---- +pg_dump -U postgres -Fc -d appdb -f appdb.dump +---- + +Use the `-N`/`-n` and `-T`/`-t` options to exclude specific schemas or tables. + +=== Restore into IvorySQL + +Restore the dump into the IvorySQL instance (via port 5432 to keep PostgreSQL behavior): + +[source,shell] +---- +# Create the target database first +psql -U postgres -p 5432 -c "CREATE DATABASE appdb;" + +# Restore the data (all objects go into the default tablespace) +pg_restore -U postgres -p 5432 -d appdb --no-owner --no-privileges --no-tablespaces appdb.dump +---- + +`--no-owner` and `--no-privileges` skip source-side ownership and grants. If you recreated the same roles on the target, you can drop these options. `--no-tablespaces` ignores tablespace settings in the dump and places all objects in the default tablespace; to preserve the source tablespace layout, confirm the tablespaces were created in the preparation step and omit this option. + +== Path 2: pg_dumpall full-cluster migration + +If the source instance is small, or you want to move global objects together with the data, use `pg_dumpall` directly: + +[source,shell] +---- +# Full-cluster export as plain SQL +pg_dumpall -U postgres -f full_cluster.sql +---- + +[IMPORTANT] +Stop writes on the source before the final export and keep them stopped until cutover completes; writes made after the snapshot are not included in the dump. + +=== Resolve role conflicts before import + +The beginning of a `pg_dumpall` dump contains global object definitions, and every role produces a `CREATE ROLE` statement, including the source `postgres` role. The IvorySQL target already has a `postgres` role created by `initdb`, so importing directly fails at this statement: + +[literal] +---- +ERROR: role "postgres" already exists +---- + +With `-v ON_ERROR_STOP=1`, `psql` exits immediately at this error (exit code 3), and neither the application database nor the data is restored. Inspect the dump and remove or comment out `CREATE ROLE` statements for roles that already exist on the target before importing: + +[source,shell] +---- +# 1. List all role creation statements in the dump +# (they are in the global objects section at the top of the file) +grep -n '^CREATE ROLE' full_cluster.sql + +# 2. List roles that already exist on the target +psql -U postgres -p 5432 -c '\du' + +# 3. Compare the two lists and delete or comment out the CREATE ROLE lines +# for roles that already exist; the postgres role always exists: +sed -i 's/^CREATE ROLE postgres;/-- CREATE ROLE postgres;/' full_cluster.sql +---- + +Only the conflicting `CREATE ROLE` statements need to be removed: the following `ALTER ROLE` statement still runs and aligns role attributes, so keep it. Repeat the same approach for any other source roles that already exist on the target. + +=== Import + +[source,shell] +---- +# Import on the target (-v ON_ERROR_STOP=1 stops at the first error so +# failures are not silently skipped) +psql -U postgres -p 5432 -v ON_ERROR_STOP=1 -f full_cluster.sql +---- + +[NOTE] +`pg_dumpall` produces plain-text SQL and does not support parallel restore. Prefer Path 1 for large instances. If the source uses custom tablespaces, create the corresponding directories on the IvorySQL host first. Note that `psql -f` does not stop on errors by default: a failed `CREATE TABLESPACE` is followed by remaining statements, and affected tables may end up in the default tablespace. `-v ON_ERROR_STOP=1` surfaces such errors immediately, so keep this option after resolving the role conflicts described above. + +== Path 3: logical replication online migration + +For businesses that cannot tolerate long downtime, use logical replication to keep the source and IvorySQL in sync, then cut over within a short window. + +=== Configure the source + +Adjust `postgresql.conf` on the source PostgreSQL and restart: + +[source,ini] +---- +wal_level = logical +max_replication_slots = 10 +max_wal_senders = 10 +---- + +Make sure `pg_hba.conf` allows the IvorySQL host to connect for replication. + +=== Pre-create the structure on the target + +Create the same table structures on IvorySQL in advance (you can generate them with `pg_dump --schema-only`). It is recommended that target tables keep the same replica identity settings as the source to improve incremental apply performance. + +=== Set replica identity on the source + +Logical replication locates rows to publish on the publisher side using the table's replica identity: tables without a primary key have no replica identity by default, and their UPDATE/DELETE operations cannot be published. Set the replica identity on the source for every primary-key-less table you plan to replicate: + +[source,sql] +---- +-- Tables with primary keys need no extra step (the primary key is the replica identity) +-- Tables without primary keys: set REPLICA IDENTITY FULL on the source +ALTER TABLE app_table REPLICA IDENTITY FULL; + +-- Check user tables on the source that lack a replica identity +-- (relreplident = 'd' and no primary key) +SELECT n.nspname AS schema_name, c.relname AS table_name +FROM pg_class c +JOIN pg_namespace n ON n.oid = c.relnamespace +WHERE c.relkind = 'r' + AND c.relreplident = 'd' + AND NOT EXISTS ( + SELECT 1 FROM pg_index i + WHERE i.indrelid = c.oid AND i.indisprimary + ) + AND n.nspname NOT IN ('pg_catalog', 'information_schema'); +---- + +=== Create publication and subscription + +[source,sql] +---- +-- Create a publication on the source +CREATE PUBLICATION app_pub FOR ALL TABLES; + +-- Create a subscription on IvorySQL +CREATE SUBSCRIPTION app_sub + CONNECTION 'host=src_host port=5432 dbname=appdb user=replica password=***' + PUBLICATION app_pub; +---- + +After the subscription is created, IvorySQL performs the initial sync and then continuously applies incremental changes. + +=== Cutover + +1. Stop writes on the source; the source now produces no further changes. + +2. Confirm the initial sync is complete. On IvorySQL, verify that every subscribed table has state `r` (normal replication): + +[source,sql] +---- +-- Run on IvorySQL +SELECT s.subname AS subscription, + r.srrelid::regclass AS table_name, + r.srsubstate +FROM pg_subscription_rel r +JOIN pg_subscription s ON s.oid = r.srsubid +ORDER BY 1, 2; + +-- srsubstate: 'd' data copy in progress; 's' data synchronized; +-- 'r' copy finished and in normal replication (all tables must be 'r' before cutover) +---- + +3. Confirm all final changes after the write stop have been applied: after writes stop, the source produces no new transactions, but internal operations such as CHECKPOINT still advance the WAL position. Therefore exact equality of LSNs should not be required; instead, confirm the subscriber has caught up to the write-stop position and no longer advances. + +[source,sql] +---- +-- Source: run CHECKPOINT after stopping writes to flush all committed transactions to WAL +CHECKPOINT; + +-- Source: record the current WAL position L1 +SELECT pg_current_wal_lsn() AS source_lsn; + +-- IvorySQL: wait until latest_end_lsn >= L1 (caught up or beyond) +-- and observe that latest_end_lsn no longer advances for a while, +-- which confirms all incremental changes have been applied +SELECT subname, received_lsn, latest_end_lsn, latest_end_time +FROM pg_stat_subscription; +---- + +4. Validate key tables with row counts and sampling checks (see "Post-upgrade validation"). + +5. Once validation passes, disable the subscription on IvorySQL: `ALTER SUBSCRIPTION app_sub DISABLE;` + +6. Point the application connection string to IvorySQL, using port 1521 if Oracle-compatible behavior is desired. + +7. After a stability observation period, drop the subscription and decommission the source. + +== Post-upgrade validation + +=== Basic checks + +[source,sql] +---- +-- Compare row counts of key tables (run on both source and target) +SELECT count(*) FROM app_table; + +-- Check sequence values to avoid primary-key conflicts after cutover +SELECT last_value FROM app_id_seq; +---- + +Logical replication does not replicate sequence values. Synchronize sequences manually before cutover: + +[source,shell] +---- +# Export sequence state from the source +# (covers all non-system schemas with schema-qualified sequence names) +psql -U postgres -d appdb -Atc \ + "SELECT 'SELECT setval(' || quote_literal(format('%I.%I', schemaname, sequencename)) || ',' || last_value || ');' \ + FROM pg_sequences \ + WHERE last_value IS NOT NULL \ + AND schemaname NOT IN ('pg_catalog', 'information_schema')" > sync_sequences.sql + +# Execute on IvorySQL +psql -U postgres -p 5432 -d appdb -f sync_sequences.sql +---- + +Script notes: `format('%I.%I', ...)` produces fully qualified, quoted sequence names that work in multi-schema scenarios; `last_value IS NOT NULL` excludes sequences that have never been used (their current value equals the start value, so no sync is needed). + +=== Enable Oracle-compatible mode + +After validation, connect through port 1521 and verify Oracle-compatible behavior: + +[source,sql] +---- +-- Confirm the compatibility mode +SHOW ivorysql.compatible_mode; + +-- Verify Oracle-style objects +SELECT SYSDATE FROM DUAL; +SELECT NVL(NULL, 'N/A') FROM DUAL; +---- + +== FAQ + +* **Empty-string semantics**: in Oracle-compatible mode `''` equals `NULL`. If your business depends on the PostgreSQL distinction between empty strings and NULL, adjust the related queries or data. +* **Identifier casing**: Oracle-compatible mode follows Oracle's case-folding rules; unquoted identifiers are treated as uppercase. Lowercase object names migrated from PostgreSQL remain accessible with double quotes, but review naming conventions before switching. +* **Extension compatibility**: confirm that PostgreSQL extensions used on the source are adapted for IvorySQL. See the xref:master/ecosystem_components/ecosystem_overview.adoc[ecosystem component list]. +* **Ownership**: after restoring with `--no-owner`, objects belong to the user who ran the restore. Create the roles on the target in advance to preserve the original permission model.