DNS 설정 하나를 바꿨는데 수천만 개의 레코드를 다시 처리해야 한다면 어떨까. Cloudflare의 DNS 변경 처리 시스템이 안고 있던 문제다. 변경된 데이터만 저장하는 구조는 이미 있었지만, 무엇이 달라졌는지 알아내는 작업은 여전히 도메인 전체를 훑을 수 있었다.
Cloudflare는 11월 2일 기술 블로그에서 필요한 부분만 다시 만드는 설계와 통합 스케줄러를 소개했다. 회사 발표에 따르면 배포 전체의 최대 메모리 사용량은 약 85% 줄었다. 이후 안전을 위해 두었던 대기 시간도 조정하면서 DNS 변경부터 빌드 완료까지의 지연 중앙값을 절반 수준으로 낮췄다. 출발점은 변경마다 어디까지 다시 만들어야 하는지 판단하는 일이었다.
바뀐 것만 저장해도, 전부 읽는 비용은 남는다
DNS 영역은 한 도메인의 레코드와 설정을 묶은 단위다. Cloudflare의 Zone Builder는 데이터베이스에서 현재 상태를 읽고, DNS 서버가 사용할 형식으로 변환한 뒤 분산 키값 저장소인 Quicksilver에 변경분을 쓴다. 여기까지의 작업을 빌드라고 부른다.
기존 전체 빌드는 영역 안의 모든 레코드를 읽고 변환한 다음, 이미 저장된 키와 비교했다. 이 과정에서 영역 전체를 메모리에 유지했다. 마지막에 저장하는 데이터가 적어도, 그 차이를 알아내는 데는 전체 처리 비용을 치렀다. 레코드가 많은 영역일수록 작은 설정 변경도 큰 작업이 되는 이유다.
Universal SSL이 좋은 예다. Universal SSL을 켜거나 끄면 메타데이터와 도메인 최상위 이름의 CAA 레코드가 달라질 수 있다. 최상위 이름은 example.com처럼 하위 이름이 붙지 않은 도메인을 뜻한다. CAA 레코드는 인증 기관에 해당 도메인의 인증서를 발급해도 되는지 알린다. 이 변경이 영향을 주는 범위를 안다면 다른 이름의 레코드까지 모두 처리할 필요가 없다.
새 플래너는 이벤트에 담긴 변경 필드와 설정을 DNS 출력에 연결한다. 어떤 이름의 레코드를 다시 만들어야 하는지, 공유 메타데이터도 갱신해야 하는지 판단해 작업 범위를 고른다. 기준은 엄격하다. 부분 빌드가 쓰는 각 키는 전체 빌드가 만들었을 키와 바이트 단위까지 같아야 한다. 무엇이 달라졌는지 알 수 없는 이벤트는 전체 빌드 대상으로 남겼다.
이벤트를 보고 계획하고, 쓰기 직전에 다시 확인한다
영향 범위를 안다고 해서 변경된 설정 하나만 고치면 끝나는 것은 아니다. Cloudflare에서는 고객의 도메인을 등록하거나 해제하는 과정 등에 같은 도메인 이름을 가진 내부 영역이 여럿 존재할 수 있다. 레코드 키는 영역별로 나뉘지만, DNS 서버가 어느 내부 영역을 사용해 응답할지 알려 주는 메타데이터 키는 공유한다.
이벤트가 발생한 뒤 빌드가 실행되기까지 현재 상태도 달라질 수 있다. 따라서 이벤트로 만든 작업 목록을 그대로 실행하지 않는다. 빌더는 실행 직전에 최신 설정과 게시 여부, 공유 메타데이터에 포함할 내부 영역 목록을 다시 읽는다. 그 결과에 따라 전체 빌드로 확대하거나, 게시를 중단할 영역을 제거하거나, 더 이상 필요 없는 작업을 건너뛸 수 있다.
메타데이터를 써야 한다면 예전 사본의 일부를 고치는 대신 최신 상태로 키 전체를 다시 만든다. 참조할 레코드를 먼저 쓰고 그 레코드를 가리키는 메타데이터를 나중에 쓰는 순서도 지킨다. 작업을 줄이면서도 공유 데이터와 실행 시점의 상태를 놓치지 않기 위한 장치다.
전체 영역이 메모리에 들어가지 않는 초대형 영역에는 예외가 있다. 일정 레코드 수를 넘으면 전체 빌드로 확대하지 않고 최상위 이름, 메타데이터, 대기 이벤트가 지목한 이름을 처리한다. Cloudflare는 끝낼 수 없는 전체 빌드를 반복하는 대신 이 절충을 택했으며, 남는 드문 사례에는 개별 이름을 수동 재빌드한다고 설명했다.
필요한 작업은 합치되, 모으려고 기다리지는 않는다
플래너가 무엇을 만들지 고른다면, 스케줄러는 언제 실행할지 정한다. 새 통합 스케줄러는 같은 영역에 이미 대기 중인 작업이 있을 때 필요한 범위를 합친다. 레코드 변경과 영역 전체 작업을 서로 다른 스케줄러가 맡던 구조에서, 두 종류의 작업을 함께 다룰 수 있게 됐다.
같은 영역의 빌드가 시작되기 전에 도착한 변경을 합친다. 별도 배치를 만들기 위해 기다리지는 않는다.
일반 레코드 변경
- A 레코드 변경www.example.com의 주소 레코드가 바뀐다.
- 플래너가 범위 선택www.example.com의 레코드를 다시 만들어야 한다.
와일드카드 변경
- 와일드카드 변경*.example.com의 레코드가 바뀐다.
- 플래너가 범위 선택*.example.com의 레코드와 공유 메타데이터를 갱신해야 한다.
- 통합 스케줄러가 병합두 이름의 레코드와 공유 메타데이터 갱신을 하나의 빌드로 처리한다.
병합할 때는 필요한 작업을 추가하는 방향으로만 움직인다. 어느 이벤트든 전체 영역 처리를 요구하면 합쳐진 작업도 전체 빌드가 된다. 개별 변경을 합친다는 이유로 더 넓은 갱신 요구를 버리지 않는 것이다.
Cloudflare는 같은 데이터베이스 상태에서 전체 빌드와 부분 빌드의 출력 키를 바이트 단위로 비교하는 테스트를 추가했다고 설명했다. 고객 트래픽을 처리하지 않는 별도 미러 환경에서도 운영 환경과 출력을 비교했다.
메모리 여유가 생긴 뒤 줄일 수 있었던 대기
Cloudflare는 24시간 측정에서 전체 빌드 횟수가 약 40% 감소했다고 보고했다. 이는 모든 빌드의 수나 총작업량이 40% 줄었다는 뜻은 아니다. 이전에 전체 빌드가 필요했던 일부 변경을 부분 빌드로 옮긴 결과다.
큰 영역 전체를 메모리에 올리는 일이 줄면서 메모리 사용량도 낮아졌다. 회사 측정에 따르면 배포 전체에서 활발히 사용되는 메모리의 최대치는 79 GiB에서 12 GiB로 약 85% 줄었다.
지연 개선에는 다음 단계가 있었다. 처음 새 스케줄러를 기존 시스템과 같은 이벤트로 비교했을 때는 대부분의 빌드가 이미 작아서 일반적인 지연이 거의 달라지지 않았다. 이후 Cloudflare는 확보한 여유를 바탕으로 대부분의 요금제에서 영역별 재빌드 간격을 줄였다. 이 간격은 변경이 잦은 영역 하나가 처리 용량을 독점하지 못하도록 둔 대기 시간이다.
데이터베이스 복제를 기다리는 시간도 줄였다. 변경 이벤트 전달과 데이터베이스 복제는 따로 진행되므로, 이벤트가 먼저 도착하면 읽기용 복제본에는 아직 변경이 보이지 않을 수 있다. 기존에는 이벤트를 잠시 보류해 이 시간차를 흡수했다. 뒤처진 복제본에서의 읽기를 거부하는 검사가 마련되자 보류 시간을 더 짧게 잡을 수 있었다.
두 대기 시간을 조정한 뒤, Cloudflare가 보고한 빌드 완료 지연 중앙값은 약 7.7초에서 약 3.3초로 낮아졌다. 당시 히스토그램으로 추정한 중앙값이며, 회사는 버킷 추정에 의존하지 않는 평균값에서도 지연이 절반 수준으로 줄었음을 확인했다고 설명했다. 이 지연은 DNS 레코드나 설정 변경부터 Quicksilver에 쓰기를 마칠 때까지다. 그 뒤 전 세계 네트워크로 전달되는 시간은 포함하지 않는다.
전체 빌드는 여전히 남아 있다. 영역을 처음 게시할 때나 변경이 어느 레코드에든 영향을 줄 수 있을 때는 전체 처리가 필요하다. 이번 설계가 줄인 것은 영향 범위를 확인할 수 있는데도 큰 영역 전체를 다시 만들던 작업이다.