Skip to content

[Test] OpenSQL Primary 분할 시 성공 응답 보존과 멱등 재전송 검증 - #548

Merged
Gimini-3 merged 5 commits into
developfrom
test/opensql-primary-network-partition
Oct 17, 2026
Merged

Gimini-3 merged 5 commits into
developfrom
test/opensql-primary-network-partition

Conversation

@Gimini-3

@Gimini-3 Gimini-3 commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

목적

살아 있는 OpenSQL Primary가 앱에서는 접근 가능하지만 두 Standby·etcd 다수와는 통신하지 못하는 네트워크 분할을 재현했습니다. 시험 목적을 세 가지로 분리했습니다.

  1. 비동기 모드: 최초 HTTP 201을 받은 쓰기가 Primary 승격 후에도 남는가?
  2. 동기 strict 모드: 201 보존과 장애 중 요청 가용성은 각각 어떤가?
  3. 멱등 재전송: 최초 실패·결과 불명 요청을 같은 ID·payload로 다시 보내면 전체 요청이 중복 없이 완료되는가?

결론: 비동기 회차는 201 632건 중 40건이 승격 후 누락돼 데이터 보존 기준에 실패했습니다. 동기 strict 회차에서는 최초 201 누락이 0건이었지만 장애 중 HTTP 오류·결과 불명이 발생했습니다. 동기 모드의 멱등 본 시험에서 751건 전체를 재전송한 뒤에는 최종 DB 751건, 요청 ID당 1행, 중복 0건이었습니다. 재전송을 포함한 복구 성공이며 장애 중 무중단 처리를 뜻하지 않습니다.

테스트 환경

항목 실제 구성·조건
실행 위치 새 GCP 격리 환경: OpenSQL DB 3VM, 앱 A/B 2VM, Redis 1VM, 부하 1VM, 내부 LB
공통 장애 시작 시 Primary를 식별하고 그 노드와 두 peer 사이의 노드 간 통신만 양방향 차단. 앱→옛 Primary와 별도 제어기 접속은 유지. 독립 자동 원복 타이머 설치
복제 모드 비동기 synchronous_mode=None; 동기 회차와 멱등 본 시험 synchronous_mode=on, synchronous_mode_strict=true
요청 경로 부하 VM → 내부 LB → 앱 A/B → OpenProxy A/B → OpenSQL. 앞의 비교 회차는 일반 쓰기 API, 멱등 본 시험·재전송은 멱등 쓰기 API
역할 호환 동기 모드 경로는 시험용 Patroni 역할 어댑터를 거친 OpenProxy. 공급사 OpenProxy 단독의 동기 모드 지원 결과로 해석하지 않음
소스 범위 추가 시험 도구·증거는 커밋 0e81a60에 고정. 클라우드 앱 A/B의 정확한 배포 소스 커밋은 확정하지 못함

테스트 방법

1. 비동기 모드 — 최초 성공 응답의 보존

실행 ID: partnet01 · 위치: GCP 격리 환경 · 방법: 일반 쓰기 API에 5 req/s × 150초로 751건을 보냈습니다. 분할 중에도 앱에서 옛 Primary에 접속할 수 있게 두고, DB 밖에 기록한 요청별 최초 HTTP 응답과 승격 후 새 Primary의 요청 ID 원장을 대조했습니다.

사전 성공 기준: 최초 201 응답 ID의 DB 누락 0건, 중복 0건. 비동기 시험 보고서·개별 로그.

2. 동기 strict 모드 — 보존과 가용성의 분리

  • 비교 실행 syncpart01: GCP 격리 환경에서 일반 쓰기 API에 5 req/s × 150초, 별도 요청 751건을 보냈습니다. 최초 201이 승격 후 보존되는지와 HTTP 실패 수를 각각 확인했습니다. 동기 strict 시험 보고서.
  • 기준선 ha501base01·진단 ha502diag01: 멱등 쓰기 API의 정상 기준선(76건)과 분할 중 오류 단계 관측(241건)을 별도 실행으로 준비했습니다.
  • 멱등 본 시험 ha503part01: 같은 GCP 경로의 멱등 쓰기 API에 5 req/s × 150초로 새 합성 ID 751개를 보냈습니다. 당시 Primary와 두 peer의 통신을 약 77초 차단했습니다. 재전송 전에 최초 응답 원장과 새 Primary의 DB 원장을 고정하고, 앱 실패 단계·LB healthy 수를 별도 수집했습니다. 본 시험·진단 보고서.

사전 성공 기준: 미전송 0건, 최초 201의 DB 누락·중복 0건, 복구 후 단일 Primary. HTTP 실패와 결과 불명은 성공 응답에 합치지 않고 별도로 집계해 가용성을 판정했습니다.

3. 같은 요청 재전송 — 최종 처리와 멱등성

실행 ID: ha503replay01 · 위치: 같은 GCP 격리 환경 · 방법: 네트워크 복구 후 ha503part01의 동일한 751개 ID·payload를 30 req/s로 각각 한 번씩 재전송했습니다. 재전송 결과와 최종 DB의 ID별 행 수를 대조했습니다. 별도 ha503conflict01에서는 기존 ID에 다른 payload를 보내 충돌 처리와 DB 불변을 확인했습니다.

사전 성공 기준: 기존 585개 ID는 200, DB에 없던 166개 ID는 201, 최종 751개 ID 모두 1행씩, 다른 payload는 409. 재전송 시험 보고서, ID별 최종 대조.

관측 결과

목적·실행 ID 최초 요청 또는 재전송 HTTP 결과 DB 감사 판정
① 비동기 보존 partnet01 751건: 201 632, 실패 HTTP 114, 결과 불명 5 최초 201 중 40건 누락. 재전송하지 않은 별도 실행 보존 실패
② 동기 strict 비교 syncpart01 별도 751건: 201 586, 500 77, 503 81, 결과 불명 7 최초 201 누락·중복 0 보존 통과, 무중단 가용성 실패
② 동기 strict 멱등 본 시험 ha503part01 새 751건: 201 585, 500 112, 503 46, 결과 불명 8 재전송 전 DB 585행; 최초 201 누락·중복 0; 나머지 166개 ID는 DB에 없음 보존 통과, 첫 시도 가용성 실패
③ 동일 ID 전체 재전송 ha503replay01 위 본 시험의 같은 751건: 기존 585건 200, 없던 166건 201; 기타·불명·미전송 0 최종 751개 ID·각 1행, 중복 0 재전송 복구·멱등성 통과
③ 다른 payload 충돌 ha503conflict01 기존 ID·다른 payload 1건 → 409 최종 DB 751행 유지, 중복 0 충돌 방지 통과

원장 관계: ①의 751건과 ②의 각 751건은 서로 다른 시험·요청 ID입니다. ③만 ②의 ha503part01과 정확히 같은 751개 ID를 재전송했습니다. 비동기 회차의 누락 40건이 이 재전송으로 복구됐다는 뜻이 아닙니다. 동기 본 시험의 최초 실패·결과 불명 166건도 첫 시도에 처리된 것이 아니라 재전송 때 새로 처리됐습니다.

본 시험 ha503part01의 500 112건은 모두 앱의 허용 목록 진단 이벤트와 연결됐습니다. 트랜잭션 시작 108건, 커밋 대기 4건입니다. 503 46건에는 대응하는 앱 진단 이벤트가 없고 같은 장애 구간에 LB healthy 0/2 표본 2개가 있었지만, 요청별 LB 로그를 수집하지 않아 각 503의 반환 주체는 확정하지 않았습니다. 최초 HTTP 원장, 재전송 전 DB, 재전송 후 DB.

검증 과정에서 확인된 사항

  • 비동기·동기 비교 회차와 멱등 본 시험은 요청 API·요청 ID·시작 상태가 다릅니다. 각 한 회차의 수치만으로 동기 모드만이 오류 증가의 유일한 원인이라고 단정하지 않습니다.
  • ha502diag01의 401 20건은 권한 확인 단계의 DB 연결 오류 이벤트와 연결됐습니다. 이를 토큰 만료로 단정하지 않습니다.
  • 옛 Primary와 새 Primary의 역할 표본만으로 모든 순간의 실제 SQL 쓰기 가능 상태나 split-brain 부재를 증명할 수 없습니다. DB 역할 관측은 부하의 마지막 약 30초까지 연속되지 않았습니다.
  • 오프라인 검증기는 final_pass=true, idempotency_pass=true, initial_201_missing_before_retry=0, final_missing_sent_ids=0, db_duplicate_ids_after_retry=0을 산출했습니다. 이는 해당 합성 시험 계약의 결과이지 모든 사용자 요청에 대한 일반 보장이 아닙니다.
  • 임시 분할 규칙·합성 관리자·JWT·VM별 로그인 역할 6개·새 OS Login 키 2개는 제거했습니다. 같은 기본 공개키의 이미 만료된 중복 등록 1개도 키 정리 과정에서 제거됐습니다. 사용자가 재시험 환경 유지를 요청해 VM 7대, DB 데이터, 동기 strict 설정, 시험용 역할 어댑터는 유지했습니다. 마지막 사후 확인은 DB3 Primary 1대·DB1/2 Replica 2대, LB healthy 2/2이며 VM 비용은 계속 발생할 수 있습니다.

@coderabbitai

coderabbitai Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: Repository: DocGrid/docgrid/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 805b3e35-b618-4d2f-9ed5-854d163dcd1c
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Gimini-3 Gimini-3 changed the title [Test] OpenSQL Primary 네트워크 분할과 201 응답 보존 검증 [Test] OpenSQL Primary 분할 시 성공 응답 보존과 멱등 재전송 검증 Oct 17, 2026
@Gimini-3
Gimini-3 marked this pull request as ready for review October 17, 2026 15:24
@Gimini-3
Gimini-3 merged commit 7991a74 into develop Oct 17, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant