안녕하세요, 경북대학교 OpenStack Swift 팀 오지우, 이주영, 황지영입니다.
지난 4월 23일, OpenStack Swift PTG 세션에 정식 안건을 등록하여 현재 진행 중인 container-sync 성능 개선을 위한 아이디어를 제안하고, 업스트림 개발자분들과의 논의를 진행했습니다.
기존 동기화 구조의 문제점과, 이를 해결하기 위해 저희 팀이 제안한 병렬 처리 및 상태 공유 아키텍처의 상세 메커니즘, 성능 개선 지표, 그리고 PTG에서 논의된 기술적인 피드백들을 포럼에 공유하고자 합니다.
배경 및 문제 정의
현재 Swift의 container-sync 데몬은 두 가지 메타데이터를 기준으로 동작합니다.
sync_point1(sp-1): 데몬이 관찰한 가장 최신 Row 위치sync_point2(sp-2): 모든 복제본(Replica)에 업데이트가 안전하게 완료되었다고 간주되는 최신 Row 위치
이러한 구조는 데이터 정합성을 보장하지만, 대규모 클러스터 환경에서 두 가지의 치명적인 성능 병목을 유발합니다.
sp-2 < row <= sp-1구간은 동기화 누락을 방지하기 위한 재시도 대상(Old Row)으로 분류됩니다. 문제는 각 Replica의 데몬들이 서로의 작업 상태를 공유하지 않고 독립적으로 동작하기 때문에, 여러 데몬이 동일한 Old Row를 중복해서 스캔한다는 점입니다. 이로 인해 동일한 객체에 대해 불필요한HEAD요청이 중복 발생하여 전체 처리량을 저하시킵니다.sp-1이후 신규 Row를 동기화할 때, 데몬은 각 Row를 순차적으로 하나씩 처리합니다. 이로 인해 원격 클러스터로의 네트워크 I/O 대기 시간이 누적되어, 대규모 객체 처리 시 전체적인 동기화 지연이 발생합니다.
제안 아이디어: 분산 구조 기반의 병렬 처리 및 상태 공유
저희 팀은 Swift의 기존 분산 아키텍처 구조를 훼손하지 않으면서 상기 제시된 병목 현상을 해소하기 위해 다음과 같은 핵심 개선안을 토대로 아이디어를 제안했습니다.
가장 큰 문제였던 Old Row 구간의 중복 스캔 문제를 해결하기 위해 Hash 기반 할당 방식을 도입하였습니다. 각 데몬은 Hash(Object Name) % Total Nodes 연산을 통해 자신이 담당해야 할 특정 Row만 필터링합니다. 전체 구간을 모두가 읽는 것이 아니라 분할 처리하므로, 전체 HEAD 요청을 1/n 수준으로 줄일 수 있습니다.
순차 처리로 인한 네트워크 I/O 대기 시간을 감소시키기 위해 Batch 단위로 데이터를 가져오고,ContextPool을 도입하여 작업을 병렬화합니다. 신규 Row와 Old Row 모두에 적용되며, Pending Queue를 두어 동시 실행되는 Worker 수를 제어함으로써 처리량을 극대화하였습니다.
데몬 간의 독립성을 유지하면서도 소유권을 확인하기 위해 Memcached를 활용하여 재시도 진행 상황(Retry progress)을 노드 간에 공유합니다. 또한, 주기적으로 동기화가 완료된 위치를 메타데이터에 기록하여 재시작 시 복구 지점을 마련합니다.
단일 노드가 특정 Row를 전담함에 따라 발생할 수 있는 누락 위험을 방지하기 위해 두 가지 정책을 설계했습니다.
Takeover: 특정 노드의 진행 상태 갱신이 멈추면 다른 노드가 이를 감지하고 해당 구간의 작업을 인수합니다.Minimum Progress: 전체sp-2의 전진은 모든 노드 중 가장 느린 진행도를 기준으로 설정하여, 검증되지 않은 Row가 스킵되는 현상을 차단합니다.
성능 측정 결과
제안한 아키텍처를 적용한 프로토타입을 통해 10,000개의 객체(Object) 동기화를 기준으로 성능을 측정한 결과, 다음과 같은 속도 향상을 확인할 수 있었습니다.
sp1(신규 Row 구간): 기존 2분 54초 → 1분 17초 (약 56% 시간 단축)sp2(재시도 Row 구간): 기존 1분 55초 → 17초 (약 85% 시간 단축)
업스트림 개발자 피드백
아이디어 발표 후 업스트림 개발자들의 피드백 및 논의사항들은 다음과 같습니다.
- Memcached에 진행 상태를 기록할 때, 복제본 간 Container DB의
ROWID가 완벽히 일치하지 않을 수 있다는 우려가 제기되었습니다. - 단순
ROWID를 공유하는 방식은 복제본 간 서로 다른ROWID로 인해 정합성 오류를 유발할 수 있습니다. 이에 따라 객체명 자체를 식별자로 활용하거나 Hash Boundary를 기준으로 복구 지점을 판단하는 등, 추가적인 보완 설계가 필요하다는 의견이 오갔습니다.
- 서버 장애 등으로 Memcached 데이터가 소실될 경우, 시스템 안정성에 미치는 영향에 대한 질의와 검토가 있었습니다.
- 데이터가 유실되면 데몬은 메타데이터에 저장된
sp-2위치로Fallback하는 구조를 설명했습니다. 일시적으로 과거의 전체 스캔(중복 스캔) 방식으로 회귀하여 성능 저하가 발생할 수 있으나 데이터 누락은 발생하지 않으므로, 이러한 트레이드오프가 시스템 무결성 관점에서 수용 가능한지에 대한 논의가 진행되었습니다.
- 특정 노드 장애 시 남은 노드들이 이를 어떻게 감지하고 안정적으로
Takeover할 것인지, 구체적인 타이밍과 로직에 대한 논의가 있었습니다. - 업스트림 측에서 우선순위를 기반으로 시간차를 두고 인수를 시도하는
Staggered Failover방식을 아이디어로 제시해 주셨습니다. 일시적인 네트워크 지연과 실제 장애를 구분하는 데 도움이 될 수 있어, 향후 복구 로직 설계 시 유효한 대안 중 하나로 참고하기로 했습니다.
설계 고도화 및 마무리
업스트림 개발자들은 container-sync가 오랫동안 뚜렷한 개선이 없었던 점을 강조하시면서 이번 설계 방향성을 긍정적으로 평가해 주셨습니다.
PTG 이후 현재 Rotation 및 Slot 기반의 상태 공유 방식으로 아키텍처를 전면 고도화하였습니다.
- Memcached에
Rotation값과Slot별 진행 상태(Point)를 저장하고, 특정 노드에 장애가 발생하여 담당 Slot 처리가 정체될 경우 다음 동기화 실행 시Rotation값을 1 증가시켜Slot과 노드 간의 매핑을 한 칸씩 순환시킵니다. 이를 통해 다른 정상 노드가 자연스럽게 미완료Slot을 인수하여 작업을 완수할 수 있도록 안정성을 강화하였습니다.
마지막으로, 이번 OpenStack Swift PTG는 학생 개발자로서 글로벌 오픈소스 커뮤니티를 직접 경험할 수 있었던 매우 뜻깊은 시간이었습니다. 오랫동안 팀원들과 고민해온 아이디어를 업스트림 개발자들에게 직접 제안하고 실질적인 피드백을 받는 과정에서 많은 것을 배울 수 있었습니다. 감사합니다.