From 22ce55f486bfc80d7ab58558cd87e9ba0ba805c1 Mon Sep 17 00:00:00 2001 From: Khyojae Date: Sun, 8 Nov 2026 15:01:50 +0900 Subject: [PATCH 1/2] =?UTF-8?q?measure(rpo-rto):=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=EC=84=B8=EC=8A=A4=20=EC=9E=AC=EA=B8=B0=EB=8F=99=20RTO=20rig=20?= =?UTF-8?q?=E2=80=94=20=EC=89=BC=C2=B7=EA=B0=80=EC=A0=95=20=ED=94=BC?= =?UTF-8?q?=ED=81=AC=C2=B7=EC=B5=9C=EB=8C=80=20=EC=93=B0=EA=B8=B0=EC=97=90?= =?UTF-8?q?=EC=84=9C=20SIGKILL=20=E2=86=92=20=EB=8B=A4=EC=8B=9C=20?= =?UTF-8?q?=EC=BF=BC=EB=A6=AC=20=EB=B0=9B=EA=B8=B0=EA=B9=8C=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../ec2_userdata.sh | 37 ++++ .../process-restart-rto-2026-11-08/launch.sh | 25 +++ .../process-restart-rto-2026-11-08/restart.sh | 190 ++++++++++++++++++ 3 files changed, 252 insertions(+) create mode 100644 loadtest/results/process-restart-rto-2026-11-08/ec2_userdata.sh create mode 100644 loadtest/results/process-restart-rto-2026-11-08/launch.sh create mode 100644 loadtest/results/process-restart-rto-2026-11-08/restart.sh diff --git a/loadtest/results/process-restart-rto-2026-11-08/ec2_userdata.sh b/loadtest/results/process-restart-rto-2026-11-08/ec2_userdata.sh new file mode 100644 index 00000000..4546f2f4 --- /dev/null +++ b/loadtest/results/process-restart-rto-2026-11-08/ec2_userdata.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# EC2 무인 라운드 — 프로세스 재기동 RTO (RPO·RTO 분기점 §4 첫 줄 · §6-2) +# 규칙은 server-prep-and-ship-cost-2026-11-08/ec2_userdata.sh 와 같다 (SHA 고정·grep 단언·감시견·terminate). +# 대문자 자리표시자(골뱅이 두 쌍으로 감싼 것)는 launch.sh 가 띄우기 직전에 채운다. +set -uo pipefail +SHA=@@SHA@@ +RUN_ID=@@RUN_ID@@ +S3=s3://shadowfit-measure-055447613012/shadowfit/$RUN_ID +CAP_MIN=@@CAP_MIN@@ +RIG=/root/init/loadtest/results/process-restart-rto-2026-11-08 +OUT=/root/rig_out +LOG=/root/round.log +exec > >(tee -a "$LOG") 2>&1 + +up() { aws s3 cp "$LOG" "$S3/round.log" --only-show-errors || true; [ -d "$OUT" ] && aws s3 sync "$OUT" "$S3/out" --only-show-errors || true; } +finish() { echo "== finish: $1 ($(date -Is))"; up; aws s3 cp - "$S3/FINISHED" <<< "$1 $(date -Is)" || true; shutdown -h now; } + +echo "== start $(date -Is) SHA=$SHA RUN_ID=$RUN_ID CAP_MIN=$CAP_MIN" +( sleep $((CAP_MIN * 60)); echo "🔴 상한 ${CAP_MIN}분 도달 — 끈다"; finish CAP ) & +( while sleep 300; do up; done ) & + +cd /root +curl -fsSL "https://raw.githubusercontent.com/Shadowfit/init/$SHA/loadtest/aws/bootstrap.sh" -o bootstrap.sh || finish BOOTSTRAP_DOWNLOAD +grep -q 'ROLE=${ROLE:-db}' bootstrap.sh || finish BOOTSTRAP_ASSERT +ROLE=db REF=$SHA SKIP_FLYWAY=1 bash bootstrap.sh || finish BOOTSTRAP_FAIL + +[ -f "$RIG/restart.sh" ] || finish RIG_MISSING +grep -q 'ckpt_age_b' "$RIG/restart.sh" || finish RIG_ASSERT +grep -q '_tpl' /root/init/loadtest/results/binlog-replay-time-2026-11-07/_writer_common.sh || finish WRITER_ASSERT +docker ps --format '{{.Names}}' | grep -E '^shadowfit-' | xargs -r docker stop + +mkdir -p "$OUT" +{ echo "instance_type=$(curl -s -H "X-aws-ec2-metadata-token: $(curl -s -X PUT http://169.254.169.254/latest/api/token -H 'X-aws-ec2-metadata-token-ttl-seconds: 60')" http://169.254.169.254/latest/meta-data/instance-type)"; nproc; free -g; df -h /; } > "$OUT/box.txt" +env OUT=$OUT WORK=/root/rig_work bash "$RIG/restart.sh" > "$OUT/run.log" 2>&1 +rc=$? +echo "== rig exit $rc" +finish "DONE rc=$rc" diff --git a/loadtest/results/process-restart-rto-2026-11-08/launch.sh b/loadtest/results/process-restart-rto-2026-11-08/launch.sh new file mode 100644 index 00000000..349f9fd6 --- /dev/null +++ b/loadtest/results/process-restart-rto-2026-11-08/launch.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# 프로세스 재기동 라운드 EC2 1대(m6i.xlarge) — 사용: bash launch.sh (DRY=1 이면 --dry-run) +set -euo pipefail +export MSYS_NO_PATHCONV=1 +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SHA=${1:?SHA 가 필요하다} +git ls-remote origin | grep -q "^$SHA" || { echo "🔴 SHA $SHA 가 원격에 없다 — 먼저 푸시"; exit 1; } +RUN_ID=restart-ec2-$(date -u +%Y%m%d-%H%M%S) +REGION=ap-northeast-2 +AMI=$(aws ec2 describe-images --region $REGION --owners amazon \ + --filters "Name=name,Values=al2023-ami-2023*-kernel-6.12-x86_64" "Name=state,Values=available" \ + --query "reverse(sort_by(Images,&CreationDate))[:1].ImageId" --output text) +ud=$(mktemp) +sed -e "s|@@SHA@@|$SHA|" -e "s|@@RUN_ID@@|$RUN_ID|" -e "s|@@CAP_MIN@@|150|" "$HERE/ec2_userdata.sh" | tr -d ' ' > "$ud" # Windows CRLF 제거 (failover 스모크 1~4회차 교훈) +grep -qE '@@[A-Z_]+@@' "$ud" && { echo "🔴 채우지 않은 자리표시자가 있다"; exit 1; } +t_api=$(date +%s%3N) +id=$(aws ec2 run-instances --region $REGION --image-id "$AMI" --instance-type m6i.xlarge \ + --key-name shadowfit-measure --security-group-ids sg-0382132d2f8b5f6ce --subnet-id subnet-0f9ea5dacb7260d34 \ + --iam-instance-profile Name=shadowfit-measure \ + --instance-initiated-shutdown-behavior terminate \ + --block-device-mappings '[{"DeviceName":"/dev/xvda","Ebs":{"VolumeSize":200,"VolumeType":"gp3","DeleteOnTermination":true}}]' \ + --tag-specifications "ResourceType=instance,Tags=[{Key=Project,Value=shadowfit-measure},{Key=RunId,Value=$RUN_ID}]" \ + "ResourceType=volume,Tags=[{Key=Project,Value=shadowfit-measure}]" \ + --user-data "$(cat "$ud")" ${DRY:+--dry-run} --query 'Instances[0].InstanceId' --output text 2>&1) || { echo "$id"; [ -n "${DRY:-}" ] && exit 0; exit 1; } +echo "RUN_ID=$RUN_ID id=$id t_api=$t_api" diff --git a/loadtest/results/process-restart-rto-2026-11-08/restart.sh b/loadtest/results/process-restart-rto-2026-11-08/restart.sh new file mode 100644 index 00000000..26d0783a --- /dev/null +++ b/loadtest/results/process-restart-rto-2026-11-08/restart.sh @@ -0,0 +1,190 @@ +#!/bin/bash +# 프로세스 재기동 RTO — mysqld 가 죽은 순간부터 다시 쿼리를 받기까지 (RPO·RTO 분기점 §4 첫 줄 · §6-2 마지막 빈칸) +# +# 질문: OOM kill·크래시로 mysqld 만 죽고 디스크는 남았을 때, 다시 쓸 수 있기까지 얼마나 걸리나? +# 그 시간은 «죽는 순간 디스크 반영이 덜 된 변경(redo, 체크포인트 이후 몫)» 에 따라 늘어날 것으로 예상한다. +# +# 팔 (죽이는 순간의 쓰기 상태, 2026-11-08 사용자 confirm): +# idle 쓰기 없음 — 기준선 +# paced 문서 §8-1 가정 피크 45행/초(측정 아님)로 PACED_SECS 동안 쓰다가 죽임 +# stress 4개 연결이 쉬지 않고 STRESS_SECS 동안 쓰다가 죽임 — 상한 확인용, 운영 값 아님 +# 순서: 앞 세 판은 팔마다 하나씩 버림, 그 뒤 세 바퀴는 팔 순서를 돌린다. +# +# 재는 것 (한 판 한 줄): +# ckpt_age_b 죽이기 직전 «LSN − 마지막 체크포인트» (바이트) = 재기동 때 다시 적용할 redo 의 상한 +# dirty_pages 죽이기 직전 버퍼 풀의 더러운 페이지 수 +# ready_ms docker start 부터 «SELECT 1» 이 처음 성공하기까지 (docker exec 고정 비용 포함 — exec_ms 로 따로 남김) +# down_ms kill 부터 같은 시각까지 (= 사람·감시 시간을 뺀 재기동 RTO 하한) +# recov_* 서버 로그의 시각으로 쪼갠 구간 (크래시 복구 시작 → redo 적용 끝 → 연결 받음) +# lost_tx 클라이언트가 «커밋 성공» 을 받았는데 재기동 뒤 없는 트랜잭션 — flush=1·sync_binlog=1 이라 0 이어야 한다 +# +# 🔴 죽이기 = docker kill (SIGKILL). 컨테이너만 죽고 호스트·볼륨은 남는다 — 문서 §4 의 «프로세스 죽음» 그대로. +# 🔴 재시작은 바로 한다(감시·사람 시간 0). 실제 RTO 는 «죽은 걸 알아채고 다시 띄우는» 시간이 더해진다. +set -euo pipefail +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +OUT="${OUT:-$HERE/_out}" +WORK="${WORK:-$HERE/_work}" +mkdir -p "$OUT" "$WORK" + +IMG=${MYSQL_IMAGE:-mysql:8.0} +PW=repl +C=rs_db; VOL=rs_data; NET=rs_net +TRIALS=${TRIALS:-"idle paced stress stress idle paced paced stress idle idle stress paced"} +BURN=${BURN:-3} +PACED_ROWS_PER_S=${PACED_ROWS_PER_S:-45} +ROWS_PER_TX=${ROWS_PER_TX:-25} +STRESS_CONNS=${STRESS_CONNS:-4} +IDLE_SECS=${IDLE_SECS:-30} +PACED_SECS=${PACED_SECS:-120} +STRESS_SECS=${STRESS_SECS:-240} +SERVER_OPTS=(--server-id=1 --log-bin=binlog --binlog-format=ROW + --innodb-flush-log-at-trx-commit=1 --sync-binlog=1 + --innodb-buffer-pool-size=${BUFFER_POOL:-2G} --innodb-redo-log-capacity=${REDO_CAPACITY:-2G}) + +log() { printf '[restart] %s\n' "$*" >&2; } +die() { log "🔴 $*"; exit 1; } +q() { docker exec -i "$1" mysql -uroot -p"$PW" -N -s -e "$2" 2>/dev/null; } +now_ms() { date +%s%3N; } +source "$HERE/../binlog-replay-time-2026-11-07/_writer_common.sh" + +wait_ready() { + local i + for i in $(seq 1 300); do [ "$(q "$1" "SELECT @@skip_networking;")" = 0 ] && return 0; sleep 1; done + die "$1 가 5분 안에 쿼리를 안 받는다" +} + +make_paced_proc() { + cat > "$WORK/_paced.sql" <<'SQL' +DELIMITER // +DROP PROCEDURE IF EXISTS lab.paced_ack // +CREATE PROCEDURE lab.paced_ack(IN p_session BIGINT, IN p_rows INT, IN p_secs INT, IN p_gap DOUBLE) +BEGIN + DECLARE v_end DATETIME(6); + DECLARE v_off INT DEFAULT 0; + DECLARE v_ver BIGINT; + SET v_end = SYSDATE(6) + INTERVAL p_secs SECOND; + WHILE SYSDATE(6) < v_end DO + START TRANSACTION; + INSERT INTO lab.pose_data_scale (session_id, timestamp_sec, joint_coordinates, sync_rate, is_correct, feedback_message) + SELECT p_session, ts, jc, 75.0, 1, fb FROM lab._tpl WHERE n >= v_off AND n < v_off + p_rows; + UPDATE lab.session_probe SET version = version + 1, last_active_at = SYSDATE(6) WHERE id = p_session; + SELECT version INTO v_ver FROM lab.session_probe WHERE id = p_session; + COMMIT; + SELECT p_session, v_ver, ROUND(UNIX_TIMESTAMP(SYSDATE(6)) * 1000000); + SET v_off = (v_off + p_rows) % (750 - p_rows); + DO SLEEP(p_gap); + END WHILE; +END // +DELIMITER ; +SQL + docker exec -i "$C" mysql -uroot -p"$PW" < "$WORK/_paced.sql" 2>/dev/null || die "paced 프로시저 생성 실패" +} + +fresh_db() { + docker rm -f "$C" >/dev/null 2>&1 || true + docker volume rm -f "$VOL" >/dev/null 2>&1 || true + docker volume create "$VOL" >/dev/null + docker network create "$NET" >/dev/null 2>&1 || true + docker run -d --name "$C" --network "$NET" -e MYSQL_ROOT_PASSWORD="$PW" -v "$VOL:/var/lib/mysql" \ + "$IMG" "${SERVER_OPTS[@]}" >/dev/null + wait_ready "$C" + q "$C" " + CREATE DATABASE lab; + CREATE TABLE lab.pose_data_scale ( + id bigint NOT NULL AUTO_INCREMENT, session_id bigint NOT NULL, timestamp_sec double NOT NULL, + joint_coordinates text COLLATE utf8mb4_unicode_ci NOT NULL, sync_rate double DEFAULT NULL, + is_correct tinyint(1) DEFAULT 1, feedback_message varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id, created_at) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + CREATE TABLE lab.session_probe (id BIGINT PRIMARY KEY, version BIGINT NOT NULL DEFAULT 0, + last_active_at DATETIME(6) NULL) ENGINE=InnoDB;" || die "스키마 실패" + local i vals="" + for i in $(seq 1 "$STRESS_CONNS"); do vals="$vals${vals:+,}($((100000 + i)),0,NULL)"; done + q "$C" "INSERT INTO lab.session_probe VALUES $vals;" + setup_writer "$C" + make_paced_proc +} + +# 서버 로그에서 «t0 이후 처음 나온» 패턴의 시각(ms). docker logs --timestamps 의 RFC3339 나노초를 쓴다 +log_ms() { # $1 since(RFC3339) $2 패턴 + docker logs --timestamps --since "$1" "$C" 2>&1 | grep -m1 -E "$2" | awk '{print $1}' \ + | xargs -r -I{} date -d {} +%s%3N +} + +trial() { # $1 번호 $2 팔 $3 kind + local idx=$1 arm=$2 kind=$3 tdir="$WORK/t$1" i pids=() + mkdir -p "$tdir" + log "── 판 $idx ($kind) 팔=$arm" + fresh_db + case "$arm" in + idle) sleep "$IDLE_SECS" ;; + paced) + local gap; gap=$(awk -v r="$ROWS_PER_TX" -v p="$PACED_ROWS_PER_S" 'BEGIN{printf "%.3f", r/p}') + docker run --rm --network "$NET" "$IMG" mysql -h "$C" -uroot -p"$PW" --unbuffered -N -B \ + -e "CALL lab.paced_ack(100001, $ROWS_PER_TX, 36000, $gap);" > "$tdir/w_1.tsv" 2>/dev/null & + pids+=($!); sleep "$PACED_SECS" ;; + stress) + for i in $(seq 1 "$STRESS_CONNS"); do + docker run --rm --network "$NET" "$IMG" mysql -h "$C" -uroot -p"$PW" --unbuffered -N -B \ + -e "CALL lab.load_run($((100000 + i)), $ROWS_PER_TX, 36000);" > "$tdir/w_$i.tsv" 2>/dev/null & + pids+=($!) + done + sleep "$STRESS_SECS" ;; + esac + + # 죽이기 직전 상태 — redo 체크포인트 나이 · 더러운 페이지 + local st lsn ckpt dirty + st=$(docker exec "$C" mysql -uroot -p"$PW" -e "SHOW ENGINE INNODB STATUS\G" 2>/dev/null) + lsn=$(awk '/^Log sequence number/{print $4; exit}' <<<"$st") + ckpt=$(awk '/^Last checkpoint at/{print $4; exit}' <<<"$st") + dirty=$(q "$C" "SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME='Innodb_buffer_pool_pages_dirty';") + local o0 o1 exec_ms; o0=$(now_ms); q "$C" "SELECT 1;" >/dev/null; o1=$(now_ms); exec_ms=$((o1 - o0)) + + local since t_kill t_start t_ready + since=$(date -u +%Y-%m-%dT%H:%M:%S.%NZ) + t_kill=$(now_ms) + docker kill "$C" >/dev/null # SIGKILL — OOM kill 과 같은 신호 + for i in "${pids[@]}"; do wait "$i" 2>/dev/null || true; done + t_start=$(now_ms) + docker start "$C" >/dev/null + until q "$C" "SELECT 1;" >/dev/null; do sleep 0.05; done + t_ready=$(now_ms) + + local r_start r_redo_done r_conn + r_start=$(log_ms "$since" "Starting crash recovery|Crash recovery" || true) + r_redo_done=$(log_ms "$since" "Apply batch completed|redo log processing" || true) + r_conn=$(log_ms "$since" "ready for connections" || true) + docker logs --timestamps --since "$since" "$C" > "$tdir/server.log" 2>&1 || true + + # 잃은 쓰기 — 클라이언트가 받은 마지막 version 합 vs 재기동 뒤 합 + local acked rest lost=NA + if [ "$arm" != idle ]; then + acked=$(for f in "$tdir"/w_*.tsv; do awk 'NF>=3{v=$2} END{print v+0}' "$f"; done | awk '{s+=$1} END{print s+0}') + rest=$(q "$C" "SELECT COALESCE(SUM(version),0) FROM lab.session_probe;") + lost=$((acked - rest)) + fi + printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \ + "$idx" "$kind" "$arm" "$((lsn - ckpt))" "${dirty:-NA}" "$((t_ready - t_start))" "$((t_ready - t_kill))" \ + "${r_start:+$((r_start - t_start))}" "${r_redo_done:+$((r_redo_done - t_start))}" "${r_conn:+$((r_conn - t_start))}" \ + "$exec_ms" "$lost" "$((t_start - t_kill))" >> "$OUT/trials.tsv" + log "판 $idx: redo $(( (lsn - ckpt) / 1048576 ))MB · 더러운 페이지 ${dirty} → 재기동 $((t_ready - t_start))ms · 잃은 트랜잭션 $lost" +} + +printf 'trial\tkind\tarm\tckpt_age_b\tdirty_pages\tready_ms\tdown_ms\trecov_start_ms\tredo_done_ms\tconn_ms\texec_ms\tlost_tx\tkill_to_start_ms\n' > "$OUT/trials.tsv" +{ + echo "# 프로세스 재기동 실험 조건 ($(date -Is))" + echo "mysql image : $IMG ($(docker image inspect -f '{{index .RepoDigests 0}}' "$IMG" 2>/dev/null))" + echo "server opts : ${SERVER_OPTS[*]}" + echo "trials : $TRIALS (앞 $BURN 판 버림)" + echo "idle ${IDLE_SECS}s · paced ${PACED_ROWS_PER_S}행/초(문서 §8-1 가정) ${PACED_SECS}s · stress ${STRESS_CONNS}연결 ${STRESS_SECS}s" + echo "recov_*/conn_ms 는 docker start 기준, 서버 로그 시각(초 단위 정밀도는 로그 형식에 따름)" +} > "$OUT/CONDITIONS.txt" +n=0 +for arm in $TRIALS; do + n=$((n + 1)) + trial "$n" "$arm" "$([ $n -le "$BURN" ] && echo 버림 || echo 본판)" +done +cp -r "$WORK"/t* "$OUT/" 2>/dev/null || true +find "$OUT" -name 'w_*.tsv' -size +5M -delete # writer 기록은 크면 버린다(합만 쓰면 된다) +docker rm -f "$C" >/dev/null 2>&1 || true +log "완료 → $OUT/trials.tsv" From 37dad96c835d15c02392a4a594453e0a4f7dc1ad Mon Sep 17 00:00:00 2001 From: Khyojae Date: Sun, 8 Nov 2026 20:24:00 +0900 Subject: [PATCH 2/2] =?UTF-8?q?measure(rpo-rto):=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=EC=84=B8=EC=8A=A4=20=EC=9E=AC=EA=B8=B0=EB=8F=99=20RTO=20?= =?UTF-8?q?=EA=B2=B0=EA=B3=BC=20+=20=EB=AA=A9=ED=91=9C=20RTO=20=ED=99=95?= =?UTF-8?q?=EC=A0=95=20=E2=80=94=20=EC=89=BC=201.3=EC=B4=88=20=C2=B7=20?= =?UTF-8?q?=EA=B0=80=EC=A0=95=20=ED=94=BC=ED=81=AC=201.9~2.4=EC=B4=88=20?= =?UTF-8?q?=C2=B7=20=EC=B5=9C=EB=8C=80=20=EC=93=B0=EA=B8=B0=20=EC=95=BD=20?= =?UTF-8?q?34=EC=B4=88,=20=EC=9E=83=EC=9D=80=200=20(EC2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - loadtest/results/process-restart-rto-2026-11-08/README.md + ec2/ 요약 산출물 (판별 원본은 S3) - rpo-rto-targets.md §6-3: 목표 RTO 확정(사용자 confirm) — 장애 종류별·시간대별, 사람 시간 포함(알림 전제). 프로세스 죽음 19초 · 호스트 상실 저녁 약 37분 / 낮 약 67분 / 새벽 기상 뒤 약 7분. 리플리카(3) 보류 유지. Co-Authored-By: Claude Opus 5.5 --- docs/decisions/rpo-rto-targets.md | 35 ++- .../process-restart-rto-2026-11-08/README.md | 65 ++++++ .../ec2/CONDITIONS.txt | 6 + .../ec2/box.txt | 7 + .../ec2/round.log | 201 ++++++++++++++++++ .../ec2/run.log | 25 +++ .../ec2/trials.tsv | 13 ++ 7 files changed, 349 insertions(+), 3 deletions(-) create mode 100644 loadtest/results/process-restart-rto-2026-11-08/README.md create mode 100644 loadtest/results/process-restart-rto-2026-11-08/ec2/CONDITIONS.txt create mode 100644 loadtest/results/process-restart-rto-2026-11-08/ec2/box.txt create mode 100644 loadtest/results/process-restart-rto-2026-11-08/ec2/round.log create mode 100644 loadtest/results/process-restart-rto-2026-11-08/ec2/run.log create mode 100644 loadtest/results/process-restart-rto-2026-11-08/ec2/trials.tsv diff --git a/docs/decisions/rpo-rto-targets.md b/docs/decisions/rpo-rto-targets.md index 5f51d60a..873adaeb 100644 --- a/docs/decisions/rpo-rto-targets.md +++ b/docs/decisions/rpo-rto-targets.md @@ -1,7 +1,7 @@ # RPO·RTO 목표 — «얼마나 잃어도 되고, 얼마 안에 돌아와야 하나» 를 정하는 분기점 작성일: 2026-11-07 · 갱신: 2026-11-08 (§8-1 읽기 : 쓰기 비율 가정 추가 · §6-2 측정 둘 — 재생 시간·호스트 상실 RPO · 측정 셋 — 새 서버 준비·반출 업로드·리플리카 승격) -상태: 🟡 **방향 확정 · 숫자 미정.** 구성 2(백업 + binlog 서버 밖 반출) 채택, 리플리카 보류, 반동기 미채택 +상태: 🟡 **방향 확정 · 목표 RTO 확정(11-08, §6-3) · 나머지 숫자 미정.** 구성 2(백업 + binlog 서버 밖 반출) 채택, 리플리카 보류, 반동기 미채택 (2026-11-07 사용자 confirm). **반출 주기·백업 주기·보존 기간 숫자는 아직 없다** — 추천안이 숫자를 정하지 않았고, 근거 없이 채우지 않는다(§6 Q4·Q6). 연관: [`./backup-restore-rto-rpo.md`](./backup-restore-rto-rpo.md) (복구 시간 실측) · @@ -143,8 +143,7 @@ Flyway 마이그레이션의 테이블 기준. «재생성» 칸은 코드·문 🟡 **반출 주기 숫자는 미정** — 추천안이 정하지 않았다. 고르는 근거는 «주기를 줄일수록 늘어나는 것» (업로드 횟수·`FLUSH BINARY LOGS` 빈도·작은 binlog 파일 수) — 11-08 에 쟀다: 가정 피크에서 반출 1번에 업로드 0.48~0.71초·FLUSH 52~56ms, 하루 횟수는 60초 주기 1,440번 · 300초 주기 288번(§6-2). FLUSH 가 쓰기를 막는 시간과 요금은 안 쟀다 -- [~] **Q5. 호스트 상실 RTO** → 🟢 **리플리카(3) 보류.** 🟡 목표 숫자는 미정 — 구성 2 의 실제 RTO(백업 붓기 + - **binlog 재생**)를 먼저 재고, 그 값이 받아들일 수 없을 때 3 을 다시 연다 +- [x] **Q5. 목표 RTO** → 🟢 **장애 종류별 · 시간대별 목표 확정, 리플리카(3) 보류 유지** (2026-11-08 사용자 confirm, §6-3) - [~] **Q6. 백업 주기·보관 위치·보존 기간** → 🟢 **위치는 서버 밖**(구성 2 의 정의). 🟡 **주기·보존 기간은 미정** — 운영 호스트가 정해지지 않았다(`31-production-readiness-plan.md:218` 과 같은 이유) - [x] **반동기(4)** → 🟢 **채택 안 함** (§6-1 근거) @@ -159,6 +158,31 @@ Flyway 마이그레이션의 테이블 기준. «재생성» 칸은 코드·문 - **반동기(4) 는 채택 안 함 쪽.** 근거: 쓰기가 무거운 경로(`pose_data` 적재)에 대가가 붙고, 그 데이터의 상당 부분은 원래 버리는 버퍼(E 등급)이며, 리플리카 1대 구성에선 조용히 강등된다 +### 6-3. 목표 RTO — ✅ 2026-11-08 사용자 confirm + +정하는 방식 (셋 다 추천대로): +- **장애 종류별로** 따로 둔다 — 복구 경로(자동 재기동 · 사람이 새 서버 · 사람이 PITR)가 다르다 +- **사람 시간을 넣는다** — 알림이 휴대폰에 온다는 전제. 사용자가 겪는 시간과 맞추려고 +- **시간대별로** 나눈다 — 운동이 몰리는 저녁에 운영자도 깨어 있고, 운영자가 못 보는 새벽엔 사용자가 적다. + 🔴 «저녁이 붐빈다» 는 상식 가정이다 — 시간대별 사용량 실측은 없다(출시 전). 출시 뒤 사용 기록으로 확인할 것 + +| 장애 | 시간대 | 목표 | 어디서 나온 숫자 | 지금 맞나 | +|---|---|---|---|---| +| MySQL 프로세스 죽음 | 하루 종일 | **19초** | AI→Spring 결과 전달 재시도 한도(약 19초, `spring_client.py`) — 넘으면 방금 끝난 운동이 `FAILED` | 가정 피크 ✅(재기동 1.9~2.4초) · 최대 쓰기 ❌(33.6~35.1초, redo 약 1.35GB) — [프로세스 재기동 실측](../../loadtest/results/process-restart-rto-2026-11-08/README.md) | +| 호스트 상실 | 저녁 18시~01시 | **약 37분** | 사람 30분(사용자 답) + 구성 2 복구 약 7분(실측 합) | 🟡 7분은 서로 다른 실험의 합 — 처음부터 끝까지 한 번에 해 본 적 없음 | +| 호스트 상실 | 낮 08시~18시 | **약 67분** | 사람 60분(사용자 답) + 약 7분 | 위와 같음 | +| 호스트 상실 | 새벽 01시~08시 | **기상(08시) 뒤 손대기 시작 + 약 7분** — 최악 약 7시간 | 운영자 수면 | 위와 같음 | +| 논리 사고 | — | 🟡 미정 | | | + +**리플리카(3) 를 보류로 두는 이유**: 리플리카가 줄이는 건 기술 시간(약 7분 → 승격 약 1.2초 + 앱 전환 수 초~약 2분)뿐이고 +사람 시간은 그대로다. 저녁 기준 약 37분 → 약 31~32분(약 14%)인데 대가는 서버 1대 상시 + 복제 감시다. +37분을 줄이려면 리플리카보다 **알림 속도·복구 절차 한 번에 실행**이 먼저다. + +목표를 지키려면 남은 일 (목표의 전제): +- 🔴 **장애 알림이 없다** — 사람 30·60분은 알림이 있다는 전제다 (모니터링 점검 #673~680 미완) +- 🔴 **복구 훈련 한 번** — 구성 2 를 처음부터 끝까지 실제로 돌려 7분을 확인(R1) +- 🟡 **최대 쓰기에서 프로세스 재기동 19초 초과** — 재기동 시간이 쌓인 redo 양에 비례한다. 줄일 방법은 미정 + ### 6-2. 결정 전에 재면 좋은 것 | 무엇 | 왜 | 어디서 | @@ -273,3 +297,8 @@ DAU 와 비교해 판단을 닫지 않는다는 원칙과도 어긋난다([[feed ③ [승격 실측](../../loadtest/results/replica-promotion-2026-11-08/README.md): DB 쪽 승격 **밀림 없으면 약 0.25초 · 약 21초 밀리면 약 7.8초**(본판 8, 잃은 트랜잭션 0). 한 박스 동거라 «잃은 0» 은 다른 박스에선 성립하지 않고, 감지·결정·앱 연결 전환 시간은 빠져 있다. → Q5 를 다시 열지는 «구성 2 의 약 7분이 목표 RTO 안에 드나» 로 가른다 — 목표 숫자는 아직 없다. +- 2026-11-08: 🟢 **Q5 목표 RTO 확정** (사용자 confirm, §6-3). 장애 종류별·시간대별, 사람 시간 포함(알림 전제). + 프로세스 죽음 19초 · 호스트 상실 저녁 약 37분 / 낮 약 67분 / 새벽 기상 뒤 약 7분 · 논리 사고 미정. + 리플리카(3) 는 **보류 유지** — 줄이는 게 기술 시간 약 7분뿐이고 사람 시간이 대부분이라서. + 근거로 쓴 같은 날 실측 둘: [프로세스 재기동](../../loadtest/results/process-restart-rto-2026-11-08/README.md)(쉼 1.3초 · 가정 피크 1.9~2.4초 · 최대 쓰기 33.6~35.1초, 잃은 0) · + [페일오버 앱 전환](../../loadtest/results/failover-app-reconnect-2026-11-08/README.md)(앱 재시작 약 106초 · DNS 5~30초, 단 네트워크 끊김에서 요청 약 14개가 약 16분 매달림, 잃은 0). diff --git a/loadtest/results/process-restart-rto-2026-11-08/README.md b/loadtest/results/process-restart-rto-2026-11-08/README.md new file mode 100644 index 00000000..1f54991b --- /dev/null +++ b/loadtest/results/process-restart-rto-2026-11-08/README.md @@ -0,0 +1,65 @@ +# 프로세스 재기동 RTO — mysqld 가 죽은 순간부터 다시 쿼리를 받기까지 (2026-11-08, EC2) + +설계 문서: [`../../../docs/decisions/rpo-rto-targets.md`](../../../docs/decisions/rpo-rto-targets.md) §4 첫 줄 · §6-3 (목표 RTO) +무대: **EC2 `m6i.xlarge` 1대** · gp3 200GB · MySQL **8.0.46**(`mysql:8.0`) · `flush=1`·`sync_binlog=1` · binlog ROW · +버퍼풀 2GB · redo 용량 2GB · RUN_ID `restart-ec2-20261108-060158` · 커밋 `22ce55f4` · 12판(버림 3 + 본판 9) + +질문: OOM kill·크래시로 mysqld 만 죽고 디스크는 남았을 때(문서 §4 «프로세스 죽음»), 다시 쓸 수 있기까지 얼마나 걸리나? +그 시간은 죽는 순간 «디스크 반영이 덜 된 변경(redo)» 양에 따라 늘 것으로 예상했다. + +## 0. 한 줄 + +**쉬는 중 1.3초 · 가정 피크(초당 45행) 1.9~2.4초 · 최대 쓰기 33.6~35.1초 — 재기동 시간은 쌓인 redo 양을 따라간다. 잃은 트랜잭션은 0.** +목표 RTO 19초(§6-3)는 가정 피크에선 맞고, 최대 쓰기에선 못 맞춘다. + +## 1. 결과 (본판) + +| 팔 | 판 | 죽일 때 redo | 더러운 페이지 | **재기동** (`docker start` → 첫 `SELECT 1`) | 죽은 순간부터 | 잃은 트랜잭션 | +|---|---:|---:|---:|---:|---:|---:| +| idle | 5 | 0 | 0 | **1,279ms** | 1,677ms | — | +| idle | 9 | 0 | 0 | **1,349ms** | 1,700ms | — | +| idle | 10 | 0 | 0 | **1,363ms** | 1,782ms | — | +| paced | 6 | 14MB | 961 | **2,446ms** | 2,883ms | 0 | +| paced | 7 | 14MB | 948 | **1,886ms** | 2,351ms | 0 | +| paced | 12 | 14MB | 949 | **2,414ms** | 2,910ms | 0 | +| stress | 4 | 1,334MB | 73,194 | **35,058ms** | 36,156ms | 0 (−2) | +| stress | 8 | 1,348MB | 73,361 | **34,356ms** | 35,481ms | 0 (−2) | +| stress | 11 | 1,355MB | 73,626 | **33,577ms** | 34,716ms | 0 (−4) | + +(버림판: idle 1,322ms · paced 2,393ms · stress 31,990ms — 본판과 같은 범위) + +- **redo 14MB → 약 2초, 약 1.35GB → 약 34초.** 쉬는 중의 약 1.3초는 redo 와 무관한 고정 비용(컨테이너·서버 기동 + rig 의 `docker exec` 약 41ms)이다 +- **잃은 트랜잭션 0.** 음수(−2·−4)는 «클라이언트가 성공 응답을 받은 것보다 DB 에 더 있다» 는 뜻이다 — 서버는 커밋했는데 응답이 돌아오기 전에 죽은 요청. + 연결 수(4) 를 넘지 않는다. `flush=1`·`sync_binlog=1` 의 약속 그대로다 +- stress 는 체크포인트가 쓰기를 못 따라가 redo 가 용량(2GB)의 약 3분의 2 까지 쌓인 상태였다 + +## 2. 목표 RTO 와 나란히 (§6-3) + +| 상황 | 재기동 | 목표 19초 | +|---|---|---| +| 쉼 | 1.3초 | ✅ | +| 가정 피크 (초당 45행) | 1.9~2.4초 | ✅ | +| 최대 쓰기 (4연결 240초) | 33.6~35.1초 | ❌ | + +19초는 AI→Spring 결과 전달 재시도 한도(약 19초)에서 왔다 — 넘으면 방금 끝난 운동이 `FAILED` 가 된다. +최대 쓰기에서 못 맞추는 걸 어떻게 할지(redo 용량·체크포인트 설정 등)는 **정하지 않았다** — 재기동 시간을 줄이는 값은 평소 쓰기 비용과 맞바꾸는 것이라 따로 재야 한다. + +## 3. 조건 — 인용할 때 떼면 안 되는 것 + +1. **감시·사람 시간 0.** 죽이고 바로 `docker start` 했다. 운영의 `restart: unless-stopped` 가 다시 띄우기까지의 시간은 안 쟀다 +2. «죽음» 은 `docker kill`(SIGKILL) — 컨테이너만 죽고 호스트·볼륨은 남는다. 호스트 상실은 [다른 실험](../hostloss-rpo-2026-11-07/README.md) +3. paced 는 문서 §8-1 **가정** 피크(측정 아님), stress 는 4개 연결이 240초간 최대 속도 — 상한 확인용이지 운영 값이 아니다 +4. 🟡 **redo 적용에 걸린 시간만 따로는 못 뽑았다.** 서버 로그에서 «복구 시작 · redo 적용 끝» 시각을 뽑으려 했는데(`recov_start_ms`·`redo_done_ms`) + 전부 비었다 — 로그 문구 매칭이 8.0.46 출력과 안 맞은 것으로 보인다(확인 안 함). 그래서 «34초 중 redo 적용이 몇 초» 는 말할 수 없다 +5. 버퍼풀 2GB · redo 2GB 는 rig 의 값이다. 운영 설정에선 쌓이는 redo 상한이 달라 stress 의 숫자가 달라진다 +6. 🟡 `round.log` 의 `bootstrap.sh: line 281: ... command not found` 는 이 rig 이 쓰지 않는 단계의 오류다 — 측정과 무관 + +## 4. 재현 + +```bash +bash launch.sh <커밋 SHA> # EC2 1대. 결과는 S3 의 RUN_ID 아래 +OUT=$PWD/_out PACED_SECS=30 STRESS_SECS=60 bash restart.sh # 로컬 스모크 +``` + +산출물: `ec2/trials.tsv`(판 1줄) · `ec2/CONDITIONS.txt` · `ec2/run.log` · `ec2/box.txt` · `ec2/round.log`. +판별 원본(쓰기 클라이언트 `w_*.tsv`, 판별 서버 로그)은 저장소에 안 넣었다 — S3 `shadowfit/restart-ec2-20261108-060158/out/t*/`. diff --git a/loadtest/results/process-restart-rto-2026-11-08/ec2/CONDITIONS.txt b/loadtest/results/process-restart-rto-2026-11-08/ec2/CONDITIONS.txt new file mode 100644 index 00000000..7d47ae21 --- /dev/null +++ b/loadtest/results/process-restart-rto-2026-11-08/ec2/CONDITIONS.txt @@ -0,0 +1,6 @@ +# 프로세스 재기동 실험 조건 (2026-11-08T06:04:43+00:00) +mysql image : mysql:8.0 (mysql@sha256:7dcddc01f13bab2f15cde676d44d01f61fc9f99fe7785e86196dfc07d358ae2b) +server opts : --server-id=1 --log-bin=binlog --binlog-format=ROW --innodb-flush-log-at-trx-commit=1 --sync-binlog=1 --innodb-buffer-pool-size=2G --innodb-redo-log-capacity=2G +trials : idle paced stress stress idle paced paced stress idle idle stress paced (앞 3 판 버림) +idle 30s · paced 45행/초(문서 §8-1 가정) 120s · stress 4연결 240s +recov_*/conn_ms 는 docker start 기준, 서버 로그 시각(초 단위 정밀도는 로그 형식에 따름) diff --git a/loadtest/results/process-restart-rto-2026-11-08/ec2/box.txt b/loadtest/results/process-restart-rto-2026-11-08/ec2/box.txt new file mode 100644 index 00000000..155c7ec8 --- /dev/null +++ b/loadtest/results/process-restart-rto-2026-11-08/ec2/box.txt @@ -0,0 +1,7 @@ +instance_type=m6i.xlarge +4 + total used free shared buff/cache available +Mem: 15 0 10 0 4 14 +Swap: 0 0 0 +Filesystem Size Used Avail Use% Mounted on +/dev/nvme0n1p1 200G 6.8G 194G 4% / diff --git a/loadtest/results/process-restart-rto-2026-11-08/ec2/round.log b/loadtest/results/process-restart-rto-2026-11-08/ec2/round.log new file mode 100644 index 00000000..5233a34f --- /dev/null +++ b/loadtest/results/process-restart-rto-2026-11-08/ec2/round.log @@ -0,0 +1,201 @@ +== start 2026-11-08T06:02:27+00:00 SHA=22ce55f486bfc80d7ab58558cd87e9ba0ba805c1 RUN_ID=restart-ec2-20261108-060158 CAP_MIN=150 + +──── root SSH (다른 박스가 이 박스로 붙는 라운드용, #642) ──── + ec2-user 의 authorized_keys 를 root 로 복사(강제명령 줄 제거), PermitRootLogin prohibit-password + +──── 패키지 ──── + buildx 0.12.2 → 0.19.3 으로 올린다 (compose build 는 0.17+ 를 요구한다) + +──── 저장소 — https://github.com/Shadowfit/init.git @ 22ce55f486bfc80d7ab58558cd87e9ba0ba805c1 ──── + 커밋: 22ce55f4 + +──── .env ──── +bootstrap.sh: line 281: compose: command not found +bootstrap.sh: line 281: shadowfit-ai: command not found +bootstrap.sh: line 281: INTERNAL_API_TOKEN:?: command not found +bootstrap.sh: line 281: MYSQL_EXPORTER_PASSWORD: command not found +bootstrap.sh: line 281: :?: command not found +bootstrap.sh: line 281: shadowfit-ai: command not found +bootstrap.sh: line 281: INTERNAL_API_TOKEN: command not found +bootstrap.sh: line 281: unused-in-this-role: command not found + MYSQL_ROOT_PASSWORD 를 rig 기본값과 맞췄다 (+ 해석만 되면 되는 변수들) + compose 해석 확인 ✅ (mysql 만 띄워도 파일 전체가 해석된다) + +──── MySQL 컨테이너 ──── +time="2026-11-08T06:03:24Z" level=warning msg="/root/init/docker-compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion" + Image mysql:8.0 Pulling + edf85873f64e Pulling fs layer 0B + 6ef6c7b50a93 Pulling fs layer 0B + e3e5d1ac74c1 Pulling fs layer 0B + 0d74d296605b Pulling fs layer 0B + 297d04cfe470 Pulling fs layer 0B + 4c8a3e0d4e4b Pulling fs layer 0B + a63160a5eda1 Pulling fs layer 0B + 7534d1db9f8d Pulling fs layer 0B + 49ec2dab01d9 Pulling fs layer 0B + ab24264a27e9 Pulling fs layer 0B + 96d30d9fbee8 Pulling fs layer 0B + 297d04cfe470 Waiting 0B + 4c8a3e0d4e4b Waiting 0B + 7534d1db9f8d Waiting 0B + 49ec2dab01d9 Waiting 0B + a63160a5eda1 Waiting 0B + ab24264a27e9 Waiting 0B + 96d30d9fbee8 Waiting 0B + 0d74d296605b Waiting 0B + e3e5d1ac74c1 Downloading 16.38kB + 6ef6c7b50a93 Downloading 884B + 6ef6c7b50a93 Verifying Checksum 0B + 6ef6c7b50a93 Download complete 0B + e3e5d1ac74c1 Download complete 0B + edf85873f64e Downloading 488.4kB + edf85873f64e Verifying Checksum 0B + edf85873f64e Download complete 0B + edf85873f64e Extracting 491.5kB + edf85873f64e Extracting 5.407MB + edf85873f64e Extracting 8.356MB + edf85873f64e Extracting 17.69MB + edf85873f64e Extracting 27.03MB + 297d04cfe470 Downloading 2.607kB + 297d04cfe470 Verifying Checksum 0B + 297d04cfe470 Download complete 0B + 0d74d296605b Downloading 64.9kB + 0d74d296605b Verifying Checksum 0B + 0d74d296605b Download complete 0B + edf85873f64e Extracting 32.93MB + 4c8a3e0d4e4b Downloading 335B + 4c8a3e0d4e4b Verifying Checksum 0B + 4c8a3e0d4e4b Download complete 0B + edf85873f64e Extracting 42.76MB + edf85873f64e Extracting 44.24MB + edf85873f64e Extracting 47.31MB + edf85873f64e Pull complete 0B + 6ef6c7b50a93 Extracting 884B + 6ef6c7b50a93 Extracting 884B + 6ef6c7b50a93 Pull complete 0B + e3e5d1ac74c1 Extracting 32.77kB + e3e5d1ac74c1 Extracting 783.6kB + e3e5d1ac74c1 Extracting 783.6kB + e3e5d1ac74c1 Pull complete 0B + 0d74d296605b Extracting 65.54kB + a63160a5eda1 Downloading 502.3kB + 7534d1db9f8d Downloading 316B + 7534d1db9f8d Verifying Checksum 0B + 7534d1db9f8d Download complete 0B + 0d74d296605b Extracting 2.884MB + a63160a5eda1 Downloading 42.36MB + 49ec2dab01d9 Downloading 536.5kB + a63160a5eda1 Verifying Checksum 0B + a63160a5eda1 Download complete 0B + 0d74d296605b Extracting 6.173MB + 0d74d296605b Pull complete 0B + 297d04cfe470 Extracting 2.607kB + 297d04cfe470 Extracting 2.607kB + 297d04cfe470 Pull complete 0B + 4c8a3e0d4e4b Extracting 335B + 4c8a3e0d4e4b Extracting 335B + 4c8a3e0d4e4b Pull complete 0B + a63160a5eda1 Extracting 524.3kB + 49ec2dab01d9 Downloading 44.08MB + a63160a5eda1 Extracting 7.864MB + 49ec2dab01d9 Downloading 88.05MB + a63160a5eda1 Extracting 14.16MB + 49ec2dab01d9 Verifying Checksum 0B + 49ec2dab01d9 Download complete 0B + a63160a5eda1 Extracting 23.07MB + ab24264a27e9 Downloading 5.327kB + ab24264a27e9 Verifying Checksum 0B + ab24264a27e9 Download complete 0B + a63160a5eda1 Extracting 32.51MB + a63160a5eda1 Extracting 40.37MB + 96d30d9fbee8 Downloading 120B + 96d30d9fbee8 Download complete 0B + a63160a5eda1 Extracting 48.23MB + a63160a5eda1 Extracting 49.93MB + a63160a5eda1 Pull complete 0B + 7534d1db9f8d Extracting 316B + 7534d1db9f8d Extracting 316B + 7534d1db9f8d Pull complete 0B + 49ec2dab01d9 Extracting 557.1kB + 49ec2dab01d9 Extracting 9.47MB + 49ec2dab01d9 Extracting 17.83MB + 49ec2dab01d9 Extracting 22.28MB + 49ec2dab01d9 Extracting 28.41MB + 49ec2dab01d9 Extracting 31.75MB + 49ec2dab01d9 Extracting 32.31MB + 49ec2dab01d9 Extracting 32.87MB + 49ec2dab01d9 Extracting 33.42MB + 49ec2dab01d9 Extracting 33.98MB + 49ec2dab01d9 Extracting 34.54MB + 49ec2dab01d9 Extracting 35.65MB + 49ec2dab01d9 Extracting 36.21MB + 49ec2dab01d9 Extracting 37.32MB + 49ec2dab01d9 Extracting 37.88MB + 49ec2dab01d9 Extracting 38.44MB + 49ec2dab01d9 Extracting 38.99MB + 49ec2dab01d9 Extracting 39.55MB + 49ec2dab01d9 Extracting 40.11MB + 49ec2dab01d9 Extracting 41.22MB + 49ec2dab01d9 Extracting 42.34MB + 49ec2dab01d9 Extracting 42.89MB + 49ec2dab01d9 Extracting 43.45MB + 49ec2dab01d9 Extracting 44.56MB + 49ec2dab01d9 Extracting 45.68MB + 49ec2dab01d9 Extracting 46.24MB + 49ec2dab01d9 Extracting 47.91MB + 49ec2dab01d9 Extracting 49.58MB + 49ec2dab01d9 Extracting 52.36MB + 49ec2dab01d9 Extracting 60.72MB + 49ec2dab01d9 Extracting 69.07MB + 49ec2dab01d9 Extracting 77.43MB + 49ec2dab01d9 Extracting 86.34MB + 49ec2dab01d9 Extracting 95.81MB + 49ec2dab01d9 Extracting 106.4MB + 49ec2dab01d9 Extracting 117MB + 49ec2dab01d9 Extracting 127MB + 49ec2dab01d9 Extracting 129.4MB + 49ec2dab01d9 Pull complete 0B + ab24264a27e9 Extracting 5.327kB + ab24264a27e9 Extracting 5.327kB + ab24264a27e9 Pull complete 0B + 96d30d9fbee8 Extracting 120B + 96d30d9fbee8 Extracting 120B + 96d30d9fbee8 Pull complete 0B + Image mysql:8.0 Pulled + Network init_shadowfit-net Creating + Volume init_mysql_data Creating + Volume init_mysql_data Creating + Network init_shadowfit-net Creating + Volume init_mysql_data Created + Volume init_mysql_data Created + Network init_shadowfit-net Created + Network init_shadowfit-net Created + Container shadowfit-mysql Creating + Container shadowfit-mysql Created + Container shadowfit-mysql Starting + Container shadowfit-mysql Started + 준비 대기... — 떴다 (TCP 인증 질의 연속 2회) + +──── percona-toolkit 이미지 ──── +docker.io/percona/percona-toolkit:latest + +──── xtrabackup 이미지 (P3 백업 · P4 복제) — percona/percona-xtrabackup:8.0 ──── +docker.io/percona/percona-xtrabackup:8.0 + +──── 스키마 — Flyway 마이그레이션 ──── + SKIP_FLYWAY=1 — 건너뛴다. 從 R1·R3 은 «측정 대상 부재» 로 찍힌다 + +──── 준비됨 ──── + 작업 디렉터리 : /root/init + 커밋 : 22ce55f4 (22ce55f486bfc80d7ab58558cd87e9ba0ba805c1) + 디스크 여유 : 193G + MySQL : 8.0.46 + +다음: + cd /root/init + S3_BASE=s3://<버킷>/<프리픽스> nohup bash loadtest/aws/run_all.sh > /root/run_all.log 2>&1 & + + ⚠️ nohup 없이 & 만 붙이면 SSH 가 끊길 때 같이 죽는다. +shadowfit-mysql +== rig exit 0 +== finish: DONE rc=0 (2026-11-08T06:35:42+00:00) diff --git a/loadtest/results/process-restart-rto-2026-11-08/ec2/run.log b/loadtest/results/process-restart-rto-2026-11-08/ec2/run.log new file mode 100644 index 00000000..ed6372d7 --- /dev/null +++ b/loadtest/results/process-restart-rto-2026-11-08/ec2/run.log @@ -0,0 +1,25 @@ +[restart] ── 판 1 (버림) 팔=idle +[restart] 판 1: redo 0MB · 더러운 페이지 0 → 재기동 1322ms · 잃은 트랜잭션 NA +[restart] ── 판 2 (버림) 팔=paced +[restart] 판 2: redo 14MB · 더러운 페이지 962 → 재기동 2393ms · 잃은 트랜잭션 0 +[restart] ── 판 3 (버림) 팔=stress +[restart] 판 3: redo 1354MB · 더러운 페이지 72923 → 재기동 31990ms · 잃은 트랜잭션 0 +[restart] ── 판 4 (본판) 팔=stress +[restart] 판 4: redo 1334MB · 더러운 페이지 73194 → 재기동 35058ms · 잃은 트랜잭션 -2 +[restart] ── 판 5 (본판) 팔=idle +[restart] 판 5: redo 0MB · 더러운 페이지 0 → 재기동 1279ms · 잃은 트랜잭션 NA +[restart] ── 판 6 (본판) 팔=paced +[restart] 판 6: redo 14MB · 더러운 페이지 961 → 재기동 2446ms · 잃은 트랜잭션 0 +[restart] ── 판 7 (본판) 팔=paced +[restart] 판 7: redo 14MB · 더러운 페이지 948 → 재기동 1886ms · 잃은 트랜잭션 0 +[restart] ── 판 8 (본판) 팔=stress +[restart] 판 8: redo 1348MB · 더러운 페이지 73361 → 재기동 34356ms · 잃은 트랜잭션 -2 +[restart] ── 판 9 (본판) 팔=idle +[restart] 판 9: redo 0MB · 더러운 페이지 0 → 재기동 1349ms · 잃은 트랜잭션 NA +[restart] ── 판 10 (본판) 팔=idle +[restart] 판 10: redo 0MB · 더러운 페이지 0 → 재기동 1363ms · 잃은 트랜잭션 NA +[restart] ── 판 11 (본판) 팔=stress +[restart] 판 11: redo 1355MB · 더러운 페이지 73626 → 재기동 33577ms · 잃은 트랜잭션 -4 +[restart] ── 판 12 (본판) 팔=paced +[restart] 판 12: redo 14MB · 더러운 페이지 949 → 재기동 2414ms · 잃은 트랜잭션 0 +[restart] 완료 → /root/rig_out/trials.tsv diff --git a/loadtest/results/process-restart-rto-2026-11-08/ec2/trials.tsv b/loadtest/results/process-restart-rto-2026-11-08/ec2/trials.tsv new file mode 100644 index 00000000..7caa341d --- /dev/null +++ b/loadtest/results/process-restart-rto-2026-11-08/ec2/trials.tsv @@ -0,0 +1,13 @@ +trial kind arm ckpt_age_b dirty_pages ready_ms down_ms recov_start_ms redo_done_ms conn_ms exec_ms lost_tx kill_to_start_ms +1 버림 idle 0 0 1322 1662 1248 41 NA 340 +2 버림 paced 15459779 962 2393 2930 2305 41 0 537 +3 버림 stress 1420702135 72923 31990 33230 31984 47 0 1240 +4 본판 stress 1399119706 73194 35058 36156 35002 48 -2 1098 +5 본판 idle 0 0 1279 1677 1214 41 NA 398 +6 본판 paced 15447214 961 2446 2883 2378 41 0 437 +7 본판 paced 15444582 948 1886 2351 1830 40 0 465 +8 본판 stress 1413863145 73361 34356 35481 34292 45 -2 1125 +9 본판 idle 0 0 1349 1700 1343 41 NA 351 +10 본판 idle 0 0 1363 1782 1286 40 NA 419 +11 본판 stress 1421318972 73626 33577 34716 33494 46 -4 1139 +12 본판 paced 15444578 949 2414 2910 2336 41 0 496