From 39d795043cc2d549f48b7953a2a7da5607d65766 Mon Sep 17 00:00:00 2001 From: SqlRush Date: Thu, 17 Sep 2026 10:53:36 +0800 Subject: [PATCH 1/2] docs(mvp): add Linux single-host four-instance quick start --- .../01-linux-four-node-deployment.md | 4 +- docs/mvp/v0.130.0-mvp.1/README.md | 4 + .../quickstart-linux-single-host.md | 157 ++++++++++++++++++ .../v0.130.0-mvp.1/quickstart-single-host.pl | 133 +++++++++++++++ 4 files changed, 297 insertions(+), 1 deletion(-) create mode 100644 docs/mvp/v0.130.0-mvp.1/quickstart-linux-single-host.md create mode 100644 docs/mvp/v0.130.0-mvp.1/quickstart-single-host.pl diff --git a/docs/mvp/v0.130.0-mvp.1/01-linux-four-node-deployment.md b/docs/mvp/v0.130.0-mvp.1/01-linux-four-node-deployment.md index bed217bd3b..949325d1f8 100644 --- a/docs/mvp/v0.130.0-mvp.1/01-linux-four-node-deployment.md +++ b/docs/mvp/v0.130.0-mvp.1/01-linux-four-node-deployment.md @@ -6,6 +6,8 @@ Author: SqlRush ## 1. 先选择部署目标 +只想在一台 Linux 主机上快速体验四实例,请直接使用[单机四实例 Quick Start](quickstart-linux-single-host.md)。下文保留四机规划和部署边界。 + | 目标 | 本版能够提供的依据 | 本文处理方式 | |---|---|---| | 同一 Linux 环境中的四个实例 | 发布验收中的受控四节点夹具、共享路径与投票设备配置 | 可参考源码复现;这是测试环境,不是四机高可用 | @@ -88,7 +90,7 @@ realpath /srv/pgrac-shared 四节点配置应使用正确初始化、四节点可访问的投票介质并启用严格多数判定,不能以 `cluster.allow_single_node=on` 代替。 - `cluster.voting_disks` 是按固定顺序配置的路径列表。介质内容、索引、身份和长度必须匹配;空文件、全零文件或刚创建的 loop 设备不合格。 -- 发布测试使用受控的文件初始化、全体正常关闭、同一内容映射为 Linux 直接 I/O 设备的流程。**单主机 loop 设备不是可跨四台主机共享的投票盘。** +- 单机 Quick Start 使用受控的新文件初始化,在四实例尚未启动时映射为 Linux 直接 I/O loop 设备,再启动集群。**单主机 loop 设备不是可跨四台主机共享的投票盘。**不要把历史两阶段测试辅助流程当成通用安装器。 - 源码中 `PostgreSQL::Test::ClusterVotingDisk` 是**会覆写目标的测试 formatter**,不是运维工具。不要把它用于生产设备。 - MVP 未提供经过四机认证的投票介质制备/设备接入 CLI。这是四机从零部署尚需补齐的交付环节;应在此记录“部署前提未满足”,不能继续开启业务。 - `pg_cluster_voting_disks` 的逐盘状态/计数部分仍是占位输出;`unknown`/0 不等于磁盘健康。需结合 quorum 视图、日志和设备检查。 diff --git a/docs/mvp/v0.130.0-mvp.1/README.md b/docs/mvp/v0.130.0-mvp.1/README.md index 4bd90d79a7..c0e21f6ae6 100644 --- a/docs/mvp/v0.130.0-mvp.1/README.md +++ b/docs/mvp/v0.130.0-mvp.1/README.md @@ -8,6 +8,8 @@ Author: SqlRush ## 四份文档 +首次体验先用[单机四实例 Quick Start(Linux)](quickstart-linux-single-host.md):从拉取固定标签、编译安装到共享行读写和全体正常关机,附可执行示例。 + | 文档 | 内容 | |---|---| | [一、Linux 四节点部署与共享存储](01-linux-four-node-deployment.md) | 版本获取、依赖、编译安装、四机规划、共享存储契约、seed/join、建库边界、启动检查与正常关机 | @@ -31,3 +33,5 @@ Author: SqlRush ## 文档验证范围 本次逐项核对了标签源码的参数注册、SQL catalog、视图 producer、CLI 与测试夹具,并检查接口覆盖和链接。没有因编写文档再次运行业务验收,也没有执行四台物理机安装演练。部署篇明确标出目前不能仅靠已发布工具完成认证交付的环节。 + +Quick Start 另做了单机最小演练:固定标签新构建、四实例共享行读写、全体正常关机及自有 loop 清理通过;不等于重跑 C8、soak、micro 或 PRE。 diff --git a/docs/mvp/v0.130.0-mvp.1/quickstart-linux-single-host.md b/docs/mvp/v0.130.0-mvp.1/quickstart-linux-single-host.md new file mode 100644 index 0000000000..f16caad07b --- /dev/null +++ b/docs/mvp/v0.130.0-mvp.1/quickstart-linux-single-host.md @@ -0,0 +1,157 @@ +# 单机四实例 Quick Start(Linux) + +Author: SqlRush + +目标:一台 Linux 主机、四个 PGRAC 进程、同一份共享业务数据。不是四台主机,也不需要容器或 GFS2。 + +版本固定为 `v0.130.0-mvp.1`。以下在 **Rocky Linux 9 / Btrfs** 上完成了编译、四实例共享行读写和全体正常关机演练。仅用于隔离评估,保留本版本的 CI 和非生产限制。 + +## 1. 准备 Linux 主机 + +使用有 `sudo` 权限的普通账号,预留充足内存和至少 20 GiB 磁盘空间。全程在同一个 Bash 终端执行;不要用 root 运行数据库。 + +```bash +bash +set -euo pipefail +test "$(id -u)" -ne 0 +umask 077 + +sudo dnf install -y dnf-plugins-core +sudo dnf config-manager --set-enabled crb +sudo dnf install -y gcc make git curl pkgconf-pkg-config bison flex \ + perl perl-IPC-Run perl-Test-Simple perl-Time-HiRes \ + readline-devel zlib-devel libicu-devel lz4-devel libzstd-devel \ + util-linux procps-ng tar kmod +test -c /dev/loop-control || sudo modprobe loop +sudo losetup --find + +export PGRAC_QS_ROOT="$(mktemp -d /var/tmp/pgrac-quickstart.XXXXXX)" +findmnt -T "$PGRAC_QS_ROOT" +df -h "$PGRAC_QS_ROOT" +printf '本次安装目录:%s\n' "$PGRAC_QS_ROOT" +``` + +该目录必须位于本机磁盘,不能使用 NFS 或主机共享映射目录。本机 ext4/XFS 不涉及跨主机挂载,但本次演练文件系统为 Btrfs。示例只创建自己的三个投票文件及对应 loop 设备,不要自行格式化现有盘。 + +## 2. 拉取 MVP 源码 + +```bash +git clone --depth 1 --branch v0.130.0-mvp.1 --single-branch \ + https://github.com/sqlrush/pgrac.git "$PGRAC_QS_ROOT/source" +test "$(git -C "$PGRAC_QS_ROOT/source" rev-parse HEAD)" = \ + c581f3835a4a9a76ce5a77c0937930f21765725a +``` + +## 3. 编译安装 + +本标签采用不启用 OpenSSL 的本机评估构建;SQL 只使用本机 Unix socket,不开放外部 SQL 端口。 + +```bash +mkdir "$PGRAC_QS_ROOT/build" +cd "$PGRAC_QS_ROOT/build" +../source/configure --prefix="$PGRAC_QS_ROOT/install" \ + --enable-cluster --enable-cassert --enable-debug --enable-tap-tests \ + --with-icu --with-lz4 --with-zstd +make -j4 +make install +make -C src/test/cluster_tap all +make -C src/test/regress pg_regress + +export PATH="$PGRAC_QS_ROOT/install/bin:$PATH" +pg_config --configure +``` + +## 4. 下载初始化示例 + +示例调用该标签已有的测试支持模块,源码版本不变。下载后先验证哈希,再执行。 + +```bash +curl -fL \ + https://raw.githubusercontent.com/sqlrush/pgrac/main/docs/mvp/v0.130.0-mvp.1/quickstart-single-host.pl \ + -o "$PGRAC_QS_ROOT/run-quad.pl" +printf '%s %s\n' \ + 0e5fe8670349c33485474f4757744507c9939a4149d2adef06a23f538ae9f4c9 \ + "$PGRAC_QS_ROOT/run-quad.pl" | sha256sum -c - + +mkdir "$PGRAC_QS_ROOT/data" "$PGRAC_QS_ROOT/log" +cat > "$PGRAC_QS_ROOT/seed.conf" <<'CONF' +fsync = on +full_page_writes = on +synchronous_commit = on +CONF + +export LC_ALL=C +export PERL5LIB="$PGRAC_QS_ROOT/source/src/test/perl" +export PG_REGRESS="$PGRAC_QS_ROOT/build/src/test/regress/pg_regress" +export PGRAC_DIRECT_IO_PROBE="$PGRAC_QS_ROOT/build/src/test/cluster_tap/pgrac_direct_io_probe" +export top_builddir="$PGRAC_QS_ROOT/build" +export TEMP_CONFIG="$PGRAC_QS_ROOT/seed.conf" +export TESTDATADIR="$PGRAC_QS_ROOT/data" +export TESTLOGDIR="$PGRAC_QS_ROOT/log" +export PG_TEST_NOCLEAN=1 PG_TEST_TIMEOUT_DEFAULT=180 +export PGRAC_STAGE8_HAPPY_PATH_ONLY=1 +unset PGRAC_TEST_TWO_STAGE_VOTING_LOOP +``` + +## 5. 初始化并启动四实例 + +脚本建立一个数据库身份及四个独立 PGDATA,共享业务数据文件;自动分配端口、配置互联与投票设备,创建 `postgres` 数据库中的 `quickstart_demo` 表。表结构在 seed 阶段创建后克隆;本示例不演示在线 DDL 或 `CREATE DATABASE`。 + +```bash +sudo -v +perl "$PGRAC_QS_ROOT/run-quad.pl" > "$PGRAC_QS_ROOT/launcher.out" 2>&1 & +export PGRAC_QS_PID=$! + +for attempt in $(seq 1 360); do + test ! -f "$PGRAC_QS_ROOT/READY" || break + if ! kill -0 "$PGRAC_QS_PID" 2>/dev/null; then + tail -n 60 "$PGRAC_QS_ROOT/launcher.out" + tail -n 60 "$PGRAC_QS_ROOT/log/regress_log_run-quad" + exit 1 + fi + sleep 1 +done +test -f "$PGRAC_QS_ROOT/READY" +source "$PGRAC_QS_ROOT/connect.env" +``` + +出现 `READY` 表示四实例已依次更新同一行,并都读到 `value=4`,不是仅进程启动成功。数据与日志路径在 `$PGRAC_QS_ROOT` 下,连接端口和四份 PGDATA 路径见 `connect.env`。 + +## 6. 连接并验证共享读写 + +```bash +# 四个实例都应读到 id=1、value=4。 +for port in "$PGPORT_0" "$PGPORT_1" "$PGPORT_2" "$PGPORT_3"; do + psql -X -v ON_ERROR_STOP=1 -p "$port" -c 'TABLE quickstart_demo' +done + +# 在 node3 修改,随后在 node0 读取,应为 14。 +psql -X -v ON_ERROR_STOP=1 -p "$PGPORT_3" \ + -c 'UPDATE quickstart_demo SET value=value+10 WHERE id=1 RETURNING *' +psql -X -v ON_ERROR_STOP=1 -p "$PGPORT_0" \ + -c 'TABLE quickstart_demo' + +# 需要交互操作时使用;输入 \q 退出。 +psql -X -p "$PGPORT_0" +``` + +## 7. 全体正常关机 + +退出交互 SQL、保持原终端打开,再执行: + +```bash +sudo -v +touch "$PGRAC_QS_ROOT/STOP" +wait "$PGRAC_QS_PID" +test -f "$PGRAC_QS_ROOT/STOPPED" + +for datadir in "$PGDATA_0" "$PGDATA_1" "$PGDATA_2" "$PGDATA_3"; do + pg_controldata "$datadir" | grep 'Database cluster state' +done +``` + +预期四行均为 `shut down`。脚本先向四实例发送 fast shutdown,验证控制文件与关机日志,再释放自己创建的 loop 设备;不删除数据目录。 + +不要直接杀进程、运行 `losetup -D`,也不要在原目录重复运行初始化脚本。它是新建示例,不是原数据重启工具。失败时保留整个目录与日志;失败清理可能立即停止示例进程,不能把失败现场当成正常关机数据。 + +更多配置见[参数手册](02-parameters.md);四机部署边界见[Linux 四节点部署](01-linux-four-node-deployment.md)。 diff --git a/docs/mvp/v0.130.0-mvp.1/quickstart-single-host.pl b/docs/mvp/v0.130.0-mvp.1/quickstart-single-host.pl new file mode 100644 index 0000000000..b8f39020dd --- /dev/null +++ b/docs/mvp/v0.130.0-mvp.1/quickstart-single-host.pl @@ -0,0 +1,133 @@ +#!/usr/bin/env perl +# Linux single-host usage example for v0.130.0-mvp.1; not a production installer. +# Author: SqlRush +use strict; +use warnings; +use File::Path qw(make_path); +use Fcntl qw(O_CREAT O_EXCL O_WRONLY); +use IPC::Run qw(run); +use PostgreSQL::Test::ClusterQuad; +use PostgreSQL::Test::Utils qw(slurp_file); +use Test::More; +use Time::HiRes qw(time usleep); + +my $root = $ENV{PGRAC_QS_ROOT} or die "PGRAC_QS_ROOT is required\n"; +die "root must be an absolute private path\n" + unless $root =~ m{\A/[A-Za-z0-9_./-]+\z} && -d $root && !-l $root; +die "Linux and a non-root account are required\n" unless $^O eq 'linux' && $< != 0; +die "offline loop startup must be selected\n" + unless ($ENV{PGRAC_STAGE8_HAPPY_PATH_ONLY} // '') eq '1' + && !($ENV{PGRAC_TEST_TWO_STAGE_VOTING_LOOP} // ''); +sysopen(my $once, "$root/INITIALIZED", O_WRONLY | O_CREAT | O_EXCL, 0600) + or die "use a new example directory; this one was already initialized: $!\n"; +close($once) or die $!; +my $quad = PostgreSQL::Test::ClusterQuad->new_quad( + 'quickstart', quorum_voting_disks => 3, + shared_data => 1, shared_system_identifier => 1, + shared_system_identifier_seed_sql => + 'CREATE TABLE quickstart_demo(id integer PRIMARY KEY, value integer NOT NULL);', + extra_conf => [ + 'fsync = on', 'full_page_writes = on', 'synchronous_commit = on', + 'autovacuum = off', 'shared_buffers = 128MB', + 'max_connections = 32', 'max_parallel_workers_per_gather = 0', + 'cluster.clean_leave_enabled = on', + 'cluster.read_scache = on', 'cluster.online_join = on', + 'cluster.quorum_poll_interval_ms = 2000', + 'cluster.write_fence_lease_ms = 60000', + 'cluster.join_convergence_timeout_ms = 30000', + 'cluster.xid_striping = on', + 'cluster.crossnode_runtime_visibility = on', + 'cluster.page_scn_shortcut = on', 'cluster.past_image = on', + 'cluster.crossnode_write_write = on', + 'cluster.undo_gcs_coherence = on', + 'cluster.crossnode_cr_data_plane = on', + 'cluster.gcs_reply_timeout_ms = 3000', + 'cluster.gcs_block_retransmit_max_retries = 8', + 'cluster.cssd_heartbeat_interval_ms = 2000', + 'cluster.cssd_dead_deadband_factor = 10', + ]); +note('Initializing four instances'); +$quad->start_quad; +for my $from (0 .. 3) { + for my $to (0 .. 3) { + next if $from == $to; + $quad->wait_for_peer_state($from, $to, 'connected', 45) + or die "node$from cannot reach node$to\n"; + } +} +make_path($quad->shared_data_root . '/pg_undo'); +for my $node ($quad->nodes) { + $node->poll_query_until('postgres', + 'SELECT in_quorum FROM pg_cluster_quorum_state', 't') + or die "quorum did not become ready\n"; + my ($rc, $out, $err) = $node->psql('postgres', + 'ALTER SYSTEM ENABLE RAC TWO_STAGE ROLLING UPDATES ALL', timeout => 45); + die "activation failed: $err\n" unless defined($rc) && + ($rc == 0 || $err =~ /RF_DEFERRED|CONDITION_NOT_YET_MET/); +} +for my $round (1 .. 2) { + my $deadline = time() + 120; + my $ready = 0; + while (time() < $deadline) { + my ($rc, $out, $err) = $quad->node0->psql('postgres', + 'ALTER SYSTEM ENABLE RAC TWO_STAGE ROLLING UPDATES ALL', timeout => 45); + if (defined($rc) && $rc == 0) { $ready = 1; last; } + die "activation failed: $err\n" unless defined($rc) && + $err =~ /RF_DEFERRED|CONDITION_NOT_YET_MET|activation request was refused/; + usleep(200_000); + } + die "activation round $round did not complete\n" unless $ready; +} +$quad->node0->safe_psql('postgres', + 'INSERT INTO quickstart_demo VALUES (1,0);'); +for my $i (0 .. 3) { + is($quad->node($i)->safe_psql('postgres', + 'UPDATE quickstart_demo SET value=value+1 WHERE id=1 RETURNING value;'), + '' . ($i + 1), "node$i sees and updates the shared row") or die "SQL check failed\n"; +} +open(my $env, '>', "$root/connect.env") or die $!; +print {$env} "export PGHOST='" . $quad->node0->host . "'\n"; +print {$env} "export PGDATABASE=postgres\n"; +for my $i (0 .. 3) { + print {$env} "export PGPORT_$i=" . $quad->node($i)->port . "\n"; + print {$env} "export PGDATA_$i='" . $quad->node($i)->data_dir . "'\n"; + is($quad->node($i)->safe_psql('postgres', + 'SELECT value FROM quickstart_demo WHERE id=1;'), '4', "node$i reads value 4") + or die "readback failed\n"; +} +close($env) or die $!; +open(my $ready, '>', "$root/READY") or die $!; +close($ready) or die $!; +note("READY: source $root/connect.env"); +usleep(200_000) until -e "$root/STOP"; +my @nodes = $quad->nodes; +my @offsets = map { -s $_->logfile } @nodes; +for my $node (@nodes) { + system('pg_ctl', '-D', $node->data_dir, '-m', 'fast', '-W', 'stop') == 0 + or die "normal shutdown request failed\n"; +} +my $stop_deadline = time() + 600; +while (grep { -f $_->data_dir . '/postmaster.pid' } @nodes) { + die "normal shutdown did not complete; retain this directory\n" + if time() >= $stop_deadline; + usleep(200_000); +} +for my $i (0 .. 3) { + my ($control, $err) = ('', ''); + run(['pg_controldata', $nodes[$i]->data_dir], '>', \$control, '2>', \$err) + or die "pg_controldata failed: $err\n"; + die "node$i was not shut down cleanly\n" + unless $control =~ /^Database cluster state:\s+shut down\s*$/m; + my $log = substr(slurp_file($nodes[$i]->logfile), $offsets[$i]); + die "node$i has an abnormal shutdown log\n" + if $log =~ /(?:FATAL:|PANIC:|abnormal database system shutdown)/; + die "node$i normal shutdown protocol did not close\n" + unless $log =~ /cluster normal-stop: protocol closed after shutdown checkpoint/ + && $log =~ /database system is shut down/; + $nodes[$i]->_update_pid(0); +} +# Only after four durable clean stops may the helper detach its owned loops. +$quad->stop_quad; +open(my $stopped, '>', "$root/STOPPED") or die $!; +close($stopped) or die $!; +done_testing(); From 0d68bb7ba16b251bdd141afbeecb682906859eca Mon Sep 17 00:00:00 2001 From: SqlRush Date: Thu, 17 Sep 2026 10:55:40 +0800 Subject: [PATCH 2/2] docs(mvp): make seed-only schema boundary explicit --- docs/mvp/v0.130.0-mvp.1/quickstart-linux-single-host.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/mvp/v0.130.0-mvp.1/quickstart-linux-single-host.md b/docs/mvp/v0.130.0-mvp.1/quickstart-linux-single-host.md index f16caad07b..315c95a935 100644 --- a/docs/mvp/v0.130.0-mvp.1/quickstart-linux-single-host.md +++ b/docs/mvp/v0.130.0-mvp.1/quickstart-linux-single-host.md @@ -95,7 +95,7 @@ unset PGRAC_TEST_TWO_STAGE_VOTING_LOOP ## 5. 初始化并启动四实例 -脚本建立一个数据库身份及四个独立 PGDATA,共享业务数据文件;自动分配端口、配置互联与投票设备,创建 `postgres` 数据库中的 `quickstart_demo` 表。表结构在 seed 阶段创建后克隆;本示例不演示在线 DDL 或 `CREATE DATABASE`。 +脚本建立一个数据库身份及四个独立 PGDATA,共享业务数据文件;自动分配端口、配置互联与投票设备,创建 `postgres` 数据库中的 `quickstart_demo` 表。表结构在 seed 阶段创建后克隆;本示例不启用共享系统目录,请勿在运行后单独建表、改表或执行 `CREATE DATABASE`。 ```bash sudo -v