一键导入
openedge-replication
Implements three-tier replication architecture (DRBD+AI, ZFS+AI, AI-only rsync) for Progress OpenEdge 12.x databases without OER license.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Implements three-tier replication architecture (DRBD+AI, ZFS+AI, AI-only rsync) for Progress OpenEdge 12.x databases without OER license.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Implements v1 ConfigMap manifests for injecting configuration data into pods via environment variables, volume mounts, and command-line arguments.
Implements apps/v1 Deployment YAML manifests with rolling update strategies, replica scaling, and rollback procedures for stateless application workloads.
Implements networking.k8s.io/v1 Ingress resources with HTTP/HTTPS routing, TLS termination, path-based routing, and ingress controller configuration.
Implements Istio service mesh patterns (sidecar injection, traffic splitting, circuit breaking, retries, and mTLS) for advanced traffic management and zero-downtime deployments in Kubernetes.
Implements networking.k8s.io/v1 NetworkPolicy resources with ingress and egress rules, pod selector targeting, and network segmentation for microservice isolation.
Implements v1 PersistentVolume, StorageClass, and PersistentVolumeClaim manifests with static/dynamic provisioning, access modes, and reclaim policies for Kubernetes storage.
| name | openedge-replication |
| description | Implements three-tier replication architecture (DRBD+AI, ZFS+AI, AI-only rsync) for Progress OpenEdge 12.x databases without OER license. |
| license | MIT |
| compatibility | opencode |
| metadata | {"version":"1.0.0","domain":"linux","role":"reference","scope":"infrastructure","output-format":"manifests","content-types":["guidance","config","examples","diagrams"],"triggers":"openedge replication, DRBD, ZFS snapshot, AI shipping, failover runbook, rfutil roll-forward, keepalived VIP","archetypes":["tactical","strategic"],"anti_triggers":["brainstorming","vague ideation"],"response_profile":{"verbosity":"medium","directive_strength":"high","abstraction_level":"tactical"},"related-skills":"linux-services, storage-architecture, shell-process-management","maturity":"stable","completeness":95,"exampleCount":5} |
Configures and operates a three-tier database replication architecture for Progress OpenEdge 12.x databases without the OER license — using After-Image shipping, DRBD block replication, ZFS snapshots, keepalived VIP failover, and rfutil roll-forward for sub-second to five-minute RPO/RTO targets.
proutil -C aimage begin and configure archiver directoryUse this skill when:
proutil -C aimage) and rfutil roll-forward-RO modeAvoid this skill for:
rfutil applyDetermine which replication tier matches your RPO, RTO, infrastructure, and budget constraints.
| Tier | Mechanism | RPO | RTO | BI Protected | Complexity |
|---|---|---|---|---|---|
| Tier 1 | DRBD async + AI shipping | < 2s | < 30s | Yes (DRBD replicates .bi) | Medium |
| Tier 2 | ZFS snapshot + AI shipping | < 5min | < 2min | Yes (in snapshot) | Medium-High |
| Tier 3 | AI-only rsync | 1-5min | < 2min | No | Low |
Checkpoint: Confirm your RPO and RTO targets, verify available infrastructure (DRBD kernel module, ZFS pool, or neither), and decide if BI protection is required. Tier 3 has no BI protection — uncommitted transactions at crash time are lost.
After-Imaging records committed transaction changes that can be replayed on the target via rfutil roll-forward.
# Connect to the source database broker
proenv
# Enable AI logging (online, no restart required)
proutil mydb -C aimage begin
# Verify AI is enabled and archiver is running
proutil mydb -C aimage list
# Expected: After Image Enabled: Yes, Archive Destination: /data/ai-archive, Archive Status: Active
# Configure AI archiver directory (copies completed extents for shipping)
proutil mydb -C aiarchive enable -aiarcdir /data/ai-archive
Checkpoint: Verify Archive Status: Active in the output. The archiver copies completed AI extents to /data/ai-archive/, which is the source for rsync transport.
/etc/drbd.d/mydb.res)resource mydb {
protocol C;
on primary-server {
device /dev/drbd1;
disk /dev/sda3;
address 10.0.0.1:7788;
meta-disk internal;
}
on standby-server {
device /dev/drbd1;
disk /dev/sda3;
address 10.0.0.2:7788;
meta-disk internal;
}
syncer {
rate 100M;
al-extents 3833;
verify-alg sha1;
}
disk {
on-io-error detach;
fencing resource-only;
}
net {
cram-hmac-alg sha256;
shared-secret "your-secret-here";
after-sb-0p disconnect;
after-sb-1p disconnect;
after-sb-2p disconnect;
}
fence-peer {
program "/usr/lib/drbd/crm-fence-peer.sh";
}
}
Initialize:
drbdadm create-md mydb
drbdadm -- --overwrite-data-of-primary primary mydb
drbdadm secondary mydb # On standby
cat /proc/drbd # Verify sync progress
Checkpoint: DRBD must show Connected state and syncing must reach 100% before going live. The after-sb-2p disconnect policy prevents split-brain by dropping the connection if both sides are primary.
# Source pool setup
zpool create -f mirror /dev/sda /dev/sdb -o ashift=12 dbdata
zfs set compression=lz4 dbdata
zfs set atime=off dbdata
zfs set sync=always dbdata/db # Durability for database
zfs set checksum=sha256 dbdata/db
zfs create dbdata/db
zfs create dbdata/ai-archive
# Initial full replication
zfs send -v dbdata/db | ssh target zfs receive -F dbdata/db
# Incremental (run via cron every 5 min)
zfs send -v -i dbdata/db@repl-202601011200 dbdata/db@repl-202601011205 \
| ssh target zfs receive -F dbdata/db
Checkpoint: Verify incremental send completes successfully and ZFS pool has sufficient space for snapshot retention. Use zfs list -t snapshot -r dbdata/db to review snapshot history.
The AI shipping layer provides transaction-level replication independent of block/snapshot replication, catching transactions between DRBD sync or ZFS snapshot intervals.
Source — AI Ship Daemon (/opt/repl/bin/ai-ship-daemon.sh):
#!/usr/bin/env bash
set -euo pipefail
SOURCE_DIR="/data/ai-archive"
TARGET_HOST="target-server"
TARGET_DIR="/data/ai-received"
DB_NAME="mydb"
LOG_FILE="/var/log/repl/ai-ship.log"
PID_FILE="/var/run/repl/ai-ship.pid"
running() { kill -0 "$1" 2>/dev/null; }
start_daemon() {
if [ -f "$PID_FILE" ] && running "$(cat "$PID_FILE")"; then
echo "Already running (PID $(cat "$PID_FILE"))" >&2
return 1
fi
mkdir -p "$(dirname "$LOG_FILE")"
(
while true; do
inotifywait -e close_write,moved_to "$SOURCE_DIR" 2>/dev/null || continue
sleep 2
latest_ai=$(ls -t "$SOURCE_DIR"/${DB_NAME}.ai* 2>/dev/null | head -1)
if [ -n "$latest_ai" ]; then
rsync --checksum --compress --timeout=60 "$latest_ai" "$TARGET_HOST:$TARGET_DIR/" \
>> "$LOG_FILE" 2>&1
fi
done
) &
echo $! > "$PID_FILE"
}
stop_daemon() {
[ -f "$PID_FILE" ] && kill "$(cat "$PID_FILE")" 2>/dev/null || true
rm -f "$PID_FILE"
}
case "${1:-}" in
start) start_daemon ;;
stop) stop_daemon ;;
status) [ -f "$PID_FILE" ] && running "$(cat "$PID_FILE")" && echo "Running" || echo "Stopped" ;;
*) echo "Usage: $0 {start|stop|status}"; exit 1 ;;
esac
Target — AI Apply Daemon (/opt/repl/bin/ai-apply-daemon.sh):
#!/usr/bin/env bash
set -euo pipefail
DB_NAME="mydb"
RECEIVE_DIR="/data/ai-received"
APPLY_DIR="/data/ai-pending"
LOG_FILE="/var/log/repl/ai-apply.log"
PID_FILE="/var/run/repl/ai-apply.pid"
LOCK_FILE="/var/run/repl/ai-apply.lock"
APPLY_INTERVAL=60
acquire_lock() {
[ -f "$LOCK_FILE" ] && return 1
echo $$ > "$LOCK_FILE"
}
release_lock() { rm -f "$LOCK_FILE"; }
apply_ai() {
local pending
pending=$(ls -1 "$RECEIVE_DIR"/${DB_NAME}.ai* 2>/dev/null | wc -l)
[ "$pending" -eq 0 ] && return 0
ls -1 "$RECEIVE_DIR"/${DB_NAME}.ai* | sort > /tmp/ai-apply-list.txt
rfutil "$DB_NAME" -C roll forward oplock -ailist /tmp/ai-apply-list.txt >> "$LOG_FILE" 2>&1 || true
rm -f /tmp/ai-apply-list.txt
proutil "$DB_NAME" -C aimage begin 2>/dev/null || true
for f in "$RECEIVE_DIR"/${DB_NAME}.ai*; do
[ -f "$f" ] && mv "$f" "$APPLY_DIR/.applied/" 2>/dev/null || true
done
}
start_daemon() {
mkdir -p "$APPLY_DIR/.applied" "$(dirname "$LOG_FILE")"
(
while true; do
if acquire_lock; then apply_ai; release_lock; fi
sleep "$APPLY_INTERVAL"
done
) &
echo $! > "$PID_FILE"
}
stop_daemon() {
[ -f "$PID_FILE" ] && kill "$(cat "$PID_FILE")" 2>/dev/null || true
rm -f "$PID_FILE"; release_lock
}
case "${1:-}" in
start) start_daemon ;;
stop) stop_daemon ;;
status) [ -f "$PID_FILE" ] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null && echo "Running" || echo "Stopped" ;;
*) echo "Usage: $0 {start|stop|status}"; exit 1 ;;
esac
Checkpoint: Both daemons must show Running status. Verify AI files appear in $TARGET_DIR/ai-received/ and get applied to $APPLY_DIR/.applied/.
The Virtual IP floats between servers, allowing clients to connect without reconnection code on failover.
Keepalived config (/etc/keepalived/keepalived.conf):
global_defs {
router_id mydb-repl
}
vrrp_script chk_proserve {
script "/opt/repl/bin/check-proserve.sh"
interval 5
timeout 3
fall 2
rise 1
}
vrrp_instance mydb_vip {
state BACKUP
interface eth0
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass mydbsecret
}
virtual_ipaddress {
192.168.1.100/24 dev eth0
}
track_script { chk_proserve }
notify /etc/keepalived/notify.sh
}
Health check script (/opt/repl/bin/check-proserve.sh):
#!/usr/bin/env bash
DB_NAME="${DB_NAME:-mydb}"
if proshut "$DB_NAME" -C status 2>/dev/null | grep -q "Multi-User"; then
exit 0
else
exit 1
fi
Standby startup command:
# Primary (read-write)
proserve mydb -H primary-server -S 3001
# Standby (read-only, for offloading SELECT queries)
proserve mydb -RO -H standby-server -S 3001
Checkpoint: ip addr show eth0 | grep 192.168.1.100 must show the VIP on the primary. Stopping the primary should cause the VIP to migrate to standby within ~5 seconds (3 failed checks at 5s interval + fall count).
# /etc/systemd/system/ai-ship.service
[Unit]
Description=AI Ship Daemon for OpenEdge Database Replication
After=network.target remote-fs.target
[Service]
Type=forking
ExecStart=/opt/repl/bin/ai-ship-daemon.sh start
ExecStop=/opt/repl/bin/ai-ship-daemon.sh stop
PIDFile=/var/run/repl/ai-ship.pid
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
# /etc/systemd/system/ai-apply.service
[Unit]
Description=AI Apply Daemon for OpenEdge Database Replication
After=network.target remote-fs.target
[Service]
Type=forking
ExecStart=/opt/repl/bin/ai-apply-daemon.sh start
ExecStop=/opt/repl/bin/ai-apply-daemon.sh stop
PIDFile=/var/run/repl/ai-apply.pid
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
# /etc/systemd/system/keepalived.service
[Unit]
Description=Keepalived VRRP for OpenEdge VIP Failover
After=network.target
[Service]
Type=forking
ExecStart=/usr/sbin/keepalived --vrrp --dump-conf /tmp/keepalived.conf
ExecStop=/usr/sbin/keepalived -k
PIDFile=/var/run/keepalived.pid
[Install]
WantedBy=multi-user.target
Enable and start:
systemctl daemon-reload
systemctl enable --now ai-ship.service ai-apply.service keepalived.service
Run health checks across all components:
# Overall health (source + target + replication lag)
/opt/repl/bin/health-monitor.sh all
# DRBD-specific (Tier 1 only)
drbdadm status mydb
cat /proc/drbd
# ZFS-specific (Tier 2 only)
zfs list -t snapshot -r dbdata/db
# Service health
systemctl status ai-ship.service ai-apply.service keepalived.service
# Database integrity on target
proutil mydb -C dbanalys > /tmp/verify-analysis.txt
grep -i "error\|corrupt\|inconsistent" /tmp/verify-analysis.txt
Checkpoint: Health monitor reports lag < threshold (5 minutes), DRBD/ZFS shows connected/synced, no errors in dbanalys output.
.bi files at block level)proserve -RO)zfs send/recv over SSH)WARNING for Tier 3: The BI file is NOT replicated. On sudden source failure (power loss, kernel panic), any uncommitted transactions at crash time are lost. This tier must never be used for financial or transaction-critical production databases.
Split-brain occurs when both servers believe they are primary, leading to data corruption. All three tiers prevent this through layered defense.
#!/usr/bin/env bash
# DANGEROUS: Promotes without verifying source is truly dead or checking existing locks
drbdadm primary mydb
proserve mydb -H new-primary -S 3001
What's wrong:
/var/run/repl/failover.lock — concurrent failover attempts can produce dual-primary state#!/usr/bin/env bash
set -euo pipefail
DB_NAME="mydb"
SOURCE_HOST="primary-server"
LOCK_FILE="/var/run/repl/failover.lock"
# Step 1: Acquire failover lock (prevents concurrent attempts)
if [ -f "$LOCK_FILE" ]; then
echo "ERROR: Failover already in progress" >&2
exit 1
fi
echo $$ > "$LOCK_FILE"
trap 'rm -f "$LOCK_FILE"' EXIT
# Step 2: Verify source is truly dead (multiple independent methods)
source_alive=false
ping -c 3 -W 5 "$SOURCE_HOST" >/dev/null 2>&1 && source_alive=true
ssh -o ConnectTimeout=5 "$SOURCE_HOST" "echo alive" >/dev/null 2>&1 && source_alive=true
proshut "$DB_NAME" -C status 2>/dev/null | grep -q "Multi-User" && source_alive=true
if $source_alive; then
echo "ERROR: Source is still reachable — do not fail over" >&2
exit 1
fi
# Step 3: Stop AI apply daemon before promoting
/opt/repl/bin/ai-apply-daemon.sh stop
# Step 4: Promote DRBD (Tier 1) or ZFS (Tier 2 is already current via receive -F)
drbdadm primary mydb
# Step 5: Start database — BI crash recovery runs automatically on startup
proserve "$DB_NAME" -H new-primary -S 3001
# Step 6: Apply remaining delta AI files
/opt/repl/bin/ai-apply-daemon.sh start
sleep 10
/opt/repl/bin/ai-apply-daemon.sh stop
# Step 7: Verify database is accepting connections
if proshut "$DB_NAME" -C status | grep -q "Multi-User"; then
echo "FAILOVER COMPLETE: Database running on new primary"
else
echo "ERROR: Database failed to start — check logs at /data/${DB_NAME}/*.lg" >&2
exit 1
fi
echo "" > "$LOCK_FILE" # Clear lock file only after verified success
trap - EXIT # Disable cleanup trap since we cleared the lock ourselves
Why this works:
Database files must be quiesced before snapshot to ensure consistency. The pre/post-snapshot hooks freeze and resume the database.
#!/usr/bin/env bash
set -euo pipefail
DATABASE="mydb"
POOL="dbdata/db"
SNAP_PREFIX="repl"
TARGET_HOST="target-server"
LOG_FILE="/var/log/repl/zfs-replicate.log"
log() { echo "$(date '+%Y-%m-%d %H:%M:%S') $*" | tee -a "$LOG_FILE"; }
snapshot_name="${POOL}@${SNAP_PREFIX}-$(date +%Y%m%d%H%M)"
last_snapshot=""
# Find last sent snapshot on target for incremental
if ssh "$TARGET_HOST" "zfs list -t snapshot -r dbdata/db 2>/dev/null"; then
last_snapshot=$(ssh "$TARGET_HOST" \
"zfs list -t snapshot -r dbdata/db | grep '${SNAP_PREFIX}' | tail -1 | awk '{print \$1}'") || true
fi
log "Creating snapshot: $snapshot_name"
touch /var/run/repl/pre-snapshot.done
# Quiesce database — flush buffers and ensure consistent state
# OpenEdge has no native quiesce; we wait for idle transactions
proenv
proutil "$DATABASE" -C promon 2>/dev/null || true
zfs snapshot "$snapshot_name"
rm -f /var/run/repl/pre-snapshot.done
if [ -n "$last_snapshot" ]; then
log "Sending incremental from $last_snapshot to $snapshot_name"
zfs send -v -i "$last_snapshot" "$snapshot_name" \
| ssh "$TARGET_HOST" zfs receive -F dbdata/db
else
log "Sending full snapshot"
zfs send -v "$snapshot_name" | ssh "$TARGET_HOST" zfs receive -F dbdata/db
fi
log "Replication complete"
Why this works: The marker file (pre-snapshot.done) signals to external tools (like monitoring) that a snapshot is in progress. Using incremental sends with the last-known target snapshot minimizes bandwidth and ensures the target stays current.
Track replication lag between source AI files and applied AI files on the target. The health monitor compares timestamps to detect when lag exceeds the RPO threshold.
#!/usr/bin/env bash
set -euo pipefail
SOURCE_HOST="primary-server"
DB_NAME="mydb"
RECEIVE_DIR="/data/ai-received"
APPLY_DIR="/data/ai-pending/.applied"
LAG_THRESHOLD=300 # 5 minutes in seconds
# Get latest shipped AI timestamp from source archive
latest_source=$(ssh "$SOURCE_HOST" \
"ls -t /data/ai-archive/${DB_NAME}.ai*" 2>/dev/null | head -1)
# Get latest applied AI timestamp on target
latest_applied=$(ls -t "${APPLY_DIR}/${DB_NAME}.ai*" 2>/dev/null | head -1)
if [ -z "$latest_source" ] || [ -z "$latest_applied" ]; then
echo "WARNING: Cannot determine lag — missing AI files"
exit 0
fi
source_time=$(stat -c %Y "$latest_source")
applied_time=$(stat -c %Y "$latest_applied")
lag=$(( source_time - applied_time ))
if [ "$lag" -gt "$LAG_THRESHOLD" ]; then
echo "ERROR: Replication lag ${lag}s exceeds threshold ${LAG_THRESHOLD}s"
exit 1
else
echo "Replication lag: ${lag}s (within ${LAG_THRESHOLD}s threshold)"
exit 0
fi
Why this works: Comparing AI file timestamps provides an accurate picture of how far behind the target is relative to committed transactions on the source. This metric directly maps to your RPO — if lag is below your RPO threshold, replication is healthy.
Emergency failover differs by tier because the storage state at failover time varies significantly.
#!/usr/bin/env bash
set -euo pipefail
DB_NAME="mydb"
TIER="${1:-tier1}" # tier1, tier2, or tier3
LOG_FILE="/var/log/repl/failover.log"
log() { echo "$(date '+%Y-%m-%d %H:%M:%S') $*" | tee -a "$LOG_FILE"; }
log "Starting emergency failover (Tier: $TIER)..."
# 1. Acquire lock
[ -f /var/run/repl/failover.lock ] && { echo "Failover in progress" >&2; exit 1; }
echo $$ > /var/run/repl/failover.lock
# 2. Stop AI apply daemon
/opt/repl/bin/ai-apply-daemon.sh stop
# Tier-specific promotion steps
case "$TIER" in
tier1)
log "Tier 1: Promoting DRBD"
drbdadm primary mydb
mount /dev/drbd1 /data/$DB_NAME
;;
tier2)
log "Tier 2: ZFS already at latest via receive -F"
zpool import dbdata || true
;;
tier3)
log "Tier 3: No block promotion needed — applying remaining AI"
for ai in /data/ai-received/${DB_NAME}.ai*; do
[ -f "$ai" ] || continue
echo "$ai" > /tmp/final-apply.txt
proshut "$DB_NAME" -by 2>/dev/null || true
rfutil "$DB_NAME" -C roll forward oplock -ailist /tmp/final-apply.txt >> "$LOG_FILE" 2>&1
rm -f "$ai" /tmp/final-apply.txt
done
;;
esac
# 3. Enable AI on target for future replication
proutil "$DB_NAME" -C aimage begin
# 4. Start database
log "Starting database..."
proserve "$DB_NAME" -H new-primary -S 3001
# 5. Verify
if proshut "$DB_NAME" -C status | grep -q "Multi-User"; then
log "FAILOVER COMPLETE: Database running on $(hostname)"
else
log "ERROR: Database failed to start — check /data/$DB_NAME/*.lg"
rm -f /var/run/repl/failover.lock
exit 1
fi
rm -f /var/run/repl/failover.lock
Planned failover during maintenance windows is significantly safer than emergency failover because you can ensure replication is fully caught up before switching.
#!/usr/bin/env bash
set -euo pipefail
DB_NAME="mydb"
TARGET_HOST="target-server"
# Step 1: Verify target readiness
/opt/repl/bin/health-monitor.sh all
echo "Replication lag check passed"
# Step 2: Stop new transactions (coordinate with applications)
echo "Waiting for applications to drain connections..."
sleep 30
# Step 3: Wait for replication to fully catch up
/opt/repl/bin/check-ai-lag.sh
if [ $? -ne 0 ]; then
echo "ERROR: Replication not caught up — aborting planned failover"
exit 1
fi
# Step 4: Stop AI shipping on source
systemctl stop ai-ship.service
# Step 5: Manually ship any remaining files
rsync -avz /data/ai-archive/${DB_NAME}.ai* "${TARGET_HOST}:/data/ai-pending/"
# Step 6: Final apply on target (ensures zero-gap)
/opt/repl/bin/ai-apply-daemon.sh start
sleep 10
/opt/repl/bin/ai-apply-daemon.sh stop
# Step 7: Promote target based on tier
drbdadm primary mydb # Tier 1
# Step 8: Start database and verify
proserve "$DB_NAME" -H new-primary -S 3001
proshut "$DB_NAME" -C status | grep "Multi-User" && echo "Planned failover complete"
/var/run/repl/failover.lock before any failover or promotion operation to prevent concurrent attemptsoplock flag with rfutil roll-forward on OpenEdge 10.2B+ to prevent other processes from interfering during apply/opt/repl/bin/health-monitor.sh all daily as part of operational checksRestart=on-failure policiesdrbdadm primary on both servers simultaneously — this creates a guaranteed split-brain condition/data/ai-archive/ or /data/ai-received/ — always let the daemons manage file lifecycleproshut -by on a standby without first confirming it's safe to do so during normal operationsfall higher than 2 — false negatives will delay failover indefinitelyvirtual_router_id for multiple VIPs on the same network segment — this causes keepalived conflictsWhen applying this skill, your output should contain:
ai-ship-daemon.sh and ai-apply-daemon.sh adapted for the target environment| Skill | Purpose |
|---|---|
linux-services | Managing the replication daemons and keepalived as systemd services with proper dependencies, restart policies, and resource limits |
storage-architecture | Deep ZFS pool design, VDEV configuration, snapshot retention policies, and DRBD disk setup for database workloads |
shell-process-management | Writing robust daemon processes with proper signal handling, PID file management, lock files, and logging — patterns used by ai-ship and ai-apply daemons |
Authoritative documentation links for OpenEdge replication, DRBD, ZFS, and keepalived. The model follows these at load time to resolve external references and inline content.
-C aimage begin, -C aiarchive enable, -C dbanalys, and -C sequence