Skip to content

docs(redis): ACR → AMR 마이그레이션 가이드 (GB 규모 + 정책×클라이언트 실측) - #57

Closed
hellices wants to merge 23 commits into
mainfrom
docs/redis-migration-guide-measured
Closed

hellices wants to merge 23 commits into
mainfrom
docs/redis-migration-guide-measured

Conversation

@hellices

Copy link
Copy Markdown
Owner

#56을 대체합니다. #56의 커밋(4eecf91)을 그대로 포함한 상위집합이라 별도 리베이스 없이 이 PR만 보면 됩니다.

무엇이 달라졌나

#56은 문서 근거로 쓴 초안(310줄)이었습니다. 이 PR은 실제 인스턴스를 만들어 측정한 결과로 다시 쓴 것입니다(1,221줄).

두 종류의 랩을 돌렸습니다.

랩 목적 규모
데이터 이관 복사 방식별 유실률·다운타임 ACR Premium P1 → AMR Balanced_B5, 3.77GB / 215만 키, 같은 리전 VM
명령 호환성 정책 × 클라이언트별 통과/실패 AMR B0 두 개(정책별) + ACR Basic C0 대조군, 명령 31개 × 클라이언트 2종 × 키 배치 2종, 각 3회 반복

측정으로 정정한 기존 문서 오류 3건

항목 기존 서술 실측
ROLE AMR에서 차단 양쪽 다 동작
FAILOVER ACR에서 허용 양쪽 다 unknown command
CONFIG 양쪽 다 차단 ACR만 차단. AMR은 수락한 뒤 조용히 무시 — CONFIG SET이 OK를 반환하는데 값은 그대로

CONFIG 건은 실패 방식이 문제입니다. 예외도 로그도 없이 설정이 안 바뀝니다.

문서와 실측이 어긋난 항목

Microsoft 문서는 "키스페이스 알림은 AMR에서 지원되지 않는다"고 명시하는데, 실측은 반대였습니다.

ACR (Basic C0) AMR (두 정책 동일)
notify-keyspace-events 기본값 빈 값 AKE
이벤트 수신 0건 10건 (+ expired 2/2)

양쪽을 모두 문서에 적고 "동작함 ≠ 지원됨" 으로 안내했습니다. 지원 대상이 아닌 동작이라 예고 없이 바뀔 수 있습니다.

리뷰에서 봐 주셨으면 하는 것

1. OSSCluster + 비클러스터 클라이언트의 실패 방식 (2.4절)

연결도 되고 SET/GET도 되는데, 다중 키 명령이 커넥션 단위로 MOVED를 냅니다. 새 연결 20회 기준 DEL 7/20 성공, EXISTS 12/20 성공. 그리고 성공 여부가 연결 시점에 정해져 그 연결이 사는 동안 고정됩니다(한 연결로 20회 반복 시 DEL 20/20 실패, 다른 연결은 TOUCH 20/20 성공).

커넥션 풀에서는 일부 커넥션만 계속 실패하는 형태로 나타납니다. 재시도해도 같은 커넥션이면 또 실패하고 특정 키와 무관해 보여서, 스모크 테스트로는 안 잡힙니다. 이 클러스터는 샤드가 1개이고 슬롯 0–16383을 전부 갖는데도 발생합니다.

원인은 규명하지 못했고 Microsoft 문서에서도 설명을 못 찾아 12절에 미측정으로 남겼습니다. 아시는 분 있으면 알려주세요.

2. 허용 목록 경계 — EnterpriseCluster가 통과시킨 다중 키 명령은 문서상 6개(DEL MSET MGET EXISTS UNLINK TOUCH)와 정확히 일치했고, 목록 밖 24개는 전부 CROSSSLOT으로 실패했습니다. 해시 태그로 모으면 네 조합 모두 31/31 통과 — 유일한 보편 해법입니다.

3. 숫자의 전제 — 유실률 48.47%와 다운타임 하한 111초는 3.77GB / 215만 키를 같은 리전 VM에서 옮겼을 때의 값입니다. 본문 전체에서 크기를 함께 표기했습니다. 규모가 커지면 둘 다 커집니다.

재현

파일 내용
redis/migration-lab/policy_matrix_test.py 정책 × 클라이언트 매트릭스 (--repeat로 반복 검증)
redis/migration-lab/results/policy-matrix-{ent,oss}.json 명령별 원본 결과와 예외 타입
redis/migration-lab/audit_commands.sh 명령어 감사 정적 스캐너 — 그동안 문서가 링크만 하고 커밋되지 않았던 파일

측정하지 않은 것

12절에 그대로 적었습니다. NoCluster 정책, 다중 샤드 OSSCluster, 8절의 실시간 전략 전부(RIOT·프록시 미러링 등 한 건도 실행 안 함), ACR Standard/Premium의 알림 활성화, RDB Import 소요 시간(환경 정책으로 차단).

랩 리소스는 전부 삭제했습니다. 결과 JSON에 남은 호스트명·IP는 삭제된 인스턴스의 것이고 자격증명은 포함돼 있지 않습니다.

🤖 Generated with Claude Code

hellices and others added 5 commits August 27, 2026 01:05
세 가지 마이그레이션 전략 비교:
1. RDB Export/Import (Premium ACR, 10-30초 다운타임) - 권장
2. 직접 복제 (Python redis-py, 3-10초 다운타임) - 실제 검증됨
3. Online Migration (Private DNS, 거의 0초)

실제 테스트 결과:
- 마이그레이션 시간: 3.066초 (7개 키)
- 성공률: 100% (7/7 키)
- 데이터 무결성: 완벽히 보장
- 클라이언트 코드 수정: 불필요

테스트 환경:
- 소스: Azure Cache for Redis (Basic C0)
- 타겟: Azure Managed Redis (Balanced_B0)
- 리전: Korea Central

문서 포함 사항:
- 단계별 프로덕션 마이그레이션 가이드
- 주의사항 및 SKU 선택 가이드
- 실제 Python/Bash 코드 예제
기존 문서는 키 7개짜리 테스트에서 나온 3.066초를 선형 외삽한 값이라
실제 고객 환경(기가 단위)에 적용할 수 없었다. Korea Central에 Premium P1 →
Balanced_B5를 실제로 만들어 215만 키 / 3.77GB 규모로 다시 측정했다.

주요 정정 사항:

- 다운타임 "3초"는 틀렸다. 쓰기를 차단한 최종 복사 패스가 111초 걸린다.
- 복사 중 들어온 쓰기의 48.47%가 유실된다. SCAN 커서가 지나간 자리에 쓰인
  키는 그 패스에서 복사되지 않기 때문이다. 기존 문서에는 이 항목이 없었다.
- "Online Migration = 복제 + Private DNS"는 사실과 반대다. Azure 마이그레이션
  도구는 호스트명만 넘기고 데이터를 옮기지 않으며, 프라이빗 엔드포인트를
  지원하지 않는다.
- clusteringPolicy가 클라이언트 무수정 여부를 결정하며 생성 후 변경 불가.
  기존 문서에는 "크로스 슬롯 명령 불가" 주의사항만 있고 해결책이 없었다.
- 근거 없는 SKU별 월 비용표 삭제.
- KEYS * 기반 예제 코드를 SCAN + DUMP/RESTORE + PTTL 보존으로 교체.

측정에 쓴 스크립트와 원본 결과 JSON을 migration-lab/에 함께 둔다.
측정하지 못한 항목(RDB Import 소요 시간, 이중 쓰기 실측)은 추정치로 채우지
않고 10절에 명시했다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
8절의 "7.95GB = B5의 77%"는 산술이 맞지 않았다(7.95/0.77 ≈ 10.3GB).
Azure Monitor에서 usedmemory/usedmemorypercentage 원시값을 다시 뽑아
유효 용량을 역산했다.

- 8,364,071,328 B → 81%, 8,036,226,283 B → 77%
- 역산 유효 용량 약 9.6~9.7 GiB
- 모델 검증: B5(6 GiB) × HA 2사본 × 0.8 = 9.60 GiB — 오차 0.2% 이내

여기서 나오는 실무 규칙은 "담을 수 있는 데이터는 표기 용량의 약 80%"다.
원시 관측치는 results/amr-memory-sizing.json에 고정했다.

그 밖에
- 9절의 "98.93% 일치"는 어느 결과 파일로도 뒷받침되지 않아
  path-b-scan-copy.json의 실제 값(2,482/2,496, 불일치 0)으로 교체
- 소스 축출 함정 추가: ACR 기본 정책 volatile-lru는 메모리 압박 시
  TTL 키를 조용히 지운다. 축출량은 정량화하지 못해 10절 미측정 목록에 명시
- 20% 예약을 B5 한 SKU에서만 확인했다는 한계를 10절에 추가
- 측정일 표기의 KST/UTC 혼동 해소

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"EnterpriseCluster면 클라이언트 무수정"이라는 결론이 근거보다 강했다.

1. 크로스 슬롯 제약이 남아 있다는 사실이 빠져 있었다.
   문서상 EnterpriseCluster에서 슬롯을 넘어 허용되는 다중 키 명령은
   DEL, MSET, MGET, EXISTS, UNLINK, TOUCH 6개뿐이다.
   랩에서 통과시킨 MGET과 DEL은 하필 그 허용 목록 안의 명령이라,
   테스트가 "허용된 명령이 허용된다"만 확인한 셈이었다.
   SUNION/RENAME/크로스 슬롯 MULTI·Lua 등은 검증하지 못했다.

2. 정책이 셋인데 둘만 적었다.
   az redisenterprise database create --clustering-policy의 허용값은
   EnterpriseCluster, NoCluster, OSSCluster다. NoCluster는 25GB 이하
   비샤딩 옵션으로, 문서가 비샤딩 ACR에서 넘어오는 경우와 크로스 슬롯
   명령을 많이 쓰는 워크로드의 선택지로 제시한다.

그 밖에
- "Enterprise"라는 이름이 소스(ACR)나 ACR Enterprise 계층과 무관하고
  AMR이 올라탄 Redis Enterprise의 프록시 기반 클러스터링을 가리킨다는
  설명을 추가. 이름 때문에 생기는 오해를 막는다.
- 8절의 20% 예약이 역산 추정이 아니라 문서에 명시된 값임을 인용으로 보강
- 1절 결론을 "무수정 가능" -> "대체로 피할 수 있음"으로 조정
- 미측정 목록에 크로스 슬롯 검증 범위와 NoCluster 추가
- clustering-policy.json에도 동일한 한계를 기록

출처: https://learn.microsoft.com/azure/redis/architecture

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
clusteringPolicy를 EnterpriseCluster/OSSCluster 각각으로 만든 AMR 두 개와
대조군 ACR 한 개에 직접 붙어, 명령 31개 × 클라이언트 2종 × 키 배치 2종을
각 3회 반복 측정했습니다. 기록된 결과는 전부 3회 일치했습니다.

측정으로 드러난 기존 문서의 오류 3건:

- ROLE: AMR에서 차단된다고 적었으나 양쪽 다 동작합니다.
- FAILOVER: ACR에서 허용된다고 적었으나 양쪽 다 unknown command입니다.
- CONFIG: 양쪽 다 차단이라고 적었으나, ACR만 차단이고 AMR은 명령을 수락한 뒤
  조용히 무시합니다. CONFIG SET이 OK를 반환하는데 값은 그대로입니다.

키스페이스 알림은 Microsoft 문서와 실측이 반대로 나왔습니다. 문서는 AMR
미지원이라고 명시하지만 기본값 AKE로 이벤트가 실제 발행됐고, ACR Basic은
0건이었습니다. 양쪽을 모두 적고 "동작함 != 지원됨"으로 안내합니다.

주요 실측 결과:

- EnterpriseCluster는 문서상 허용 목록 6개를 정확히 통과시키고 목록 밖
  24개를 전부 CROSSSLOT으로 실패시킵니다.
- OSSCluster에 비클러스터 클라이언트로 붙으면 연결과 SET/GET은 되지만
  다중 키 명령이 커넥션 단위로 MOVED가 납니다. 성공 여부가 연결 시점에
  정해져 그 연결 내내 고정되므로, 커넥션 풀의 일부만 계속 실패합니다.
  샤드가 1개뿐이라 리다이렉트 대상이 없는데도 발생합니다.
- 해시 태그로 같은 슬롯에 모으면 네 조합 모두 31/31 통과합니다.
- 클러스터 클라이언트는 샤드 IP로 재접속해 TLS 호스트명 검증에 실패합니다.

재현 스크립트(policy_matrix_test.py)와 원본 결과 JSON 2개를 추가했고,
그동안 문서가 링크만 하고 커밋되지 않았던 audit_commands.sh를 함께 넣습니다.
12절에는 새로 생긴 미측정 항목 4건(MOVED 분기 원인, 다중 샤드 동작,
알림 불일치의 지속성, ACR Standard/Premium 알림 활성화)을 적었습니다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@my-reviewer-agent my-reviewer-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 리뷰는 부분 리뷰입니다. PR 전체 변경 중 일부 파일(redis/migration-lab/load_data.py, results/clustering-policy.json, results/path-a-rdb.json, results/policy-matrix-ent.json, results/policy-matrix-oss.json)만 확인했으며, 나머지 변경 파일은 이번 검토 범위에 포함되지 않았습니다. 따라서 전체 변경에 대한 완전한 검토를 주장하지 않습니다.

확인한 범위에서의 총평입니다.

results/ 아래 JSON 산출물들은 실측 로그 성격의 데이터이고 구조가 일관되어 별도 지적 사항은 없습니다. 특히 policy-matrix-oss.json / policy-matrix-ent.json은 OSSCluster에서 크로스 슬롯 다중 키 명령이 전부 ClusterCrossSlotError로 실패하고 같은 슬롯에서는 통과한다는 점, EnterpriseCluster에서는 허용 목록 밖 명령(SUNION, ZUNIONSTORE, MULTI/EXEC, EVAL 등)까지 성공한다는 점을 runs/ok_count/stable로 재현 가능하게 남겨 두어 근거로서 충분합니다. clustering-policy.json이 IMPORTANT_caveat로 "이 테스트는 클라이언트 무수정을 증명하지 못한다"고 스스로 한계를 밝히고 clusteringPolicy가 변경 불가(재생성 필요, access-keys-auth 재활성화 필요)라는 운영 함정을 기록한 점도 좋습니다. path-a-rdb.json도 실패 원인을 테넌트 정책 수준까지 특정해 두어 재현 가치가 있습니다.

다만 load_data.py는 랩 도구임을 감안하더라도 몇 가지 보완할 점이 있어 인라인으로 남겼습니다. 요약하면 (1) TLS 인증서 검증 비활성화, (2) 액세스 키를 커맨드라인 인자로 받는 노출 경로, (3) 자식 프로세스 실패를 감지하지 않아 적재가 미완성이어도 성공으로 보고되는 문제, (4) 목표 용량 환산에서 큰 해시 용량이 누락되어 --target-gb와 실제 적재량이 어긋나는 문제입니다. 모두 차단 이슈는 아니라고 판단해 COMMENT로 남깁니다.

PR_REVIEW_SCOPE_V1 {"included_files":["redis/migration-lab/load_data.py","redis/migration-lab/results/clustering-policy.json","redis/migration-lab/results/path-a-rdb.json","redis/migration-lab/results/policy-matrix-ent.json","redis/migration-lab/results/policy-matrix-oss.json"],"omitted_files":["redis/azure-cache-to-managed-redis-migration.md","redis/migration-lab/README.md","redis/migration-lab/audit_commands.sh","redis/migration-lab/concurrent_writer.py","redis/migration-lab/import_rdb.py","redis/migration-lab/migrate_scan_copy.py","redis/migration-lab/policy_matrix_test.py","redis/migration-lab/results/amr-memory-sizing.json","redis/migration-lab/results/path-b-repeat-pass.json","redis/migration-lab/results/path-b-scan-copy.json","redis/migration-lab/results/path-c-tooling.json","redis/migration-lab/verify_migration.py"],"original_changed_lines":5147,"original_diff_lines":5251,"original_diff_unavailable_reason":null}

Comment thread redis/migration-lab/load_data.py Outdated
Comment thread redis/migration-lab/load_data.py Outdated
Comment thread redis/migration-lab/load_data.py
Comment thread redis/migration-lab/load_data.py
hellices and others added 3 commits August 27, 2026 17:47
1.1절이 "ACR → AMR에서 바뀌는 것"이라는 이름으로 기능 차이와 마이그레이션
결과(유실률·이관 수단·다운타임)를 한 표에 섞어 놓고 있었습니다. 1~3절은
옮기기 전에 알아야 할 것, 4절부터가 옮기는 방법이라는 문서 구조와 어긋납니다.

1.1절을 "기능 차이 — 엔진, 샤딩, 명령어, 클라이언트"로 바꾸고 다섯 개 표로
나눴습니다: 엔진과 클러스터 구조 / Redis Enterprise 스택이 새로 주는 것 /
명령어 / 클라이언트와 연결 / 용량과 지표. 마이그레이션 성격의 행(단일 패스
48.47% 유실, Azure 도구가 데이터를 옮기지 않는 것, REPLICAOF 기반 전략 불가,
다운타임 하한 111초)과 우선순위 열은 뺐습니다. 네 항목 모두 1.3절과 6~8절에
이미 있어 내용 손실은 없습니다.

지금까지 문서에 없던 Enterprise 스택 기능을 채웠습니다.

- 모듈(RediSearch/RedisJSON/RedisBloom/RedisTimeSeries) — ACR Basic/Standard/
  Premium에는 없고, AMR은 생성 시점에만 추가 가능하며 수동 로드·버전 갱신 불가
- RediSearch는 EnterpriseCluster 정책과 NoEviction 축출 정책을 강제 — 벡터
  검색 계획이 있으면 정책 선택이 사실상 하나로 정해집니다
- 지역 복제가 Premium의 passive에서 active로 바뀌고, 액티브 구성에서는
  FLUSHALL/FLUSHDB가 차단되며 병행 가능한 모듈이 RediSearch·RedisJSON뿐
- Flash Optimized의 NVMe 계층, 전 계층 지속성, 전 계층 SLA
- 명령 처리가 OSS의 단일 스레드에서 인스턴스당 다중 vCPU 활용으로

REPLICAOF 행은 REPLICAOF만 양쪽 실측이고 PSYNC/REPLCONF는 문서 근거임을
구분해 표기했습니다.

1.4절에 모듈 문서를 추가하고, 13절에 1.1절 근거인 계층별 기능 비교 문서
세 건(AMR overview, ACR overview, redis-modules)을 절 근거로 묶었습니다.

앵커·표 열 수·상대 링크 검증 통과.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Microsoft Learn 원문과 az CLI 정의로 문서 내용을 검증하고 다음을 고쳤습니다.

틀린 주장 수정
- clusteringPolicy "생성 후 변경 불가"는 과장. CLI 정의상 NoCluster에서
  나오는 방향은 변경 가능하고, OSSCluster/EnterpriseCluster에서만 DB 재생성이
  필요합니다. 2.6절 제목과 본문, 1.1/1.2/9절, migration-lab/README를 함께 수정.
- AMR "TLS 필수"는 오류. AMR은 생성 시 TLS/비TLS 중 한 모드를 고르며 두
  모드를 혼용할 수 없는 것이고, 포트는 어느 쪽이든 10000입니다.

오해 소지 정정
- ACR의 FAILOVER는 Azure가 막은 것이 아니라 Redis 6.2에 추가된 명령이라
  6.0.x에 존재하지 않는 것입니다. AMR은 문서상 명시적 Failover 미지원.

공식 가이드를 기준 문서로 명시
- 문서 상단에 공식 3단계 가이드와 이 문서 절 번호의 대응표 추가.
- 4.0절 신설: Option 1(자체 마이그레이션, 권장) / Option 2(도구)와
  경로 A/B/C의 대응, 공식 문서가 드는 권장 근거 3가지.
- 7절에 마이그레이션 도구의 실제 절차(사전 생성, Validate의 경고/오류 구분,
  연결 blip, 호스트명 존속, PowerShell) 보강.

누락 보완
- 액티브 지역 복제 시 크로스 슬롯 허용 목록이 3개로 축소되는 점.
- 네트워크·운영 차이 표 신설: VNet 주입·IP 방화벽 미지원, Entra ID RBAC 미지원,
  수동 재부팅 없음(Flush로 대체), 영역 중복 기본값, RDB 내보내기 SKU 조건.
- 샤드 개별 포트 13XXX → 85XX.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- 9절 권장 절차의 기준을 공식 문서 Option 1(자체 마이그레이션)로 명시하고,
  Option 2(마이그레이션 도구)를 검토해도 되는 조건 4가지를 체크리스트로 추가.
- nodejs/cluster_failover_recovery.md의 "클러스터 정책은 생성 시 설정으로
  변경 불가" 표현을 정정. 결론(OSS Cluster에서 전환하려면 재생성)은 맞지만
  NoCluster에서는 변경이 가능하므로 조건을 명시하고, 데이터 재이관이
  동반된다는 점과 가이드 2.6절 링크를 추가.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@my-reviewer-agent my-reviewer-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 리뷰는 부분 리뷰입니다. 이번 검토에서는 redis/migration-lab/load_data.py, results/clustering-policy.json, results/path-a-rdb.json, results/policy-matrix-ent.json, results/policy-matrix-oss.json만 확인했고 나머지 변경 파일은 범위에 포함되지 않았으므로 전체 변경에 대한 완전한 검토를 주장하지 않습니다.

이전 SHA에서 남긴 load_data.py 관련 지적(ssl_cert_reqs="none"로 TLS 검증 비활성화, --password로 액세스 키를 커맨드라인에 노출, 자식 프로세스 실패 미감지, --target-gb 환산에서 큰 해시 용량 누락)은 이번 diff에서도 그대로 남아 있으나 중복 제기는 하지 않고 인라인으로는 새로 확인된 두 가지만 남깁니다. 요약하면 (1) 워커 분배 시 정수 나눗셈 나머지가 버려져 계획 키 수와 실제 적재량이 어긋나고 진행률이 100%에 도달하지 않는 문제, (2) 완료 요약이 dbsize에만 의존해 계획 대비 실제 적재를 검증하지 않는 문제입니다.

results/ 아래 JSON 산출물은 실측 로그 성격이며 runs/ok_count/stable 구조가 일관되어 근거 자료로 충분합니다. 다만 policy-matrix-oss.json의 일부 error 문자열이 고정 길이에서 잘려 닫는 괄호나 violating-key 값이 중간에 끊긴 항목이 있습니다(예: SUNIONSTORE, ZUNIONSTORE, GEOSEARCHSTORE 케이스). 재현 근거로 쓰기에 치명적이지는 않지만, 수집 스크립트에서 잘라내는 길이를 늘리거나 잘림 여부를 나타내는 플래그를 함께 남기면 나중에 원문 해석이 흔들리지 않습니다.

clustering-policy.json이 IMPORTANT_caveat로 이 테스트가 클라이언트 무수정을 증명하지 못한다는 점과 clusteringPolicy 변경 불가(재생성 및 access-keys-auth 재활성화 필요)를 명시한 점, path-a-rdb.json이 실패 원인을 테넌트 정책 수준까지 특정한 점은 좋습니다. 확인한 범위에서 차단 이슈는 없어 COMMENT로 남깁니다.

PR_REVIEW_SCOPE_V1 {"included_files":["redis/migration-lab/load_data.py","redis/migration-lab/results/clustering-policy.json","redis/migration-lab/results/path-a-rdb.json","redis/migration-lab/results/policy-matrix-ent.json","redis/migration-lab/results/policy-matrix-oss.json"],"omitted_files":["redis/azure-cache-to-managed-redis-migration.md","redis/migration-lab/README.md","redis/migration-lab/audit_commands.sh","redis/migration-lab/concurrent_writer.py","redis/migration-lab/import_rdb.py","redis/migration-lab/migrate_scan_copy.py","redis/migration-lab/policy_matrix_test.py","redis/migration-lab/results/amr-memory-sizing.json","redis/migration-lab/results/path-b-repeat-pass.json","redis/migration-lab/results/path-b-scan-copy.json","redis/migration-lab/results/path-c-tooling.json","redis/migration-lab/verify_migration.py","redis/nodejs/cluster_failover_recovery.md"],"original_changed_lines":5313,"original_diff_lines":5428,"original_diff_unavailable_reason":null}

Comment thread redis/migration-lab/load_data.py Outdated
Comment thread redis/migration-lab/load_data.py
1,350줄짜리 단일 문서를 판단에 필요한 것만 담은 메인 문서와
주제별 상세 문서로 나눴습니다.

- azure-cache-to-managed-redis-migration.md (325줄) — 요약, 공식 가이드
  대응표, clusteringPolicy 결정 트리, 경로 선택(Option 1 기본),
  우선순위와 순서, 권장 절차. 실측은 결론 숫자만
- amr-differences.md (320줄) — 1.1절, 2절 전체. 기능 차이,
  정책 세 가지, 정책 x 클라이언트 실측 호환성 매트릭스
- amr-client-audit.md (223줄) — 3절 전체. TIER 1~4 체크리스트,
  SDK별 확인 포인트, 명령어 감사
- amr-migration-paths.md (536줄) — 5~8절, 10~12절. 경로 A/B/C,
  실시간 전략 비교, 용량 산정, 테스트 환경, 측정하지 않은 것

상세 문서는 원래 절 번호를 그대로 유지합니다. 본문의 "2.4절",
"3.3절" 같은 참조가 그대로 유효하도록 하기 위한 것으로, 링크에서
파일 경로만 바뀝니다.

부수 수정:
- nodejs/cluster_failover_recovery.md:33 — "클러스터 정책은 생성 후
  변경 불가"가 남아 있던 것을 정정. NoCluster에서 나오는 방향만 변경
  가능하고, OSSCluster/EnterpriseCluster는 DB 재생성이 필요합니다
- migration-lab/README.md — 측정 상세 링크를 amr-migration-paths.md로
  재지정

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@my-reviewer-agent my-reviewer-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이번 리뷰는 부분 리뷰입니다. PR 전체 변경 중 redis/migration-lab/load_data.py 와 results/ 아래 JSON 4개(clustering-policy.json, path-a-rdb.json, policy-matrix-ent.json, policy-matrix-oss.json)만 확인했고, 나머지 변경 파일은 이번 검토 범위에 포함되지 않았습니다. 따라서 전체 변경에 대한 완전한 검토를 주장하지 않습니다.

이전 SHA에서 이미 지적한 사항(TLS 인증서 검증 비활성화, 액세스 키의 커맨드라인 노출, 자식 프로세스 실패 미감지, --target-gb 환산에서 큰 해시 용량 누락)은 반복하지 않습니다.

results/ 아래 JSON 산출물은 이전 검토와 동일하게 구조가 일관되고 실측 로그로서 근거가 충분합니다. policy-matrix-oss.json / policy-matrix-ent.json은 크로스 슬롯과 같은 슬롯을 runs/ok_count/stable로 나누어 재현 가능하게 남겼고, clustering-policy.json은 IMPORTANT_caveat로 검증 한계를 스스로 밝히고 clusteringPolicy 변경 불가라는 운영 함정까지 기록했습니다. path-a-rdb.json도 실패 원인을 테넌트 정책 수준까지 특정해 두었습니다. 이 파일들에 대해서는 추가 지적 사항이 없습니다.

이번에는 load_data.py의 진행률·처리량 계측과 큰 해시 적재 병렬성에 대해 세 건을 인라인으로 남겼습니다. 모두 랩 도구 수준에서 차단 이슈는 아니라고 판단해 COMMENT로 제출합니다.

PR_REVIEW_SCOPE_V1 {"included_files":["redis/migration-lab/load_data.py","redis/migration-lab/results/clustering-policy.json","redis/migration-lab/results/path-a-rdb.json","redis/migration-lab/results/policy-matrix-ent.json","redis/migration-lab/results/policy-matrix-oss.json"],"omitted_files":["redis/amr-client-audit.md","redis/amr-differences.md","redis/amr-migration-paths.md","redis/azure-cache-to-managed-redis-migration.md","redis/migration-lab/README.md","redis/migration-lab/audit_commands.sh","redis/migration-lab/concurrent_writer.py","redis/migration-lab/import_rdb.py","redis/migration-lab/migrate_scan_copy.py","redis/migration-lab/policy_matrix_test.py","redis/migration-lab/results/amr-memory-sizing.json","redis/migration-lab/results/path-b-repeat-pass.json","redis/migration-lab/results/path-b-scan-copy.json","redis/migration-lab/results/path-c-tooling.json","redis/migration-lab/verify_migration.py","redis/nodejs/cluster_failover_recovery.md"],"original_changed_lines":5341,"original_diff_lines":5481,"original_diff_unavailable_reason":null}

Comment thread redis/migration-lab/load_data.py
Comment thread redis/migration-lab/load_data.py
Comment thread redis/migration-lab/load_data.py Outdated
주변 78개 문서의 실제 관례를 측정해서 맞췄습니다.

구조
- 상세 3개를 redis/migration-guide/ 하위로 이동하고 01-/02-/03- 번호 부여
  (memory/agent-memory, monitor/sre-agent-event-lab/guides, aisearch/custom_vectorization
   세 선례가 모두 하위 디렉터리 + 번호 파일명 방식)
- 절 번호를 문서마다 1부터 재부여. 문서 간 참조는 문서 이름을 함께 표기
  (예: `ACR과 AMR의 차이 2.4절`), 같은 문서 안에서는 `2.4절`

제목
- H1에서 `(상세)` 제거하고 `01. ACR과 AMR의 차이 — 기능·정책·명령어` 형태로 통일
- `## 0. 요약` → `## 핵심 요약`, `## 7. 참고 자료` → `## 참고` (레포 관례)
- 01번 문서 `## 2. ACR과 AMR은 무엇이 다른가` → `## 2. 왜 다른가 — 제품 계보와 클러스터 정책`
  (H1·1절과 제목이 겹쳐서 구분되지 않던 문제)

문체
- 표 셀 27개를 명사구로 정리. 레포 전체 한국어 표 셀 3,325개 중 종결어미로
  끝나는 것은 47개(1%)뿐인데 이 묶음만 10% 수준이었음
- 본문은 문서별 합니다체 단일 사용 유지
- `디렉토리` → `디렉터리`

링크
- 이동으로 깨진 상대 경로 16건, 링크 텍스트와 대상이 어긋난 4건 수정
- 번호만 남아 있던 산문 절 참조 15건을 새 번호·문서명으로 교체
- 레포 전체 앵커/상대 경로/표 열 수 검증 0건

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@my-reviewer-agent my-reviewer-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이번 리뷰는 부분 리뷰입니다. PR 전체 변경 중 redis/migration-lab/load_data.py 와 results/ 아래 JSON 4개(clustering-policy.json, path-a-rdb.json, policy-matrix-ent.json, policy-matrix-oss.json)만 확인했고, 나머지 변경 파일은 이번 검토 범위에 포함되지 않았습니다. 따라서 전체 변경에 대한 완전한 검토를 주장하지 않습니다.

이전 SHA에서 이미 남긴 load_data.py 관련 지적(ssl_cert_reqs="none"로 TLS 검증 비활성화, --password로 액세스 키를 커맨드라인에 노출, 자식 프로세스 실패 미감지, --target-gb 환산에서 BIG_HASH 용량 누락, 워커 분배의 정수 나눗셈 나머지 손실, dbsize 기반 완료 요약, 진행률·처리량 계측과 큰 해시 적재 병렬성)은 반복하지 않습니다. 이번 diff에서 load_data.py 본문은 이전 검토 시점과 동일한 내용으로 보여 추가로 새로 지적할 만한 사항을 찾지 못했습니다.

대신 이번에는 policy-matrix-oss.json 산출물 자체의 해석 가능성 측면에서 네 건을 인라인으로 남겼습니다. 요약하면 (1) 클러스터 클라이언트 측정에서만 TLS 호스트네임 검증을 끈 상태라는 점이 결과 상단에 기록되어 있어 두 클라이언트의 측정 조건이 다르다는 점, (2) "단일 키 SET ×50" 케이스의 키 분포가 명시되지 않아 clustering-policy.json의 ossCluster set_500_keys(MovedError 대부분 실패) 기록과 상충하는 것처럼 읽힌다는 점, (3) 노드 주소로 공인 IP와 포트가 그대로 남아 있다는 점, (4) 관리 명령 표의 일부 실패가 서버 제약이 아니라 redis-py 클라이언트 사용상의 오류로 보이는데 다른 서버 측 제약과 같은 "실패"로 묶여 있어 AMR의 제약으로 오독될 수 있다는 점입니다.

clustering-policy.json이 IMPORTANT_caveat로 검증 한계를 스스로 밝히고 clusteringPolicy 변경 불가(재생성 및 access-keys-auth 재활성화 필요)를 기록한 점, path-a-rdb.json이 import 실패 원인을 테넌트 정책(MCAPSGovDeployPolicies/StorageAccount_PublicNetwork_Modify)까지 특정한 점, policy-matrix-ent.json이 EnterpriseCluster에서 허용 목록 밖 명령의 크로스 슬롯 실패와 같은 슬롯 성공을 runs/ok_count/stable로 나눈 점은 근거 자료로 충분합니다. 확인한 범위에서 차단 이슈는 없어 COMMENT로 제출합니다.

PR_REVIEW_SCOPE_V1 {"included_files":["redis/migration-lab/load_data.py","redis/migration-lab/results/clustering-policy.json","redis/migration-lab/results/path-a-rdb.json","redis/migration-lab/results/policy-matrix-ent.json","redis/migration-lab/results/policy-matrix-oss.json"],"omitted_files":["redis/azure-cache-to-managed-redis-migration.md","redis/migration-guide/01-differences.md","redis/migration-guide/02-client-audit.md","redis/migration-guide/03-migration-paths.md","redis/migration-lab/README.md","redis/migration-lab/audit_commands.sh","redis/migration-lab/concurrent_writer.py","redis/migration-lab/import_rdb.py","redis/migration-lab/migrate_scan_copy.py","redis/migration-lab/policy_matrix_test.py","redis/migration-lab/results/amr-memory-sizing.json","redis/migration-lab/results/path-b-repeat-pass.json","redis/migration-lab/results/path-b-scan-copy.json","redis/migration-lab/results/path-c-tooling.json","redis/migration-lab/verify_migration.py","redis/nodejs/cluster_failover_recovery.md"],"original_changed_lines":5342,"original_diff_lines":5489,"original_diff_unavailable_reason":null}

Comment thread redis/migration-lab/results/policy-matrix-oss.json
Comment thread redis/migration-lab/results/policy-matrix-oss.json
Comment thread redis/migration-lab/results/policy-matrix-oss.json Outdated
Comment thread redis/migration-lab/results/policy-matrix-oss.json
루트 README의 카테고리 목록이 6개로 멈춰 있어 실제 18개와 어긋나 있었다.
문서를 추가할 때마다 손으로 고치는 구조라 계속 밀린다.

- AGENTS.md: 문서 작성 규칙. 기존 문서 78개를 세어서 나온 다수 관행을
  근거 숫자와 함께 적었다. 문체·표·묶음 구조·측정값 표기·공개 저장소 주의사항
- CLAUDE.md: AGENTS.md를 가리키는 짧은 포인터
- scripts/gen_index.py: 루트 README의 카테고리·실습 랩 표를 저장소 구조에서 생성.
  마커 사이만 바꾸므로 서술은 보존된다
- scripts/check_docs.py: 깨진 링크·앵커·표 열 수·용어를 오류로,
  표 셀 종결어미·문체 혼용을 경고로 잡는다
- .github/workflows/docs.yml: main 푸시 시 인덱스 자동 커밋,
  PR에서는 이번에 바뀐 문서만 검사해 과거 문서가 새 PR을 막지 않게 한다

함께 고친 것
- README: 죽은 템플릿 절과 고정 Last Updated 제거, 기여 방법을 AGENTS.md로 연결
- memory/README.md: H1이 `memory`였던 것을 문서 이름으로
- 디렉토리 → 디렉터리 2건, 이미지 경로 1건, 표 열 수 1건

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@my-reviewer-agent my-reviewer-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 리뷰는 부분 리뷰입니다. 이번 SHA에서도 PR 전체 변경 중 일부 파일(redis/migration-lab/load_data.py, results/clustering-policy.json, results/path-a-rdb.json, results/policy-matrix-ent.json, results/policy-matrix-oss.json)만 확인했고, 나머지 변경 파일은 검토 범위에 포함되지 않았습니다. 따라서 전체 변경에 대한 완전한 검토를 주장하지 않습니다.

확인한 범위의 총평입니다.

policy-matrix-oss.json / policy-matrix-ent.json은 OSSCluster에서 크로스 슬롯 다중 키 명령이 전부 ClusterCrossSlotError로 실패하고 같은 슬롯에서는 통과한다는 점, EnterpriseCluster에서는 허용 목록(MGET, MSET, EXISTS, TOUCH, UNLINK, DEL)만 크로스 슬롯에서 통과하고 SUNION, SINTER, SDIFF 등 목록 밖 명령은 동일하게 실패한다는 점을 runs/ok_count/stable 구조로 재현 가능하게 남겨 두어 근거로 충분합니다. 관리 명령 계층(FAILOVER, REPLICAOF는 unknown command, CONFIG GET/SET·INFO commandstats·DBSIZE는 성공)과 클러스터 클라이언트가 EnterpriseCluster에 붙지 못하는 결과(RedisClusterException, cluster mode not enabled)까지 기록된 점도 좋습니다. path-a-rdb.json은 import 실패를 테넌트 정책 수준까지 특정해 두어 재현 가치가 큽니다.

이번에는 이전 리뷰에서 이미 지적한 사항(TLS 검증 비활성화, 액세스 키의 커맨드라인 노출, 자식 프로세스 실패 미감지, 큰 해시 용량이 --target-gb 환산에서 빠지는 문제)은 반복하지 않고, 새로 눈에 띈 세 가지만 인라인으로 남깁니다. 요약하면 (1) clustering-policy.json의 OSSCluster 서술과 policy-matrix-oss.json의 실측 결과가 서로 어긋나 보이는 부분, (2) 워커 분배의 정수 나눗셈으로 실제 적재량이 계획과 달라지고 workers=0에서 ZeroDivisionError가 나는 문제, (3) 큰 해시 적재가 단일 프로세스에 직렬로 묶여 전체 적재 시간을 지배하는 문제입니다. 모두 차단 이슈는 아니라고 판단해 COMMENT로 남깁니다.

PR_REVIEW_SCOPE_V1 {"included_files":["redis/migration-lab/load_data.py","redis/migration-lab/results/clustering-policy.json","redis/migration-lab/results/path-a-rdb.json","redis/migration-lab/results/policy-matrix-ent.json","redis/migration-lab/results/policy-matrix-oss.json"],"omitted_files":[".github/workflows/docs.yml","AGENTS.md","CLAUDE.md","README.md","aisearch/custom_vectorization/01_custom_embedding_guide.md","aisearch/custom_vectorization/ref_bge_m3_vs_qwen3_comparison.md","aks/argocd_image_updater.md","memory/README.md","redis/azure-cache-to-managed-redis-migration.md","redis/migration-guide/01-differences.md","redis/migration-guide/02-client-audit.md","redis/migration-guide/03-migration-paths.md","redis/migration-lab/README.md","redis/migration-lab/audit_commands.sh","redis/migration-lab/concurrent_writer.py","redis/migration-lab/import_rdb.py","redis/migration-lab/migrate_scan_copy.py","redis/migration-lab/policy_matrix_test.py","redis/migration-lab/results/amr-memory-sizing.json","redis/migration-lab/results/path-b-repeat-pass.json","redis/migration-lab/results/path-b-scan-copy.json","redis/migration-lab/results/path-c-tooling.json","redis/migration-lab/verify_migration.py","redis/nodejs/cluster_failover_recovery.md","scripts/check_docs.py","scripts/gen_index.py"],"original_changed_lines":6164,"original_diff_lines":6418,"original_diff_unavailable_reason":null}

Comment thread redis/migration-lab/results/clustering-policy.json
Comment thread redis/migration-lab/load_data.py Outdated
Comment thread redis/migration-lab/load_data.py
hellices and others added 2 commits August 27, 2026 23:20
PR #57 리뷰에서 나온 지적을 확인하고 반영했다.

증거 두 개가 서로 어긋나 보이던 문제를 조건 차이로 정리했다.
policy-matrix-oss.json은 비클러스터 클라이언트의 단일 키 SET을 성공으로,
clustering-policy.json(Balanced_B5)은 대부분 MovedError 실패로 기록하고 있었다.
기록상 갈리는 지점은 클러스터 구성뿐이다 — 전자는 CLUSTER SLOTS 응답 노드가
1개라 한 샤드가 슬롯 0-16383을 전부 갖고 있었다. 샤드가 하나라서 단일 키가
살아남은 것이므로, 표의 "성공"을 조건 없이 읽으면 다중 샤드에서 정반대가 된다.

- 01-differences.md 2.4절에 두 측정의 조건 비교표와 다중 샤드 경고 추가
- 메인 가이드의 OSSCluster 인용문도 같은 조건을 달도록 수정
- 두 결과 JSON에 샤드 수를 기록 (B5 쪽은 재지 않았음을 명시)

공개 저장소에 노드 공인 IP가 세 곳에 들어가 있었다. AGENTS.md 6.3에 어긋나고
독자에게 주는 정보도 없어 자리표시자로 바꿨다. 과거 커밋에는 남아 있다.

랩 스크립트 5개가 같은 연결 코드를 복사해 쓰고 있어 함께 고쳤다.

- ssl_cert_reqs를 none에서 required로 (policy_matrix_test.py는 이미 CERT_REQUIRED로
  같은 엔드포인트에 붙고 있었으므로 검증이 통과하는 것이 확인된 설정이다)
- 액세스 키를 명령행 대신 환경 변수/프롬프트로 받음. 명령행 인자는 셸 히스토리와
  ps 출력에 남는다. 이 저장소의 다른 랩 테스트도 `--password`를 유출 패턴으로 본다
- load_data.py: 워커 비정상 종료 시 실패로 종료 (조용히 덜 적재된 상태로 잰
  이관 시간은 다른 측정과 비교할 수 없다), --workers 하한 검사, 키 배분을
  divmod로 정확히 맞춤, 적재량을 dbsize 증분으로 계산

--target-gb 환산식과 큰 해시 적재 방식은 그대로 뒀다. 지금 고치면 같은 명령이
공개된 3.77GB 데이터셋을 더 이상 재현하지 못하고, 랩 리소스가 삭제되어 다시
잴 수도 없다. 대신 큰 해시가 목표 용량 밖이라는 점을 모듈 주석에 적었다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
리뷰 지적대로 policy-matrix JSON의 클러스터 클라이언트 관리명령 실패는
대부분 서버가 거부한 것이 아니었다. SELECT는 redis-py의 AttributeError,
SWAPDB/FAILOVER/REPLICAOF는 클러스터 명령 테이블에 없다는 라이브러리 메시지,
ROLE은 보낼 노드를 못 정한 라우팅 실패다.

가이드의 "차단 (실측)" 주장은 전부 비클러스터 클라이언트가 받은 서버
ResponseError를 근거로 하고 있어 그대로 유효하다. 다만 두 종류가 한 표에
섞여 있으면 다음 사람이 잘못 읽을 수 있어 JSON에 구분을 적어 두었다.

02-client-audit.md의 ROLE 항목에는 실패 원인이 클라이언트 라우팅이라는 점을
덧붙였다. 서버 제약이 아니므로 보낼 노드를 지정하면 풀린다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@hellices

Copy link
Copy Markdown
Owner Author

리뷰 반영 완료 — 커밋 46f57bf, 19a5c11

지적 16건을 하나씩 코드와 대조해 확인했습니다. 결과적으로 문서 오류 하나가 드러나서 그 쪽이 가장 큰 수확이었습니다.

1. 문서를 고친 것

샤드 수 조건이 빠져 있었습니다. 증거 두 개가 어긋나 보인다는 지적(clustering-policy.json vs policy-matrix-oss.json)을 따라가 보니, 원인은 리뷰가 짚은 키 분포가 아니라 샤드 수였습니다. 매트릭스 쪽 클러스터는 connect.nodes가 1이라 한 샤드가 슬롯 0–16383을 전부 갖고 있었고, 그래서 비클러스터 클라이언트의 단일 키 SET이 통과했습니다. Balanced_B5에서 대부분 MovedError로 실패한 쪽이 일반적인 모양입니다.

원래 표는 이 조건 없이 "성공"만 적고 있어서, 다중 샤드 타깃을 쓰는 독자가 정반대 결론을 내릴 수 있었습니다. 샤드 1개는 문제를 덜 보이게 만드는 조건입니다.

  • 01-differences.md 2.4절 — 두 측정의 조건 비교표, 다중 샤드 경고
  • azure-cache-to-managed-redis-migration.md — OSSCluster 인용문에 같은 조건
  • 두 결과 JSON에 샤드 수 기록 (B5는 재지 않았음을 명시)

관리명령 실측에서 서버 거부와 클라이언트 사정이 섞여 있었습니다. 클러스터 클라이언트 행의 실패는 대부분 redis-py 쪽 문제입니다. 가이드의 "차단 (실측)" 주장은 전부 비클러스터 클라이언트가 받은 서버 ResponseError를 근거로 해서 결론은 유효하지만, JSON에 구분을 적어 뒀습니다. ROLE은 서버 제약이 아니라 라우팅 실패라는 점을 02-client-audit.md에 덧붙였습니다.

노드 공인 IP를 자리표시자로 바꿨습니다. 리뷰가 찾은 JSON 1곳 외에 문서에 2곳 더 있었습니다. 이 PR에서 추가한 AGENTS.md 6.3을 정작 스스로 어기고 있었습니다.

2. 랩 스크립트

5개가 같은 연결 코드를 복사해 쓰고 있어 한꺼번에 고쳤습니다. 한 파일만 고치면 나머지에서 그대로 복사돼 나갑니다.

  • ssl_cert_reqs를 none → required (policy_matrix_test.py가 이미 CERT_REQUIRED로 같은 엔드포인트에 붙고 있어 통과가 확인된 설정)
  • 액세스 키를 명령행 대신 환경 변수/프롬프트로. 이 저장소의 다른 랩 테스트도 --password를 유출 패턴으로 봅니다
  • load_data.py — 워커 exitcode 검사, --workers 하한, divmod 배분, 적재량을 dbsize 증분으로

3. 고치지 않은 것

  • --target-gb 환산식 — 큰 해시가 빠져 있는 건 맞지만, 고치면 같은 명령이 문서에 실린 3.77GB / 215만 키 데이터셋을 재현하지 못합니다. 랩 리소스가 삭제돼 다시 잴 수도 없습니다. 대신 모듈 주석과 계획 출력에 목표 밖이라고 적었습니다. 문서 숫자는 목표치가 아니라 적재 후 실제 used_memory/dbsize라 결론에는 영향이 없습니다
  • 큰 해시 단일 프로세스 — 같은 이유입니다. 문서가 인용하는 111초는 migrate_scan_copy.py의 이관 시간이지 적재 처리량이 아닙니다
  • ssl_check_hostname 비대칭 — 의도된 측정 조건이고 01-differences.md에 절을 따로 두어 설명하고 있습니다

각 스레드에 개별로 답을 달아 뒀습니다.

@my-reviewer-agent my-reviewer-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이번 리뷰는 부분 리뷰입니다. PR 전체 변경 중 redis/migration-lab/load_data.py 와 results/ 아래 JSON 3개(path-a-rdb.json, policy-matrix-ent.json, policy-matrix-oss.json)만 확인했고, 나머지 변경 파일은 이번 검토 범위에 포함되지 않았습니다. 따라서 전체 변경에 대한 완전한 검토를 주장하지 않습니다.

먼저 이전 SHA 지적 중 이번 diff에서 해결된 부분을 확인했습니다. connect()가 ssl_cert_reqs="required"로 바뀌어 TLS 인증서 검증이 켜졌고, resolve_password()가 환경 변수·프롬프트 경로를 우선하도록 정리되었으며, share()가 divmod로 나머지를 앞쪽 워커에 배분해 계획 키 수와 실제 적재량이 맞아떨어지고, 워커 exitcode를 확인해 조용한 실패가 "적재 완료"로 찍히지 않게 되었습니다. --workers < 1 방어도 추가되었습니다. 이미 지적했던 항목들은 반복하지 않습니다.

이번에 새로 눈에 띈 것은 두 가지입니다. (1) TLS 검증을 켜면서 인증서 체인은 검증하지만 호스트네임 일치는 확인하지 않는 상태라 검증 강화가 절반만 이루어진 점, (2) reporter가 5초 스로틀 출력만 하고 종료 시 마지막 줄을 찍지 않아 진행률이 100%에 닿는 화면이 남지 않는 점입니다. 둘 다 랩 도구 수준에서 차단 이슈는 아닙니다.

results/ 아래 JSON은 실측 로그로서 runs/ok_count/stable 구조가 일관되고, 노드 주소가 로 마스킹된 점, admin_command_note로 클러스터 클라이언트 행의 실패가 서버 제약이 아니라 redis-py 사정임을 명시한 점, shards_note로 policy-matrix-oss.json과 clustering-policy.json의 단일 키 SET 결과 차이를 샤드 수 차이로 설명한 점은 이전 지적이 반영된 좋은 개선입니다. path-a-rdb.json도 import 실패를 테넌트 정책 수준까지 특정해 두어 재현 가치가 큽니다. 다만 policy-matrix-oss.json의 클러스터 클라이언트 크로스슬롯 결과에서 EXISTS/TOUCH/UNLINK/DEL이 "성공"으로 남는 근거가 서버의 크로스 슬롯 허용이 아니라 클라이언트 측 키 분할이라는 점이 표에 드러나지 않아 인라인으로 남겼습니다.

확인한 범위에서 차단 이슈는 없어 COMMENT로 제출합니다.

PR_REVIEW_SCOPE_V1 {"included_files":["redis/migration-lab/load_data.py","redis/migration-lab/results/path-a-rdb.json","redis/migration-lab/results/policy-matrix-ent.json","redis/migration-lab/results/policy-matrix-oss.json"],"omitted_files":[".github/workflows/docs.yml","AGENTS.md","CLAUDE.md","README.md","aisearch/custom_vectorization/01_custom_embedding_guide.md","aisearch/custom_vectorization/ref_bge_m3_vs_qwen3_comparison.md","aks/argocd_image_updater.md","memory/README.md","redis/azure-cache-to-managed-redis-migration.md","redis/migration-guide/01-differences.md","redis/migration-guide/02-client-audit.md","redis/migration-guide/03-migration-paths.md","redis/migration-lab/README.md","redis/migration-lab/audit_commands.sh","redis/migration-lab/concurrent_writer.py","redis/migration-lab/import_rdb.py","redis/migration-lab/migrate_scan_copy.py","redis/migration-lab/policy_matrix_test.py","redis/migration-lab/results/amr-memory-sizing.json","redis/migration-lab/results/clustering-policy.json","redis/migration-lab/results/path-b-repeat-pass.json","redis/migration-lab/results/path-b-scan-copy.json","redis/migration-lab/results/path-c-tooling.json","redis/migration-lab/verify_migration.py","redis/nodejs/cluster_failover_recovery.md","scripts/check_docs.py","scripts/gen_index.py"],"original_changed_lines":6271,"original_diff_lines":6525,"original_diff_unavailable_reason":null}

port=args.port,
password=args.password,
ssl=True,
ssl_cert_reqs="required",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

심각도: 보통(보안). ssl_cert_reqs="required"로 인증서 체인 검증은 켜졌지만 redis-py의 StrictRedis는 ssl_check_hostname을 명시하지 않으면 기본적으로 호스트네임 일치를 확인하지 않습니다. 이 상태에서는 신뢰된 CA가 발급한 임의의 다른 도메인 인증서로도 연결이 성립하므로, 액세스 키가 오가는 경로에서 중간자 공격을 막는 효과가 반쪽만 남습니다. 같은 PR의 results/policy-matrix-*.json이 ssl_check_hostname_disabled_for_cluster_client를 별도 필드로 기록할 만큼 이 설정을 중요한 측정 조건으로 다루고 있는 만큼, 로더에서도 명시하는 편이 일관됩니다. 수정 제안: connect()의 인자에 ssl_check_hostname=True를 추가하고, 랩 환경에서 부득이하게 끄는 경우에만 명시적 플래그(예: --insecure-tls)로만 False가 되도록 하십시오.

while True:
item = progress.get()
if item is None:
break

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

심각도: 낮음(관측성). reporter는 5초 스로틀 조건(now - last_print > 5) 안에서만 출력하고, 센티널 None을 받으면 곧바로 break하므로 마지막 구간의 진행량은 화면에 남지 않습니다. 결과적으로 계획 키 수 대비 진행률이 100%로 찍히는 줄이 한 번도 나오지 않고, 마지막 출력이 예를 들어 97%에서 끊긴 채 곧바로 "=== 적재 완료 ==="로 넘어갑니다. 로더 로그를 그대로 문서 근거로 붙이는 이 랩의 성격상 오해를 부르기 쉽습니다. 수정 제안: break 직전(또는 while 루프 종료 직후)에 스로틀을 무시하고 done/total_expected와 counts를 한 번 더 출력하는 최종 요약 print를 추가해, 큐로 집계된 실제 적재량이 계획치와 일치하는지 화면에서 바로 확인되게 하십시오.

"error": "MSET - all keys must map to the same key slot"
},
{
"command": "EXISTS(다중)",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

심각도: 낮음(근거 해석). 클러스터 클라이언트의 크로스슬롯 결과에서 MGET/MSET은 RedisClusterException(all keys must map to the same key slot)으로 실패하는데 EXISTS(다중)/TOUCH(다중)/UNLINK(다중)/DEL(다중)은 ok_count 3으로 "성공"으로 기록되어 있습니다. 이 성공은 서버가 크로스 슬롯 다중 키를 허용해서가 아니라 redis-py의 RedisCluster가 해당 명령들을 키별로 분해해 각 노드에 나눠 보내고 결과를 합치기 때문입니다. 같은 파일의 비클러스터 클라이언트 행에서는 동일 명령이 모두 ClusterCrossSlotError로 실패한다는 점이 그 근거입니다. 지금 표만 보면 "AMR OSSCluster는 EXISTS/DEL의 크로스 슬롯을 지원한다"로 읽혀 클라이언트 감사 결론이 뒤집힐 수 있습니다. 수정 제안: 이 네 케이스에 admin_command_note와 같은 형태의 note 필드(예: "클라이언트가 키별로 분할 전송해 성공한 것이며 서버의 크로스 슬롯 허용이 아니다")를 추가하거나, tier 값을 "허용목록(클라이언트 분할)"처럼 구분해 서버 판단과 클라이언트 동작을 분리해 기록하십시오.

표를 연달아 쌓아 결론을 셀 안에 넣던 서술을 고쳤습니다.
사실, 숫자, 링크, 결론은 그대로 두고 배치와 연결 문장만 바꿨습니다.

- 마이그레이션 가이드: 핵심 요약·1·3·4·5.1절에 표가 답하는 질문을 문장으로 선행
  3절의 요구/답/근거 표는 논증이 셀에 갇혀 있어 문장으로 풀어냄
- 01-differences: 1절을 여섯 개의 나열된 표에서 "다른 소프트웨어"라는
  한 사실이 어디까지 번지는지 따라가는 사슬로 재구성. 2.1·2.3절에 도입과 전환 추가
- 02-client-audit: 1·2·3·5·6·7절이 표로 시작하던 것을 뒤집고,
  4절 TIER 1 표의 "왜" 열을 본문으로 이동 (SKU별 DB 개수 포함)
- 03-migration-paths: 3·4.6절 도입 추가.
  4.7절 흐름도가 문서 분할 이전 절 번호(8.x·9.1)를 가리키던 것을 수정

AGENTS.md에 3절 "어떻게 설명하는가"와 4.1절 "표에는 조회할 것만 넣습니다"를
추가했습니다. 기존 규칙이 표 셀 형식만 정하고 무엇을 셀에 넣을지는 정하지
않아, 다음 문서에서 같은 문제가 반복됩니다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@my-reviewer-agent my-reviewer-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 리뷰는 부분 리뷰입니다. 이번 검토에서는 redis/migration-lab/load_data.py, redis/migration-lab/results/path-a-rdb.json, redis/migration-lab/results/policy-matrix-ent.json, redis/migration-lab/results/policy-matrix-oss.json 네 파일만 확인했고, 나머지 변경 파일은 이번 검토 범위에 들어 있지 않습니다. 따라서 PR 전체 변경에 대한 완전한 검토를 주장하지 않습니다.

먼저 이전 리뷰에서 지적했던 load_data.py의 항목들이 이번 SHA에서 정리된 점을 확인했습니다. ssl_cert_reqs가 required로 바뀌어 TLS 인증서 검증이 켜졌고, resolve_password()가 환경 변수와 getpass 프롬프트를 거치도록 바뀌어 액세스 키가 ps/셸 히스토리에 남는 경로가 줄었으며, proc.exitcode를 모아 워커가 조용히 죽은 경우를 실패로 처리하고, 모듈 상단 docstring에 큰 해시가 --target-gb 환산에서 제외된다는 점을 명시했습니다. 해당 항목들은 다시 제기하지 않습니다.

results/ 아래 JSON들은 이번에도 실측 로그 성격의 산출물로 구조가 일관됩니다. policy-matrix-oss.json은 shards_note에서 CLUSTER SLOTS 응답 노드가 1개라 슬롯 0-16383을 한 샤드가 전부 갖는 구성이며 그래서 비클러스터 클라이언트의 단일 키 SET이 통과한다는 점을, 샤드가 여러 개인 구성과의 차이까지 포함해 명확히 적어 두었습니다. admin_command_note에서 관리명령 실패 중 서버 판단(ResponseError)과 redis-py 클라이언트 사정(AttributeError, doesn't exist in Redis commands, No way to dispatch)을 분리해 오독을 막은 것도 결과 해석 관점에서 정확합니다. policy-matrix-ent.json은 EnterpriseCluster에서 클러스터 클라이언트 연결 자체가 "Cluster mode is not enabled on this node"로 실패한다는 사실까지 남겨 두어 정책별 차이가 재현 가능하게 정리되어 있습니다. path-a-rdb.json도 import 실패를 테넌트 정책과 --sas-uris 전용 제약이라는 근본 원인 수준으로 특정하고 403 근거까지 남겨 재현 가치가 있습니다. 이 세 파일에 대해서는 별도 지적 사항이 없습니다.

이번에 새로 확인한 load_data.py 관련 보완점 세 가지는 인라인으로 남겼습니다. 요약하면 (1) ssl_cert_reqs만 켜고 ssl_check_hostname을 지정하지 않아 호스트명 검증이 빠지는 문제, (2) 큰 해시 적재가 진행률 단위와 병렬화 양쪽에서 실제 작업량과 어긋나는 문제, (3) --workers만 검증하고 --target-gb는 검증하지 않아 0 이하 값에서 아무것도 적재하지 않고도 성공으로 끝나는 문제입니다. 모두 랩 도구 범위에서 차단 이슈는 아니라고 판단해 COMMENT로 남깁니다.

PR_REVIEW_SCOPE_V1 {"included_files":["redis/migration-lab/load_data.py","redis/migration-lab/results/path-a-rdb.json","redis/migration-lab/results/policy-matrix-ent.json","redis/migration-lab/results/policy-matrix-oss.json"],"omitted_files":[".github/workflows/docs.yml","AGENTS.md","CLAUDE.md","README.md","aisearch/custom_vectorization/01_custom_embedding_guide.md","aisearch/custom_vectorization/ref_bge_m3_vs_qwen3_comparison.md","aks/argocd_image_updater.md","memory/README.md","redis/azure-cache-to-managed-redis-migration.md","redis/migration-guide/01-differences.md","redis/migration-guide/02-client-audit.md","redis/migration-guide/03-migration-paths.md","redis/migration-lab/README.md","redis/migration-lab/audit_commands.sh","redis/migration-lab/concurrent_writer.py","redis/migration-lab/import_rdb.py","redis/migration-lab/migrate_scan_copy.py","redis/migration-lab/policy_matrix_test.py","redis/migration-lab/results/amr-memory-sizing.json","redis/migration-lab/results/clustering-policy.json","redis/migration-lab/results/path-b-repeat-pass.json","redis/migration-lab/results/path-b-scan-copy.json","redis/migration-lab/results/path-c-tooling.json","redis/migration-lab/verify_migration.py","redis/nodejs/cluster_failover_recovery.md","scripts/check_docs.py","scripts/gen_index.py"],"original_changed_lines":6500,"original_diff_lines":6754,"original_diff_unavailable_reason":null}

port=args.port,
password=args.password,
ssl=True,
ssl_cert_reqs="required",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

심각도: 보통(보안). ssl_cert_reqs="required"로 인증서 체인 검증은 켜졌지만 ssl_check_hostname이 지정되어 있지 않습니다. redis-py의 SSLConnection은 ssl_check_hostname 기본값이 False라서, 이 상태에서는 공인 CA가 발급한 다른 호스트의 유효한 인증서를 제시하는 중간자에게도 연결이 성립합니다. 즉 체인 검증만으로는 지금 접속하려는 --host가 맞는지 확인하지 못합니다.

근거: connect()가 host=args.host, ssl=True, ssl_cert_reqs="required"만 넘기고 ssl_check_hostname을 넘기지 않습니다. 같은 랩의 결과 산출물에서도 ssl_check_hostname을 명시적으로 기록하고 있어(policy-matrix-*.json의 ssl_check_hostname 필드) 이 값이 검증 의미를 갖는 축이라는 점이 드러납니다.

수정: connect()의 인자에 ssl_check_hostname=True를 추가하세요. 클러스터 클라이언트처럼 노드 IP로 붙어야 해서 호스트명 검증을 끌 수밖에 없는 경우가 아니라면, 이 스크립트는 FQDN으로 접속하므로 True로 두는 데 제약이 없습니다.

for j in range(chunk_start, min(chunk_start + 5_000, BIG_HASH_FIELDS))
}
r.hset(key, mapping=mapping)
progress.put(("bighash", 1))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

심각도: 낮음~보통(성능·계측). 큰 해시 적재가 진행률 단위와 병렬화 양쪽에서 실제 작업량과 어긋납니다.

근거: load_big_hashes()는 BIG_HASH_COUNT(50) × BIG_HASH_FIELDS(100,000) = 500만 필드를 단일 프로세스에서 5,000 필드 청크로 순차 기록하는데, progress에는 해시 하나가 끝날 때마다 ("bighash", 1)만 넣습니다. 반면 total_expected는 string/hash/list/zset 키 개수에 BIG_HASH_COUNT를 그대로 더한 값이라, 500만 필드 쓰기가 전체 진행률에서 50단위로만 계산됩니다. 결과적으로 (1) 다른 워커가 모두 끝난 뒤 진행률이 사실상 멈춘 것처럼 보이는 긴 구간이 생기고, (2) 마지막에 출력하는 "평균 처리량 : {loaded / elapsed} keys/s"가 이 구간의 비용을 키 개수 기준으로만 반영해 실제 처리량을 과소평가합니다. --workers를 올려도 이 부분은 전혀 빨라지지 않습니다.

수정: progress.put을 청크 단위로 옮겨 ("bighash", 청크에 쓴 필드 수 또는 청크 1개)를 보고하고 total_expected도 같은 단위로 맞추거나, load_big_hashes를 키 단위로 분할해 여러 Process에 나눠 주면(예: share()와 같은 방식으로 BIG_HASH_COUNT를 워커에 배분) 진행률 왜곡과 직렬 병목이 함께 해소됩니다.

p.add_argument("--workers", type=int, default=8)
args = p.parse_args()

if args.workers < 1:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

심각도: 낮음(입력 검증·오류 처리). --workers는 1 이상인지 검증하는데 --target-gb에는 같은 검증이 없습니다.

근거: args.target_gb가 0이면 string_count/hash_count/list_count/zset_count가 모두 0이 되어 일반 워커는 아무것도 쓰지 않고, 음수면 int() 결과가 음수가 되어 range()가 빈 반복이 됩니다. 두 경우 모두 예외 없이 진행되어 exitcode 검사도 통과하고 마지막에 "=== 적재 완료 ==="와 함께 적재한 키가 사실상 0인 상태로 성공 종료합니다. 이 스크립트의 출력값은 이후 이관 시간 측정의 기준이 되므로, 오타 하나로 빈 데이터셋 위에서 측정이 진행될 수 있습니다.

수정: --workers 검증 바로 옆에 다음을 추가하세요.

if args.target_gb <= 0:
    p.error("--target-gb는 0보다 커야 합니다")

설명 방식에 이어 문체와 제목 형태를 참고 도서(클린 아키텍처 파이썬)의
서술 방식에 맞췄습니다. 사실·숫자·링크는 그대로 두고 배치와 어투만 바꿨습니다.

- 마이그레이션 묶음 4개 문서를 합니다체 → 한다체로 전환
  (코드 블록 주석 포함. 조회용 표 셀은 기존대로 명사구 유지)
- 종결어미로 끝나던 절 제목 10개를 명사구로 변경.
  의문형 제목("왜 안 되는가", "얼마나 줄어드나")은 그대로 둠
- 이름|설명 2열 설명형 표 4개를 `**이름**: 설명 문장.` 불릿으로 전환
  (정책 변경 방향, 명령어 감사 대상, 정적 스캔의 한계,
   마이그레이션 도구 제약, Option 1 권장 근거)
- 실측 표 뒤에 "이 표에서 읽어 낼 것은 셋이다" 형태의 해설 불릿 추가
- 제목 변경에 따른 인바운드 앵커 15개 갱신
  (redis/nodejs/cluster_failover_recovery.md 포함)
- 03-migration-paths.md의 stale 참조 수정: [5·6절] → [1·2절]

AGENTS.md/CLAUDE.md도 함께 갱신해 다음 문서에서 되풀이하지 않게 했습니다.
- 2.1에 명사구 절 제목 규칙 추가 (의문형 예외 명시)
- 4.2에 설명형 2열 표 → 명명된 불릿 규칙 추가
- 3.5 문체 라우팅 수정: "가이드"라는 이름이 아니라 글이 하는 일로 판단.
  실측 기반 비교·리서치 묶음은 한다체
- AGENTS.md 자체의 문장형 소제목 7개도 명사구로 (본문은 합니다체 유지)

검사: check_docs.py --strict redis/ AGENTS.md CLAUDE.md → 오류 0 / 경고 0
      gen_index.py --check → 최신
      측정값·인라인 코드 토큰·링크 대상 파일 HEAD와 동일함을 확인

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@my-reviewer-agent my-reviewer-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 리뷰는 부분 리뷰입니다. 이번 SHA에서 확인한 파일은 redis/migration-lab/load_data.py와 results/path-a-rdb.json, results/policy-matrix-ent.json, results/policy-matrix-oss.json 네 개뿐이며, 나머지 변경 파일은 검토 범위에 들어 있지 않습니다. 따라서 PR 전체 변경에 대한 완전한 검토를 주장하지 않습니다.

먼저 이전 리뷰에서 지적했던 load_data.py의 문제 대부분이 이번 SHA에서 해소된 점을 확인했습니다. connect()가 ssl_cert_reqs="required"로 바뀌어 TLS 검증을 살렸고, resolve_password()가 환경 변수와 프롬프트 경로를 우선하도록 정리되었으며, 자식 프로세스의 exitcode를 모아 비정상 종료 시 1을 반환하도록 바뀌어 "조용한 부분 적재"가 성공으로 보고되지 않습니다. --target-gb 환산에 큰 해시가 빠진다는 점도 모듈 docstring과 환산부 주석에 명시되어 수치 해석의 혼선이 줄었습니다. 해당 항목들은 반복 지적하지 않습니다.

results/ 아래 JSON들은 실측 로그 성격의 산출물로 구조가 일관됩니다. policy-matrix-oss.json은 shards_note에서 CLUSTER SLOTS 노드가 1개라 단일 키 SET이 성공했고 다중 샤드 구성에서는 MovedError로 갈렸다는 점을 밝혀, 다른 결과 파일과의 모순을 구성 차이로 정직하게 설명합니다. 또 admin_command_note가 클러스터 클라이언트 행의 실패(AttributeError, "doesn't exist in Redis commands", "No way to dispatch")를 서버 미지원 근거로 읽으면 안 된다고 못 박은 것은 이 매트릭스에서 가장 오독되기 쉬운 부분을 잘 막아 둔 서술입니다. policy-matrix-ent.json도 같은 주석 체계를 공유하고 runs/ok_count/stable로 재현성을 남겨 근거로 충분합니다. path-a-rdb.json은 import 실패를 테넌트 정책(publicNetworkAccess 강제)과 az CLI의 --sas-uris 전용 제약까지 특정해 두어 재현 가치가 높습니다. 이 파일들에 대해서는 별도 지적 사항이 없습니다.

load_data.py에서 새로 눈에 띈 두 가지는 인라인으로 남겼습니다. 하나는 진행률 Queue 소비자(reporter)가 죽었을 때 워커의 put이 막혀 메인의 join이 무한 대기할 수 있다는 점이고, 다른 하나는 dbsize 차분으로 적재량을 보고하는 방식이 재실행 시 키 이름이 겹쳐 실제 적재량을 심하게 과소 보고한다는 점입니다. 둘 다 랩 도구 범위에서 차단 이슈는 아니라고 판단해 COMMENT로 남깁니다.

PR_REVIEW_SCOPE_V1 {"included_files":["redis/migration-lab/load_data.py","redis/migration-lab/results/path-a-rdb.json","redis/migration-lab/results/policy-matrix-ent.json","redis/migration-lab/results/policy-matrix-oss.json"],"omitted_files":[".github/workflows/docs.yml","AGENTS.md","CLAUDE.md","README.md","aisearch/custom_vectorization/01_custom_embedding_guide.md","aisearch/custom_vectorization/ref_bge_m3_vs_qwen3_comparison.md","aks/argocd_image_updater.md","memory/README.md","redis/azure-cache-to-managed-redis-migration.md","redis/migration-guide/01-differences.md","redis/migration-guide/02-client-audit.md","redis/migration-guide/03-migration-paths.md","redis/migration-lab/README.md","redis/migration-lab/audit_commands.sh","redis/migration-lab/concurrent_writer.py","redis/migration-lab/import_rdb.py","redis/migration-lab/migrate_scan_copy.py","redis/migration-lab/policy_matrix_test.py","redis/migration-lab/results/amr-memory-sizing.json","redis/migration-lab/results/clustering-policy.json","redis/migration-lab/results/path-b-repeat-pass.json","redis/migration-lab/results/path-b-scan-copy.json","redis/migration-lab/results/path-c-tooling.json","redis/migration-lab/verify_migration.py","redis/nodejs/cluster_failover_recovery.md","scripts/check_docs.py","scripts/gen_index.py"],"original_changed_lines":6554,"original_diff_lines":6808,"original_diff_unavailable_reason":null}

for proc in procs:
proc.start()
for proc in procs:
proc.join()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

severity: warning (신뢰성)

근거: 모든 워커는 progress.put()으로 reporter 프로세스에 진행률을 보내고, 메인은 여기서 proc.join()으로 무기한 대기합니다. reporter는 별도 프로세스이고 그 exitcode는 아래 failed 검사 대상에서도 빠져 있습니다. reporter가 어떤 이유로든(예: 출력 파이프 오류, 예기치 못한 예외) 먼저 죽으면 Queue의 내부 버퍼가 차는 순간 워커의 put이 블록되고, 워커가 끝나지 않으므로 메인의 join()도 영원히 돌아오지 않습니다. GB 단위 적재는 수십 분이 걸리므로 이 상태는 "느린 것"과 구분되지 않아 랩 실행이 통째로 날아갑니다.

수정 제안: join을 무한 대기 대신 타임아웃 루프로 바꾸고 매 주기마다 rep.is_alive()를 확인해, reporter가 죽었으면 남은 워커를 terminate()하고 오류로 종료하도록 하십시오. 예: while any(p.is_alive() for p in procs): if not rep.is_alive(): [p.terminate() for p in procs]; print("진행률 리포터가 비정상 종료했습니다", file=sys.stderr); return 1; time.sleep(1) 이후 각 proc.join(). 아울러 아래 failed 목록에 rep.exitcode도 함께 포함해 리포터 실패가 성공으로 보고되지 않게 하는 편이 좋습니다.

elapsed = time.time() - start
info = r.info("memory")
dbsize = r.dbsize()
loaded = dbsize - before

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

severity: warning (측정 정확도)

근거: loaded는 dbsize 차분(dbsize - before)으로 계산되는데, 이 스크립트가 만드는 키는 cache:string:{worker_id}:{i}처럼 실행마다 동일한 결정적 이름입니다. 따라서 같은 인스턴스에 두 번째로 적재하면 기존 키를 덮어쓰기만 하므로 dbsize가 거의 늘지 않고, 실제로는 수백만 건을 썼는데도 "적재한 키"가 0에 가깝게 찍힙니다. 바로 아래 평균 처리량도 loaded를 분모 재료로 쓰므로 함께 왜곡되고, 이 수치는 문서에 인용되는 값이라 마이그레이션 시간 비교의 기준을 흔듭니다.

수정 제안: 실제로 쓴 건수를 reporter가 집계한 done(또는 계획값 total)과 함께 출력하고, dbsize는 "적재 전/후" 참고값으로만 표기하십시오. 재실행 시 겹침 자체를 없애려면 키 접두사에 실행 식별자를 넣는 방법(예: 시작 시각 기반 run_id를 인자로 받아 cache:string:{run_id}:{worker_id}:{i})이 가장 확실하며, 그게 과하다면 적어도 loaded가 계획값보다 현저히 작을 때 덮어쓰기 가능성을 경고로 출력해야 합니다.

hellices and others added 2 commits August 28, 2026 10:13
3절만 "셋 남는다"고 예고해 놓고 셋을 산문 한 문단에 뭉쳐 넣고 있었다.
같은 문서 4절("이유는 세 가지다")과 6.2절("주의할 점은 셋이다")은
예고 → 불릿으로 제대로 가는데 3절만 어긋나 있었고, 그 결과
표·불릿·코드 없는 산문이 6문단 연속으로 이어졌다.

- 정책으로 덮이지 않는 셋(다중 DB, 허용 목록 밖 다중 키 명령,
  키스페이스 알림)을 명명된 불릿으로 분리
- "다운타임 없이"의 막힌 두 경로(마이그레이션 도구, REPLICAOF)도
  열거이므로 같은 형태로 분리하고, 종합 문장을 뒤에 붙임
- 4절로 넘기는 연결 문장 추가 (AGENTS.md 3.3)
- 산문 최대 연속 6문단 → 4문단

내용은 그대로다. 측정값·링크 대상·인라인 코드 토큰이 이전 커밋과 동일함을
대조로 확인했고, check_docs.py --strict 9개 문서 오류 0 · 경고 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
제목이 고객 요구를 큰따옴표로 인용해 "…에 대한 답" 형태로 감싸고 있었다.
절이 실제로 확정하는 것은 두 값이다 — 클라이언트를 어디까지 안 고칠 수
있는가, 다운타임 하한이 얼마인가. 그것을 그대로 제목에 쓴다.

- 제목: "클라이언트 수정 없이, 다운타임 없이"에 대한 답
        → 클라이언트 무수정 가능 범위와 다운타임 하한
- 도입부의 "그래서 따로 답을 적어 둔다" 메타 문장 제거
- 본문 표지어를 기술 용어로: 클라이언트 수정 없이 → 클라이언트 무수정,
  다운타임 없이 → 다운타임 0
- 맺음 문장의 인용 표현 제거
- 목적별 읽기 경로의 인바운드 앵커 1건 갱신

측정값·인라인 코드·링크 대상 파일이 이전 커밋과 동일함을 대조로 확인했고,
check_docs.py --strict 9개 문서 오류 0 · 경고 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@my-reviewer-agent my-reviewer-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 리뷰는 부분 리뷰입니다. 이번 SHA에서 확인한 파일은 redis/migration-lab/load_data.py, redis/migration-lab/results/path-a-rdb.json, redis/migration-lab/results/policy-matrix-ent.json, redis/migration-lab/results/policy-matrix-oss.json 네 개뿐이며, 나머지 변경 파일은 검토 범위에 포함되지 않았습니다. 따라서 PR 전체 변경에 대한 완전한 검토를 주장하지 않습니다.

먼저 이전 리뷰에서 지적했던 load_data.py의 주요 항목들이 이번 SHA에서 해소된 점을 확인했습니다. ssl_cert_reqs가 "required"로 바뀌어 TLS 검증이 켜졌고, resolve_password()가 환경 변수·프롬프트 경로를 제공해 액세스 키를 커맨드라인에 남기지 않아도 되며, 자식 프로세스의 exitcode를 모아 비정상 종료 시 stderr로 알리고 1을 반환하도록 바뀌었습니다. --target-gb 환산에 큰 해시가 빠진다는 점도 모듈 docstring과 주석에 명시되어 오해 소지가 줄었습니다. 이 부분들은 다시 지적하지 않습니다.

results/ 아래 JSON 산출물은 이번에도 실측 로그 성격이며 구조가 일관됩니다. policy-matrix-oss.json은 shards_note와 admin_command_note를 통해 "샤드가 1개라 비클러스터 클라이언트의 단일 키 SET이 성공한다", "클러스터 클라이언트 행의 실패는 대체로 redis-py 사정이지 서버 미지원 근거가 아니다"라는 해석 함정을 스스로 밝혀 두어, 결과를 잘못 인용할 위험을 크게 줄여 줍니다. runs/ok_count/stable을 함께 남긴 것도 재현 가능성 면에서 좋습니다. path-a-rdb.json도 실패 원인을 테넌트 정책(publicNetworkAccess 강제)까지 특정하고 403 근거를 남겨 두어 문서 인용 자료로 충분합니다. 이 파일들에 대해서는 별도 지적 사항이 없습니다.

남은 지적은 load_data.py의 진행률·병렬성 관련 두 건이며, 모두 랩 도구 범위의 개선 제안이라 차단 이슈로 보지 않고 COMMENT로 남깁니다.

PR_REVIEW_SCOPE_V1 {"included_files":["redis/migration-lab/load_data.py","redis/migration-lab/results/path-a-rdb.json","redis/migration-lab/results/policy-matrix-ent.json","redis/migration-lab/results/policy-matrix-oss.json"],"omitted_files":[".github/workflows/docs.yml","AGENTS.md","CLAUDE.md","README.md","aisearch/custom_vectorization/01_custom_embedding_guide.md","aisearch/custom_vectorization/ref_bge_m3_vs_qwen3_comparison.md","aks/argocd_image_updater.md","memory/README.md","redis/azure-cache-to-managed-redis-migration.md","redis/migration-guide/01-differences.md","redis/migration-guide/02-client-audit.md","redis/migration-guide/03-migration-paths.md","redis/migration-lab/README.md","redis/migration-lab/audit_commands.sh","redis/migration-lab/concurrent_writer.py","redis/migration-lab/import_rdb.py","redis/migration-lab/migrate_scan_copy.py","redis/migration-lab/policy_matrix_test.py","redis/migration-lab/results/amr-memory-sizing.json","redis/migration-lab/results/clustering-policy.json","redis/migration-lab/results/path-b-repeat-pass.json","redis/migration-lab/results/path-b-scan-copy.json","redis/migration-lab/results/path-c-tooling.json","redis/migration-lab/verify_migration.py","redis/nodejs/cluster_failover_recovery.md","scripts/check_docs.py","scripts/gen_index.py"],"original_changed_lines":6566,"original_diff_lines":6820,"original_diff_unavailable_reason":null}

for j in range(chunk_start, min(chunk_start + 5_000, BIG_HASH_FIELDS))
}
r.hset(key, mapping=mapping)
progress.put(("bighash", 1))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

severity: warning (성능·진행률 신뢰성)

evidence: load_big_hashes()는 BIG_HASH_COUNT(50) × BIG_HASH_FIELDS(100_000) = 500만 필드를 단일 프로세스에서 순차로 씁니다. 반면 진행률에는 큰 해시 1개당 ("bighash", 1)만 올라가므로, 이 프로세스가 전체 작업량의 대부분을 차지하면서도 total에서는 50개로만 잡힙니다(총계 계산은 아래 total = ... + BIG_HASH_COUNT 라인). 결과적으로 다른 워커가 모두 끝난 뒤에도 이 프로세스만 오래 남아 돌지만 진행률 표시는 거의 100%에 붙어 있어, 적재가 멈춘 것인지 진행 중인지 구분하기 어렵습니다. 큰 해시는 이 스크립트에서 가장 오래 걸리는 구간이므로 벽시계 시간에도 그대로 영향을 줍니다.

fix: (1) 5_000 필드 청크를 쓸 때마다 progress.put(("bighash", 1))을 올리고 total 계산도 BIG_HASH_COUNT × (BIG_HASH_FIELDS // 5_000) 단위로 맞춰 진행률 분모와 분자를 같은 단위로 통일하고, (2) BIG_HASH_COUNT를 --workers 또는 별도 인자만큼 분할해 여러 Process로 나눠 실행하면(각 프로세스가 i % n == worker_id인 해시만 담당) 마지막 구간의 직렬 병목을 없앨 수 있습니다.

f"{rate:,.0f} items/s {counts}",
flush=True,
)
last_print = now

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

severity: suggestion (관측성)

evidence: reporter()는 now - last_print > 5인 경우에만 진행 상황을 출력하고, 센티널 None을 받으면 곧바로 break 합니다. 따라서 마지막 5초 구간에 들어온 항목은 절대 출력되지 않고, 실행이 끝나는 시점의 마지막 줄은 예를 들어 (94.3%) 같은 중간 값에서 멈춥니다. 이후 main()이 찍는 "적재한 키"는 dbsize 차이라서 progress 집계와 별개 값이며, 큐 집계가 계획한 total에 실제로 도달했는지는 로그만으로 확인할 수 없습니다. 워커가 조용히 일부만 쓰고 정상 종료한 경우(exitcode 0) 이 격차가 유일한 단서인데 그게 화면에 남지 않습니다.

fix: while 루프의 break 직전(또는 루프 종료 후)에 동일한 형식의 최종 라인을 무조건 한 번 출력하도록 하고, done과 total_expected가 다르면 경고 문구를 함께 찍으십시오. 예: 루프를 빠져나온 뒤 print(f"[{time.time()-start:6.0f}s] {done:,}/{total_expected:,} 최종 {counts}", flush=True)를 실행하고 done != total_expected일 때 stderr로 불일치를 알리는 방식입니다.

hellices and others added 2 commits August 28, 2026 10:30
"이 문서는 공식 가이드를 대체하지 않는다 / 먼저 읽어야 할 것은 공식 문서다
/ 이 묶음은 보조 자료다"는 정보를 주지 않고 자리만 차지한다. 공식 방법론이
3단계라는 사실과 이 묶음이 각 단계 어디에 대응하는지만 적으면, 기준이 어느
쪽인지는 표에서 저절로 드러난다.

- 제목: 이 문서 묶음과 공식 가이드 → 공식 마이그레이션 방법론과 이 묶음의 대응
- 도입 문단을 방법론 3단계 서술로 교체
- 참고 절에 남아 있던 "보조 자료다" 한 문장 제거
- AGENTS.md 6.2에 면책 문장 금지 규칙과 전후 예시 추가
- CLAUDE.md 리드 불릿에 같은 항목 추가

1절 앵커를 가리키는 인바운드 링크는 없음을 grep으로 확인했다.
측정값·링크 대상·인라인 코드 토큰이 이전 커밋과 동일하고,
check_docs.py --strict 9개 문서 오류 0 · 경고 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
§1 `공식 마이그레이션 방법론과 이 묶음의 대응` → `공식 마이그레이션 가이드 3단계`.
§1 안에 들어가 있던 `상세 문서`/`목적별 읽기 경로`는 `문서 구성`/`읽는 순서` H2로
앞머리에 올림 (memory/agent-memory/README.md 허브 형태).
03 §7 `이 문서가 측정하지 않은 것` → `측정하지 않은 것`, 인바운드 앵커 2건 갱신.

AGENTS.md 2.1에 규칙 추가 — 제목은 주제를 가리키고 문서 자체를 가리키지 않음,
길잡이 절은 번호 절 밖에. 6.2 도입부와 핵심 요약 7번의 "보조 자료" 표현 정리.

수치·링크 대상·인라인 코드 토큰은 §7 앵커 2건 외 변동 없음.
check_docs.py --strict: 문서 9개 · 오류 0건 · 경고 0건.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@my-reviewer-agent my-reviewer-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 리뷰는 부분 리뷰입니다. PR 전체 변경 파일 중 일부(redis/migration-lab/load_data.py, redis/migration-lab/results/path-a-rdb.json, redis/migration-lab/results/policy-matrix-ent.json, redis/migration-lab/results/policy-matrix-oss.json)만 확인했고 나머지 변경 파일은 이번 검토 범위에 포함되지 않았습니다. 따라서 전체 변경에 대한 완전한 검토를 주장하지 않습니다.

먼저 이전 리뷰에서 지적한 load_data.py의 네 가지는 이번 SHA에서 모두 해소된 것으로 확인했습니다. ssl_cert_reqs가 "required"로 바뀌어 TLS 인증서 검증이 살아났고, resolve_password가 환경 변수와 getpass 프롬프트를 우선 경로로 두어 액세스 키의 커맨드라인 노출을 피할 수 있게 되었으며, 적재 종료 후 proc.exitcode를 모아 비정상 종료 워커가 있으면 stderr 출력과 함께 1을 반환하도록 바뀌었습니다. 큰 해시가 --target-gb 환산에 포함되지 않는다는 점도 모듈 docstring과 환산 지점 주석에 명시되어 오해 소지가 줄었습니다. 같은 지적은 반복하지 않겠습니다.

results/ 아래 JSON들은 이번에도 실측 로그 성격이고 구조가 일관됩니다. policy-matrix-oss.json이 shards_note로 "CLUSTER SLOTS 노드가 1개라 슬롯을 한 샤드가 전부 가져가므로 비클러스터 클라이언트의 단일 키 SET이 성공한다"고 다른 결과 파일과의 차이를 스스로 설명한 점, 그리고 두 policy-matrix 파일이 공통으로 admin_command_note에서 클러스터 클라이언트 행의 실패가 redis-py 쪽 사정(AttributeError, 명령 테이블 부재, 디스패치 실패)이지 서버의 미지원 근거가 아니라고 못 박은 점은 이 데이터를 문서 근거로 인용할 때 생기기 쉬운 오독을 막아 줍니다. path-a-rdb.json도 import 실패를 테넌트 정책 이름 수준까지 특정하고 403 근거를 남겨 재현 가치가 있습니다. 이 세 파일에 대해서는 추가로 지적할 사항이 없습니다.

남은 의견은 load_data.py의 데이터 현실성과 진행률 관련 두 가지이며, 모두 랩 도구 수준에서 차단 이슈는 아니라고 판단해 COMMENT로 남깁니다.

PR_REVIEW_SCOPE_V1 {"included_files":["redis/migration-lab/load_data.py","redis/migration-lab/results/path-a-rdb.json","redis/migration-lab/results/policy-matrix-ent.json","redis/migration-lab/results/policy-matrix-oss.json"],"omitted_files":[".github/workflows/docs.yml","AGENTS.md","CLAUDE.md","README.md","aisearch/custom_vectorization/01_custom_embedding_guide.md","aisearch/custom_vectorization/ref_bge_m3_vs_qwen3_comparison.md","aks/argocd_image_updater.md","memory/README.md","redis/azure-cache-to-managed-redis-migration.md","redis/migration-guide/01-differences.md","redis/migration-guide/02-client-audit.md","redis/migration-guide/03-migration-paths.md","redis/migration-lab/README.md","redis/migration-lab/audit_commands.sh","redis/migration-lab/concurrent_writer.py","redis/migration-lab/import_rdb.py","redis/migration-lab/migrate_scan_copy.py","redis/migration-lab/policy_matrix_test.py","redis/migration-lab/results/amr-memory-sizing.json","redis/migration-lab/results/clustering-policy.json","redis/migration-lab/results/path-b-repeat-pass.json","redis/migration-lab/results/path-b-scan-copy.json","redis/migration-lab/results/path-c-tooling.json","redis/migration-lab/verify_migration.py","redis/nodejs/cluster_failover_recovery.md","scripts/check_docs.py","scripts/gen_index.py"],"original_changed_lines":6601,"original_diff_lines":6855,"original_diff_unavailable_reason":null}



def load_strings(args, worker_id, count, progress):
r = connect(args)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[중간] 워커 프로세스가 fork로 생성되면서 random 모듈의 시드 상태를 그대로 물려받습니다. load_strings/load_hashes/load_lists/load_zsets는 모두 make_value를 통해 random.randint와 random.choices에 의존하므로, worker_id만 다를 뿐 모든 워커가 동일한 값 시퀀스와 동일한 TTL 부여 패턴(random.random() < TTL_FRACTION)을 만들어 냅니다.

근거: connect(args) 직후 워커 안에서 random을 재시드하는 코드가 없고, compressible_value/incompressible_value 선택 자체도 random.random()으로 결정됩니다. incompressible_value만 os.urandom을 쓰므로 워커별로 달라집니다.

영향: 모듈 docstring이 밝힌 첫 번째 목적인 "RDB 압축률이 현실적이어야 export 시간과 blob 크기가 의미를 가진다"가 깨집니다. 워커 수만큼 완전히 동일한 페이로드가 반복되면 RDB 압축률이 실제 캐시보다 좋게 나오고, path-a-rdb.json의 compression_ratio_vs_memory 같은 수치를 다른 환경과 비교하기 어려워집니다. TTL 분포도 워커 간 동일해집니다.

수정 제안: 각 워커 함수 진입부(예: 이 줄의 connect 호출 직전)에서 random.seed(os.getpid()) 또는 random.seed((worker_id, os.getpid())) 형태로 재시드하십시오. load_big_hashes에도 동일하게 적용하면 됩니다.

list_count = int(target_bytes * 0.10 / 2200)
zset_count = int(target_bytes * 0.10 / 2000)

total = string_count + hash_count + list_count + zset_count + BIG_HASH_COUNT

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[낮음] 진행률의 분모에 큰 해시가 키 개수 기준(BIG_HASH_COUNT=50)으로만 들어가 있어 진행률이 실제 작업량을 반영하지 못합니다.

근거: total에 더해지는 값은 BIG_HASH_COUNT뿐이고, load_big_hashes는 해시 1개를 다 채운 뒤에야 progress.put(("bighash", 1))을 호출합니다. 그런데 큰 해시는 50개 × BIG_HASH_FIELDS(100,000) = 500만 필드로, 문자열/해시/리스트/zset 키 개수와 비교해도 무시할 수 없는 쓰기량입니다.

영향: 다른 워커가 모두 끝난 뒤 reporter는 100%에 가까운 진행률을 찍으면서 실제로는 큰 해시 적재가 한참 남아 있는 상태가 오래 지속됩니다. 5초 주기 출력의 items/s 값도 이 구간에서 의미를 잃습니다.

수정 제안: load_big_hashes의 청크 루프 안에서 chunk 단위 진행을 보고하고(예: progress.put(("bighash", 1))을 청크마다 호출), total 계산도 BIG_HASH_COUNT 대신 BIG_HASH_COUNT * (BIG_HASH_FIELDS // 5_000) 같은 청크 수로 맞추십시오. 최소한 적재 완료 출력에 큰 해시 필드 수를 별도로 찍어 두면 진행률 왜곡을 해석할 수 있습니다.

im-not-ai 분류 체계로 진단 → 윤문 → 대조 검증 3단계를 돌렸다.
문서별 진단이 지목한 지배 패턴만 겨냥했고, 내용은 건드리지 않았다.

주로 걷어낸 것

- C-11 연결어미 뒤 쉼표: 총 87건 제거, 신규 삽입 0건
  ending_comma_rate z값 +3.59~+4.69 → -0.29~+1.97
- C-8 이항 대립 과다: 01-differences 22 → 10건.
  ACR/AMR 대조가 문서 목적이라 실제 대비인 자리는 남겼다
- I-3/I-4 문단 끝 잠언조와 당위 마무리
- J-1 산문 인라인 볼드: 허브 109 → 80건. 표와 링크 텍스트는 제외
- E-2 같은 종결어미 연속

동결한 것

제목 줄 전부 바이트 동일(문서 간 앵커 참조 의존), 코드블록,
표 행/열 구조, 모든 숫자, URL과 링크 타깃, `> ` 인용 블록 개수.
5개 문서 전수 대조로 확인했다.

대조 검증에서 잡아 되돌린 의미 드리프트 7건

- 03-migration-paths 4.4/4.5절: "반드시 ~해야 한다"가 사실 단정으로
  내려앉음. "미리"까지 함께 소실돼 원문 서법으로 복원
- 03-migration-paths 5절: "추정이 아니라 문서에 명시된 값" 대구 삭제.
  직전 문단이 역산 추정이라 근거 층위를 가르는 자리여서 복원
- 01-differences 1절: 사역 해체가 "두 갈래 → 두 비용" 대응을
  "둘 다 해야 한다"는 당위로 바꿈. 같은 문서 표와 충돌해 복원
- 01-differences 2.1절: 등가인 두 오독 중 하나를 우세 독법으로 승격
- 허브 4절: "직접 정할 수 있다"가 단정으로 바뀌어 복원
- 허브 실측 결론: 표가 중간 지대를 정량화한다는 없던 서술 주입
- 02-client-audit 4/7절: 조건 연결어미 `-라면,` 쉼표 잔존분 제거

검증

verify_gates.py 5개 문서 전부 수렴(exit 0).
P0 문자 변경률 0.3~1.7%, P3 golden PASS(수치 주입·인용 이상 없음),
P5 서법 표지 원문과 동일.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@my-reviewer-agent my-reviewer-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 리뷰는 부분 리뷰입니다. 이번 SHA에서 확인한 파일은 redis/migration-lab/load_data.py, redis/migration-lab/results/path-a-rdb.json, redis/migration-lab/results/policy-matrix-ent.json, redis/migration-lab/results/policy-matrix-oss.json 네 개뿐이며, 나머지 변경 파일은 검토 범위에 포함되지 않았습니다. 따라서 PR 전체 변경에 대한 완전한 검토를 주장하지 않습니다.

이전 리뷰에서 지적했던 load_data.py 항목들(ssl_cert_reqs="none"로 TLS 검증 비활성화, 액세스 키의 커맨드라인 노출, 자식 프로세스 exitcode 미확인, --target-gb 환산에서 큰 해시 용량 누락, 워커 분배의 나머지 손실, ssl_check_hostname 미지정, reporter 종료 시 마지막 요약 미출력, 큰 해시 적재의 직렬성, dbsize 차분 기반 처리량 왜곡, results JSON의 노드 주소 마스킹·admin_command_note·shards_note)은 이번 diff에서 해소되었거나 이미 제기한 내용이므로 반복하지 않습니다.

results/ 아래 JSON은 이번에도 실측 로그 성격의 산출물로 구조가 일관됩니다. policy-matrix-oss.json의 shards_note는 CLUSTER SLOTS 응답 노드가 1개라는 구성 차이로 다른 결과 파일과의 단일 키 SET 결과 차이를 정직하게 설명하고, admin_command_note는 클러스터 클라이언트 행의 실패가 redis-py 사정이지 서버 미지원 근거가 아니라는 점을 못 박아 두어 인용 시 오독을 크게 줄여 줍니다. policy-matrix-ent.json도 동일한 주석 체계와 runs/ok_count/stable 구조를 공유합니다. path-a-rdb.json은 import 실패를 테넌트 정책과 az CLI의 --sas-uris 전용 제약까지 특정하고 403 근거를 남겨 재현 가치가 큽니다. 이 세 파일에는 추가 지적 사항이 없습니다.

이번에 새로 확인한 load_data.py 관련 두 건만 인라인으로 남깁니다. 하나는 fork로 생성된 워커들이 부모의 random 상태를 그대로 물려받아 동일한 값 시퀀스를 생성하므로 RDB 압축률 측정이 낙관적으로 치우칠 수 있다는 점이고, 다른 하나는 비클러스터 클라이언트 한 개로 dbsize/info를 읽어 요약을 만들기 때문에 클러스터형 엔드포인트에서는 수치가 한 노드분만 반영된다는 점입니다. 둘 다 랩 도구 범위에서 차단 이슈는 아니라고 판단해 COMMENT로 제출합니다.

PR_REVIEW_SCOPE_V1 {"included_files":["redis/migration-lab/load_data.py","redis/migration-lab/results/path-a-rdb.json","redis/migration-lab/results/policy-matrix-ent.json","redis/migration-lab/results/policy-matrix-oss.json"],"omitted_files":[".github/workflows/docs.yml","AGENTS.md","CLAUDE.md","README.md","aisearch/custom_vectorization/01_custom_embedding_guide.md","aisearch/custom_vectorization/ref_bge_m3_vs_qwen3_comparison.md","aks/argocd_image_updater.md","memory/README.md","redis/azure-cache-to-managed-redis-migration.md","redis/migration-guide/01-differences.md","redis/migration-guide/02-client-audit.md","redis/migration-guide/03-migration-paths.md","redis/migration-lab/README.md","redis/migration-lab/audit_commands.sh","redis/migration-lab/concurrent_writer.py","redis/migration-lab/import_rdb.py","redis/migration-lab/migrate_scan_copy.py","redis/migration-lab/policy_matrix_test.py","redis/migration-lab/results/amr-memory-sizing.json","redis/migration-lab/results/clustering-policy.json","redis/migration-lab/results/path-b-repeat-pass.json","redis/migration-lab/results/path-b-scan-copy.json","redis/migration-lab/results/path-c-tooling.json","redis/migration-lab/verify_migration.py","redis/nodejs/cluster_failover_recovery.md","scripts/check_docs.py","scripts/gen_index.py"],"original_changed_lines":6601,"original_diff_lines":6855,"original_diff_unavailable_reason":null}


def make_value(size):
# 캐시 페이로드는 보통 구조화된 텍스트가 많으므로 압축 가능한 쪽에 가중치를 둔다.
return compressible_value(size) if random.random() < 0.7 else incompressible_value(size)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

심각도: 경고(비차단). multiprocessing이 fork로 워커를 만들면 자식들은 부모의 random 모듈 시드 상태를 그대로 복제합니다. 그 결과 make_value()가 호출하는 random.random(), random.randint(), random.choices()가 모든 워커에서 동일한 순서의 값을 만들어 내고, cache:string:0:i와 cache:string:1:i의 페이로드가 사실상 같은 내용이 됩니다. 이 스크립트의 목적 자체가 모듈 docstring에 적힌 대로 "RDB 압축률이 현실적이어야 export 시간과 blob 크기가 의미를 가진다"인데, 워커 간 값이 중복되면 RDB의 사전 압축이 과하게 잘 먹혀 path-a-rdb.json의 compression_ratio_vs_memory(0.47) 같은 수치가 실제 워크로드보다 낙관적으로 나올 수 있습니다. 또한 random.random() < TTL_FRACTION 판정도 워커마다 같은 패턴으로 갈립니다.

구체적 수정: 각 워커 진입점(load_strings, load_hashes, load_lists, load_zsets, load_big_hashes)의 첫 줄에서 random.seed(os.getpid()) 또는 random.seed(os.urandom(16))로 재시드하십시오. 예를 들어 load_strings 시작부에 random.seed((worker_id, os.getpid(), time.time()))을 넣으면 워커별로 서로 다른 스트림이 보장되고, 필요하면 시드 값을 로그로 남겨 재현성도 유지할 수 있습니다.


elapsed = time.time() - start
info = r.info("memory")
dbsize = r.dbsize()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

심각도: 경고(비차단). 완료 요약의 r.dbsize()와 r.info("memory")는 connect()가 만든 비클러스터 redis.StrictRedis 연결 하나에서 읽습니다. 이 랩의 다른 산출물(policy-matrix-oss.json)이 보여 주듯 대상 인스턴스가 OSSCluster 정책이고 샤드가 여러 개인 구성에서는 이 두 명령이 접속한 단일 노드의 값만 돌려주므로, 적재한 키와 used_memory가 클러스터 전체가 아니라 한 노드분으로 축소되어 출력됩니다. 실제로 policy-matrix-oss.json의 shards_note는 이번 측정 대상이 샤드 1개짜리라서 비클러스터 클라이언트가 통했을 뿐이고 다중 샤드 구성에서는 단일 키 SET조차 대부분 MovedError였다고 기록하고 있어, 같은 스크립트를 다중 샤드 엔드포인트에 돌리면 요약 수치가 그대로 문서로 인용될 위험이 있습니다.

구체적 수정: 요약 직전에 r.info("cluster")["cluster_enabled"]를 확인해 1이면 redis.RedisCluster로 붙어 dbsize()/info("memory")의 노드별 결과를 합산해 출력하거나, 최소한 cluster_enabled=1인 경우 "아래 수치는 접속한 단일 노드 기준"이라는 경고 문구를 함께 찍어 수치가 전체 값으로 오인되지 않게 하십시오.

hellices and others added 3 commits August 29, 2026 10:33
문체가 아니라 구조를 손봤다.

- 상호참조 라벨 6건이 실제 절 번호와 어긋나 있었다. 앵커는 맞는 곳으로
  가는데 사람이 읽는 숫자만 낡은 상태였다 (01의 허브 참조 2건,
  02의 TIER 표 4건).
- 01 §1의 번호 없는 H4 여섯 개를 `### 1.1`~`### 1.6`으로 승격했다.
  같은 문서 §2가 이미 번호 H3 체계라 규칙이 둘이었다. §2.4 아래
  H4 두 개에도 2.4.1·2.4.2를 매겼다.
- 03의 H3 번호 뒤 마침표를 뺐고(`### 1.1.` → `### 1.1`) 번호 없는
  H3 네 개에 번호를 매겼다. 앵커 생성 시 마침표는 어차피 빠지므로
  인바운드 링크는 그대로다.
- 03 §6 제목에서 "부록"을, §6.1.1 제목에서 괄호 상호참조를 뺐다.
  제목은 문서 구조가 아니라 주제를 가리켜야 한다.
- TIER 등급표가 02 §3과 랩 README §0에 중복돼 있었다. 등급 정의는
  02가 근거지이므로 README 쪽은 다음 단계 안내 문장으로 줄였다.
  재현 함정 3항도 관측치가 붙어 있는 03 §6.2로 보냈다.

check_docs.py --strict redis/ → 문서 7개 · 오류 0건 · 경고 0건 (변동 없음)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
문서 5종에 반복되던 AI 지문을 걷어냈다.

- 개수 예고 열거(D-3) 9건. `…은 셋이다`·`…이 넷이다`처럼 뒤에 올 항목
  수를 미리 세어 주는 도입구를 내용으로 바꿨다. 코퍼스 전체에 걸친
  패턴이라 문서 하나만 봐서는 보이지 않았다.
- 연결어미 뒤 쉼표(C-11) 26건 → 0건. 03에 24건, 랩 README에 2건.
- 번역투(A-2/A-7) `~를 통해`·`~을 갖는다`와 형식명사 결말(I-2) 정리.
- 긴 접속절을 문장으로 분리 (01 9건).
- 복수 `-들` 남용 2건.

수치·고유명사·명령어·인용 블록은 편집 전후 토큰 전수 일치를 확인했고
문체 축(가이드 4종 한다체 / 랩 README 합니다체)도 유지했다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
저장소의 볼드 리드-인 376건 중 335건은 `**이름**: 설명.` 형태로 이름에서 끝난다.
나머지 41건은 문장 전체가 볼드 안에 들어가 있고, 그중 26건이 redis 묶음에 몰려 있었다.
저장소 다수 관행이 아니라 이 묶음의 습관이므로 규칙으로 세우고 정리한다.

- AGENTS.md에 4.3 신설 — 판별법은 한 목록 안의 균일성. 불릿이 전부 볼드면
  볼드가 아무것도 가리키지 않는다. 평문 사이의 한둘은 진짜 강조이므로 둔다.
  기존 4.3(셀의 끝)은 4.4로. 이 앵커로 들어오는 링크는 없음을 확인했다.
- CLAUDE.md 요약에 한 줄 추가
- 균일 문장볼드 20건 정리 — 훑을 대상이면 명사구 이름으로(허브 4·01 6·02 2),
  아니면 볼드만 제거(01 5·03 3). 금지 조항은 이름으로 줄이면 당위가 사라지므로
  볼드만 뺐다. 혼재 목록 6건은 진짜 강조라 그대로 둔다.

check_docs.py --strict: redis/ 문서 7개 · 오류 0건 · 경고 0건, AGENTS.md·CLAUDE.md 오류 0건 · 경고 0건
verify_gates.py: 5종 전부 exit 0. 01-differences는 직전 회차 P1 경고(z +2.02 미달)가
이번 편집으로 +1.91까지 내려가 해소됐다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@my-reviewer-agent my-reviewer-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 리뷰는 부분 리뷰입니다. 이번 커밋에서 확인한 파일은 redis/migration-lab/load_data.py, results/path-a-rdb.json, results/policy-matrix-ent.json, results/policy-matrix-oss.json 네 개뿐이며, PR의 나머지 변경 파일은 검토 범위에 포함되지 않았습니다. 따라서 전체 변경에 대한 완전한 검토를 주장하지 않습니다.

먼저 이전 리뷰에서 지적했던 load_data.py의 문제들이 실제로 반영된 점을 확인했습니다. TLS는 ssl_cert_reqs="required"로 바뀌었고, 비밀번호는 resolve_password()로 환경 변수 또는 프롬프트를 거치게 되어 커맨드라인 노출 경로가 사실상 사라졌으며, 자식 프로세스의 exitcode를 모아 하나라도 비정상이면 종료 코드 1로 실패를 알리도록 바뀌었습니다. --target-gb 환산에 큰 해시가 빠진다는 점도 모듈 docstring과 환산 구역 주석에 명시되어 오해 소지가 줄었습니다. share()로 나머지를 앞쪽 워커에 배분해 진행률이 100%에 닿게 한 처리도 적절합니다.

결과 JSON들은 실측 로그 성격이며 구조가 일관됩니다. policy-matrix-oss.json은 shards_note와 admin_command_note로 "샤드가 1개라 비클러스터 클라이언트의 단일 키 SET이 성공한다", "클러스터 클라이언트 행의 실패는 대체로 redis-py 사정이지 서버 미지원 근거가 아니다"라는 해석상의 함정을 스스로 밝혀 두어, 이 데이터를 인용할 때의 오독 위험을 잘 낮췄습니다. policy-matrix-ent.json과의 대비(허용 목록 밖 명령까지 성공)도 runs/ok_count/stable로 재현 가능하게 남아 근거로서 충분합니다. path-a-rdb.json도 import 실패를 테넌트 정책 이름 수준까지 특정해 두어 재현 가치가 있습니다. 이 세 파일에 대해서는 별도 지적 사항이 없습니다.

남은 지적은 load_data.py에 두 건이며 모두 차단 이슈는 아니라 COMMENT로 남깁니다. 요약하면 (1) TLS에서 인증서 체인만 검증하고 호스트네임 검증은 켜지 않은 점, (2) fork된 워커들이 부모의 random 상태를 그대로 물려받아 동일한 값 시퀀스를 만들어 내는 점입니다. 특히 (2)는 이 스크립트가 존재하는 이유(현실적인 RDB 압축률 확보)와 정면으로 충돌하므로 측정값을 문서에 인용하기 전에 확인해 두시는 편이 좋겠습니다.

PR_REVIEW_SCOPE_V1 {"included_files":["redis/migration-lab/load_data.py","redis/migration-lab/results/path-a-rdb.json","redis/migration-lab/results/policy-matrix-ent.json","redis/migration-lab/results/policy-matrix-oss.json"],"omitted_files":[".github/workflows/docs.yml","AGENTS.md","CLAUDE.md","README.md","aisearch/custom_vectorization/01_custom_embedding_guide.md","aisearch/custom_vectorization/ref_bge_m3_vs_qwen3_comparison.md","aks/argocd_image_updater.md","memory/README.md","redis/azure-cache-to-managed-redis-migration.md","redis/migration-guide/01-differences.md","redis/migration-guide/02-client-audit.md","redis/migration-guide/03-migration-paths.md","redis/migration-lab/README.md","redis/migration-lab/audit_commands.sh","redis/migration-lab/concurrent_writer.py","redis/migration-lab/import_rdb.py","redis/migration-lab/migrate_scan_copy.py","redis/migration-lab/policy_matrix_test.py","redis/migration-lab/results/amr-memory-sizing.json","redis/migration-lab/results/clustering-policy.json","redis/migration-lab/results/path-b-repeat-pass.json","redis/migration-lab/results/path-b-scan-copy.json","redis/migration-lab/results/path-c-tooling.json","redis/migration-lab/verify_migration.py","redis/nodejs/cluster_failover_recovery.md","scripts/check_docs.py","scripts/gen_index.py"],"original_changed_lines":6626,"original_diff_lines":6880,"original_diff_unavailable_reason":null}

port=args.port,
password=args.password,
ssl=True,
ssl_cert_reqs="required",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

심각도: 경고(보안). ssl_cert_reqs="required"로 인증서 체인 검증은 켜졌지만 ssl_check_hostname이 지정되지 않았습니다. redis-py의 StrictRedis/Connection 기본값은 ssl_check_hostname=False이므로, 신뢰 체인에 속한 아무 인증서(다른 호스트용 인증서 포함)라도 제시되면 연결이 성립합니다. 즉 검증을 켰다는 인상과 달리 중간자 공격 방어가 완성되지 않습니다. 근거는 이 connect()가 만드는 연결이 Azure Cache/AMR의 공개 TLS 엔드포인트(기본 6380)를 향한다는 점입니다.

구체적 수정: connect()의 인자에 ssl_check_hostname=True를 추가하십시오. policy-matrix-oss.json에 기록된 것처럼 클러스터 클라이언트에서 호스트네임 검증을 꺼야 하는 경우가 있다면, 그 예외를 이 로더에 암묵적으로 적용하지 말고 --insecure-skip-hostname 같은 명시적 플래그로 분리해 기본값은 True를 유지하는 편이 안전합니다.

written = 0
for i in range(count):
key = f"cache:string:{worker_id}:{i}"
value = make_value(random.randint(500, 1500))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

심각도: 경고(정확성/측정 신뢰도). 워커들은 multiprocessing.Process로 생성되며 Linux 기본 start method는 fork입니다. 부모가 import 시점에 초기화한 random 모듈의 내부 상태가 모든 자식에게 그대로 복제되므로, load_strings/load_hashes/load_lists/load_zsets의 각 워커는 make_value()가 만드는 값 시퀀스와 TTL 분기(random.random() < TTL_FRACTION), compressible/incompressible 선택까지 동일한 순서로 재생산합니다. 결과적으로 workers=8이면 같은 페이로드가 8중으로 적재됩니다.

이것이 문제인 이유는 모듈 docstring이 밝힌 이 스크립트의 첫 번째 목적, 즉 "RDB 압축률이 현실적이어야 export 시간과 blob 크기가 의미를 가진다"와 정면으로 충돌하기 때문입니다. 중복 페이로드는 RDB 압축률을 실제보다 좋게 만들어, path-a-rdb.json의 compression_ratio_vs_memory 0.47 같은 수치가 워커 수에 의존해 흔들리게 됩니다. os.urandom을 쓰는 incompressible_value만 프로세스별로 다르고 비중 30%에 그칩니다.

구체적 수정: 각 워커 함수의 시작 부분(connect 직전)에서 random.seed(os.getpid())처럼 프로세스별로 시드를 다시 잡거나, 더 결정적으로 하려면 random.seed((worker_id, "string"))처럼 워커 ID와 타입을 조합한 시드를 사용하십시오. load_big_hashes에도 동일하게 적용해야 합니다.

@hellices hellices closed this Sep 12, 2026
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