Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/mvp/v0.130.0-mvp.1/01-linux-four-node-deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Author: SqlRush <sqlrush@gmail.com>

## 1. 先选择部署目标

只想在一台 Linux 主机上快速体验四实例,请直接使用[单机四实例 Quick Start](quickstart-linux-single-host.md)。下文保留四机规划和部署边界。

| 目标 | 本版能够提供的依据 | 本文处理方式 |
|---|---|---|
| 同一 Linux 环境中的四个实例 | 发布验收中的受控四节点夹具、共享路径与投票设备配置 | 可参考源码复现;这是测试环境,不是四机高可用 |
Expand Down Expand Up @@ -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 视图、日志和设备检查。
Expand Down
4 changes: 4 additions & 0 deletions docs/mvp/v0.130.0-mvp.1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Author: SqlRush <sqlrush@gmail.com>

## 四份文档

首次体验先用[单机四实例 Quick Start(Linux)](quickstart-linux-single-host.md):从拉取固定标签、编译安装到共享行读写和全体正常关机,附可执行示例。

| 文档 | 内容 |
|---|---|
| [一、Linux 四节点部署与共享存储](01-linux-four-node-deployment.md) | 版本获取、依赖、编译安装、四机规划、共享存储契约、seed/join、建库边界、启动检查与正常关机 |
Expand All @@ -31,3 +33,5 @@ Author: SqlRush <sqlrush@gmail.com>
## 文档验证范围

本次逐项核对了标签源码的参数注册、SQL catalog、视图 producer、CLI 与测试夹具,并检查接口覆盖和链接。没有因编写文档再次运行业务验收,也没有执行四台物理机安装演练。部署篇明确标出目前不能仅靠已发布工具完成认证交付的环节。

Quick Start 另做了单机最小演练:固定标签新构建、四实例共享行读写、全体正常关机及自有 loop 清理通过;不等于重跑 C8、soak、micro 或 PRE。
157 changes: 157 additions & 0 deletions docs/mvp/v0.130.0-mvp.1/quickstart-linux-single-host.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# 单机四实例 Quick Start(Linux)

Author: SqlRush <sqlrush@gmail.com>

目标:一台 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 阶段创建后克隆;本示例不启用共享系统目录,请勿在运行后单独建表、改表或执行 `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)。
133 changes: 133 additions & 0 deletions docs/mvp/v0.130.0-mvp.1/quickstart-single-host.pl
Original file line number Diff line number Diff line change
@@ -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 <sqlrush@gmail.com>
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();
Loading