Discord가 네트웍 디스크의 지연시간을 최소화한 방법
(discord.com)- 초당 200만개의 메시지를 처리하는 NoSQL DB 클러스터(ScyllaDB)를 운용 중
- DB성능에 가장 큰 영향을 미치는 것은 피지컬 디스크 하드웨어의 레이턴시
→ 쿼리량이 낮은 수준에서는 상관없지만, 특정 시점을 초과하면 1~2ms가 걸리는 읽기 시간 만으로도 디스크에서 읽는 대기열이 발생하며 쿼리 자체에 대해 시간 초과가 발생 - 디스크 레이턴시는 보통 마이크로세컨드 단위인데, 왜 디스크 오퍼레이션에 1~2ms가 걸릴까 ?
- 디스코드는 대부분의 하드웨어를 Google Cloud에서 운용
- NVMe 기반의 로컬 SSD를 지원하지만 자체적으로 테스트해보니 안정성 문제가 있어서 중요한 데이터 저장소로 사용하기엔 맘이 편하지 않았음
- Persistent Disk는 서버에 실시간으로 연결/분리 가능하며, 다운타임 없이 리사이즈 가능, 언제나 스냅샷 생성가능하고, 기본으로 복제되게 설계됨
→ 문제는 서버에 직접 붙어있지 않고 네트웍으로 연결 된다는 것
- 로컬 네트웍 커넥션 레이턴시가 아무리 낮아도, PCI/SATA 보다 낮지는 않음
→ 네트워크는 1~2ms, 직접연결된 디스크는 0.5ms - 로컬 SSD는 HDD처럼 하드웨어 문제가 생기면 그 디스크의 데이터를 잃어버리게 되며, 호스트 자체가 문제가 생기면 스냅샷도 불가능 해서 아예 데이터를 잃어 버리는 상황이 발생
→ 그래서 디스코드는 Local SSD 를 이용하지 않고, Persistent Disk 를 사용
문제 분석
- 로컬 SSD와 Persistent Disk 의 장점만 모은 저장 장치가 있다면 최고겠지만 그런 것은 없음. 장점중 일부만 가져온다면?
- 디스코드는 쓰기 지연시간은 문제가 아님. 성능에 영향을 미치는 것은 "읽기 지연시간"
- "다운타임 없는 디스크 리사이징"은 필수 기능은 아님. 사이즈는 미리 예측 가능
- 최종 요구 사항은
- GCP 에 그대로 있으면서
- 데이터 백업을 위해서 Point-in-Time 스냅샷 사용
- 읽기 지연시간 최소화를 최우선 순위로
- 기존 데이터베이스 업타임 보장을 희생하지 않을 것
- 읽기는 GCP의 Local SSD를 활용하고, 쓰기는 Persistent Disk에 하면 좋을 것 같음
→ 소프트웨어 수준에서 이런 Super-disk를 만들 수 있을까?
Super-Disk 만들기
- 요구사항은 기본적으로 Write-Through 캐쉬였음. GCP의 로컬 SSD를 캐쉬로 사용하고, PD를 저장 레이어로 사용
- DB서버로 Ubunut를 사용하고 있어서, 리눅스 커널단에서 디스크 레벨의 캐쉬 적용 가능(dm-cache, lvm-cache, bcache 같은 모듈)
- 하지만, 실험해보니 캐쉬디스크에 배드섹터 발생시 전체 읽기 작업이 실패함
- 배드섹터가 발생하면 스토리지 레이어에서 읽어다 엎어써야 하는데, 평가한 디스크 캐슁 솔루션들은 이런 기능이 없었음
- 배드섹터 발생시 데이터베이스가 데이터 안정성 문제로 셧다운 되어버림
- 추가 요구사항으로 "로컬 SSD에 배드섹터가 발생해도 살아남아야 함"이 추가됨
- 그래서 리눅스 커널의 "md"를 조사
- md는 소프트웨어 RAID를 생성할수 있도록 지원
- SSD와 PD를 미러링 하는 것으로는 문제가 해결 되지 않음. 절반이상의 읽기는 PD에서 될 것이기 때문에
- md에는 전통적인 RAID에는 없는 "write-mostly" 가 있음
- 특정 디스크를 write-mostly로 지정하면 일반 읽기에서는 제외되며, 다른 옵션이 없을 때만 읽기가 실행됨. "느리게 연결된 기기에 유용"
- 즉, SSD와 PD를 RAID1으로 묶고, PD를 write-mostly로 세팅하면 요구사항을 맞출 수 있음
- 마지막 남은 문제는 GCP의 Local SSD는 크기가 딱 375GB라는 것
- 디스코드는 특정 어플리케이션에 대해서는 DB 인스턴스당 1TB 이상이 필요하기도 함
- 그래서 여러개의 SSD를 RAID0로 묶기로
- 최종 모습은
- RAID0 로 묶인 로컬 SSD 4개를 md0
- md0 와 Persistent Disk를 RAID1으로 묶은 md1 을 구성
DB 성능
- 딱 예상한 결과가 나왔음
- 피크시에도 디스크 오퍼레이션들이 큐에 쌓이지 않으며, 쿼리 레이턴시가 변하지 않음
- 성능 향상이 일어나서 각 서버당 처리 쿼리량이 더 늘어남
- RAID 사용해본 사람들은 이게 "그냥 동작할까?" 라는 의구심이 들겠지만, 실제로는 다양한 일이 있었고, 나머지는 따로 상세히 소개할 예정
이전에는 golang의 퍼포먼스에 만족을 못해서 rust로 서버도 짜는 것 보면 discord 회사의 geek함도 대단한 것 같아요.
HN에서는 그거 그냥 GCP 문제 아냐? 라는 얘기도 있기는 합니다만..
https://news.ycombinator.com/item?id=32474093
이런 시도도 가능하구나 정도로 알아두면 좋을 것 같아요.