이 블로그 게시물은 카우치베이스의 리밸런싱 기술에 대한 핵심적인 내용을 다루는 두 개의 블로그 게시물 중 첫 번째 게시물입니다. 첫 번째 블로그에서는 리밸런싱 기능 자체와 second 를 모니터링하고 작업하는 방법에 대해 설명합니다.
Couchbase를 사용한 리밸런싱을 위한 실무자 가이드
2010년 중반, 노스케일이라는 작은 회사에서 다음과 같은 제품을 출시했습니다. Membase. 많은 혁신적인 기능 중에는 '노드'(소프트웨어 인스턴스) 클러스터를 동적으로 성장(또는 축소)시킬 수 있는 기능이 있습니다. 이 기능은 설계상 온라인 작업이므로 해당 클러스터에 저장된 데이터의 가용성에 영향을 미치지 않으며 해당 데이터에 액세스하는 애플리케이션의 성능에도 큰 영향을 미치지 않습니다.
지금으로 빨리 감기(2011년 말... 그래요, 빨리 감기였어요). 이후 이 회사는 두 차례에 걸쳐 회사명을 Couchbase로 변경했으며, 제품이 '실제 세계에서' 어떻게 작동하는지에 대해 많은 것을 배웠습니다. 이 제품은 완전히 다른 환경(자체 데이터 센터와 아마존 클라우드)에서 완전히 다른 애플리케이션(로깅 대 소셜 게임 대 광고 타겟팅)에 배포되고 있습니다. 동일한 '리밸런싱' 기술이 Membase뿐만 아니라 곧 출시될 Couchbase Server 2.0에도 적용될 예정입니다. 이 프로세스를 둘러싼 많은 질문에 답하고 알려진 몇 가지 문제를 해결하기 위해 지금이 다소 긴 이 글을 작성하기에 좋은 시기라고 생각했습니다. 현재 릴리스는 1.7.1.1이며, 2010년 여름의 기억에 남는 1.6.0 베타 릴리스 이후 많은 발전이 있었습니다.
소개는 그만하고, 토끼굴을 얼마나 더 내려갈 수 있는지 알아봅시다...
리밸런싱이란 무엇인가요?
높은 수준에서 시작하겠습니다. 리밸런싱은 Membase/Couchbase Server 클러스터 내에서 추가되거나 제거된 노드에 데이터를 재분배하는 프로세스입니다.
꽤 간단해 보이시죠? 일반인이 보기에는 그렇습니다. 그리고 저희는 그렇게 하려고 했습니다. 대부분의 다른 기술과 마찬가지로, 이 단순함을 달성하기 위해서는 상당한 복잡성이 필요합니다(자세한 내용은 https://plus.google.com/112218872649456413744/posts/dfydM2Cnepe 참조).
이제 한 단계 내려갑니다.
Membase/Couchbase Server의 작동 방식을 이해하기 위해 기본 개념은 vbuckets(http://dustin.github.com/2010/06/29/memcached-vbuckets.html)입니다. v버킷은 전체 데이터 집합의 논리적 '조각'입니다(저희의 경우, 버킷... 이름 지어서 죄송합니다. 아직 제품 마케팅 팀이 없었을 때였으니까요). 이러한 v버킷에는 정해진 수가 있으며 키는 이 정적 목록에 해시(CRC32 사용)됩니다. 이렇게 하면 주어진 키가 항상 동일한 v버킷에 해시되도록 보장할 수 있습니다. 그런 다음 각 v버킷은 클러스터 내의 지정된 서버에 할당됩니다. v버킷이 256개이고 서버가 하나라면 모두 같은 서버에 있을 것입니다. 서버가 두 대라면 두 대에 각각 128개의 버킷이 똑같이 공유되고, 네 대의 서버에는 각각 64개의 버킷이 할당되는 식입니다.
'v버킷 맵'은 단순히 이 v버킷 목록과 이를 소유한 서버를 열거한 것입니다.
지금은 일부러 복제를 무시하고 있습니다...나중에 다시 설명하겠지만 지금으로서는 중요하지 않습니다.
리밸런싱은 간단히 말해서 (또 그 단어가 나왔네요...) 특정 서버에서 다른 서버로 특정 수의 v버킷을 이동하는 프로세스입니다. 최종 목표는 클러스터 내의 각 서버가 동일한 수의 v버킷을 보유하게 되는 것입니다. 이렇게 하면 데이터가 클러스터 전체에 고르게 분산되므로 해당 데이터에 대한 애플리케이션 액세스도 클러스터 내의 모든 노드에서 균등하게 로드 밸런싱됩니다.
지금까지 함께하셨나요? (그렇지 않은 경우 이메일 perry@couchbase.com).
그리고 또 다른 차원.
리밸런싱이 시작되면 '오케스트레이터'라는 프로세스가 현재 vbucket 맵을 살펴보고 노드 추가/제거와 결합하여 새 vbucket 맵이 어떤 모습이어야 하는지 계산합니다. 그런 다음 오케스트레이터는 한 노드에서 다른 노드로 vbucket을 이동하기 시작합니다. 오케스트레이터는 두 노드 사이에서 단순히 프로세스를 '시작'할 뿐 실제로 데이터 자체를 전송하지는 않으며, 병목 현상이나 장애 지점을 피하기 위해 의도적으로 소스 및 대상 노드가 조율하도록 남겨둔다는 점에 유의하는 것이 중요합니다. 오케스트레이터의 입장에서는 클러스터에서 노드가 추가되는지 제거되는지는 중요하지 않습니다. 실제로 동일한 리밸런싱에서 여러 노드를 클러스터에서 추가 및/또는 제거할 수 있습니다. 단지 새로운 vbucket 맵이 계산되고 그 맵을 현실로 만들기 위해 vbucket 이동이 시작될 뿐입니다.
각 v버킷은 개별적으로 다른 v버킷과 독립적으로 이동됩니다(여러 개를 병렬로 이동할 수도 있고 이동할 수도 있지만, 중요한 것은 고유한 v버킷 간에 관계가 없다는 것입니다). 대상 노드는 소스 노드의 vbucket에 대한 TAP(http://www.couchbase.org/wiki/display/membase/TAP+Protocol) 연결을 여는 'ebucketmigrator'라는 프로세스를 시작합니다. 이 연결에는 특정 플래그가 있어 a) 그 안에 포함된 모든 데이터를 원하고 b) 모든 작업이 완료되면 해당 v버킷을 '인수'할 계획이라는 신호를 보냅니다.
이 마지막 부분은 모든 데이터가 전송되는 즉시 소스 노드에 전환 프로세스를 시작하도록 지시합니다. (역사 수업: ebucketmigrator는 예전에는 vbucketmigrator라고 불렸지만 Erlang VM으로 옮겼기 때문에 'e'가 붙었습니다.)
각 vbucket이 이동되는 동안 클라이언트 액세스(읽기 및 쓰기)는 여전히 원래 위치로 이동합니다. 데이터가 모두 복사되면 원래 위치에서 '나는 더 이상 이 vbucket의 마스터가 아닙니다'라고 말하고 새로 만든 vbucket에 '당신이 마스터입니다'라는 '토큰'을 전송하는 원자적 전환이 이루어집니다. 원래 vbucket은 활성에서 비활성 상태로 전환되고, 새 vbucket은 보류에서 활성으로 전환됩니다. 스마트 클라이언트와 Moxi는 이러한 일이 발생했음을 알 수 있도록 새 vbucket 맵으로 업데이트되며, 이후의 데이터 요청은 새 위치로 전달됩니다. (아래 몇 개의 섹션을 참조하여 Moxi 및 스마트 클라이언트 동작에 대해 더 자세히 알아보세요).
그리고 이제 마지막 단계
적어도 여기까지는요. 면책 조항: 이 내용은 상당히 기술적인 내용이 될 것입니다. 시간이 부족하다면 섹션을 건너뛰세요.
위에서 언급했듯이, 오케스트레이터는 대상 노드의 ebucketmigrator 프로세스에 TAP을 통해 소스 노드에서 vbucket을 "끌어오도록" 지시합니다.
소스 노드에서 TAP 연결
소스 노드에 대한 TAP 연결이 시작되면 '커서'가 특정 vbucket 내에서 해시 테이블을 탐색하기 시작합니다. 동시에 '백필러' 프로세스가 시작되어 디스크에서 데이터를 대량으로 로드할지 여부를 결정합니다. 아시다시피, Membase/Couchbase는 사용 가능한 RAM보다 더 많은 데이터를 시스템에 저장하는 것을 원활하게 지원합니다. 이 경우 다른 노드로 전송하기 위해 디스크에서 읽어야 하는 데이터의 양이 상당할 수 있습니다. 백필러 프로세스는 '상주 항목 비율'(RAM에 캐시된 데이터의 양과 그렇지 않은 데이터의 양)을 살펴봅니다. 이 비율이 90%보다 낮으면 디스크에서 임시 RAM 버퍼로 전체 vbucket을 일괄 로드합니다(노드의 RAM 용량을 초과하지 않도록 하기 위해 내장된 캡과 백오프가 있습니다). 상주 항목 비율이 90%보다 높으면 이 프로세스가 발생하지 않습니다.
커서가 해시 테이블을 지나가면서 TAP 연결을 통해 키와 문서를 전송하기 시작합니다. 이미 RAM에 캐시된 것은 매우 빠르게 전송되고, 그 외의 것은 임시 버퍼 공간(사용 가능한 경우)에서 가져오거나 디스크에서 일회성 방식으로 읽습니다.
이 과정에서 이미 전송된 데이터에 대한 변경 사항이 발생하는 대로 TAP 스트림을 통해 전송됩니다(기술적으로는 대기 중이지만 어쨌든 엄청나게 빠르게 진행됩니다). 아직 전송되지 않은 데이터에 대한 변경 사항은 소스 v버킷에만 적용되며 해당 특정 문서가 전송될 때 수신됩니다.
모든 데이터가 복사되고 동기화되면 전환이 이루어집니다. 기술적으로는 변경 사항을 너무 빨리 vbucket으로 전송하여 절대 따라잡을 수 없을 정도로 빠르게 전환할 수 있을 것입니다. 하지만 실제로는 이런 일은 결코 일어나지 않습니다. 클라이언트 측에서 요청 사이에 약간의 지연만 있어도 마지막 비트를 넘길 수 있으며, 노드 간 통신이 클라이언트-서버 통신보다 현저히 느려지는 경우는 극히 드뭅니다.
대상 노드에서 TAP 연결
일반적으로 TAP 연결의 수신 쪽은 해당 노드의 특정 v버킷에 데이터를 넣는 일반 클라이언트와 크게 다르지 않게 취급됩니다. 몇 가지 예외가 있습니다:
- 대상 측의 vbucket은 "보류 중" 상태입니다. 즉, 그 안에 포함된 데이터는 vbucket으로 트래픽을 보내는 TAP 스트림 이외의 다른 어떤 것도 액세스할 수 없습니다.
- 데이터가 복제되지 않습니다. 기존에는 데이터를 v버킷에 넣으면 해당 v버킷의 복제본에 데이터가 복제되었습니다. 보류 중인 vbucket에서는 이런 일이 발생하지 않습니다.
- TAP 백오프(이것은 중요합니다): 빠른 소스 노드가 느린 대상 노드를 압도하는 것을 방지하기 위해 TAP 프로토콜에는 "백오프"라는 특수한 부분이 있습니다. 이를 통해 대상 노드는 발신자에게 "STOP! WAIT! 시간이 더 필요합니다..."라는 메시지를 보낼 수 있습니다. 발신자가 이 메시지를 받으면 잠시 후 백오프하고 요청을 다시 시도합니다. 이러한 백오프는 현재 대상의 디스크 쓰기 대기열이 1m 항목에 도달하면 시작됩니다. 이는 해당 노드의 모든 v버킷에서 측정되며 애플리케이션 트래픽과 재밸런싱 트래픽의 조합일 수 있습니다. 이를 모니터링하는 방법에 대해서는 아래에서 자세히 설명합니다.
축하합니다, 이제 레벨 4 리밸런서입니다! 어지러운 여정이었다는 거 알아요, 함께 해주셔서 감사합니다.
복제 및 리밸런싱
여기서 할 말이 많지는 않지만 완전히 빼고 싶지는 않았습니다. 각 v버킷은 "복제" v버킷에 1, 2 또는 3번 복제됩니다. 리밸런싱 중에 이러한 복제 v버킷은 균형 잡힌 클러스터를 보장하기 위해 이동되며, 아직 존재하지 않는 경우 만들어집니다. 예를 들어, 단일 노드에 복제본이 없는 경우입니다. 두 번째 노드를 추가하면 해당 복제본이 생성됩니다. 복제본 수가 1보다 큰 경우 노드를 더 추가하면 더 많은 복제본이 생성됩니다. 여러 복제본이 관련되어 있으면 이동되는 데이터의 양이 증가하므로 이 점에 유의해야 합니다.
리밸런싱 중에 활성 vbucket과 복제본을 모두 옮기지 않아도 되는 몇 가지 특별한 트릭이 있습니다. 알고리즘에 따라 소프트웨어는 활성 vbucket과 복제본을 "제자리에서" 전환한 다음 복제본만 이동할 수 있습니다.
마지막으로(적어도 이 주제에 대해서는), 리밸런싱 프로세스는 리플리카가 활성 v버킷을 충분히 "따라잡을" 때까지 완료되지 않으므로 리밸런싱에 걸리는 시간도 늘어날 수 있습니다. 이는 활성 v버킷에만 관심을 가졌던 원래 구현에서 발전한 것입니다. 이로 인해 두 가지 문제가 발생했습니다. 하나는 클러스터가 실제로 안전해지기 전에 리밸런싱이 "완료"되었다는 것입니다. 둘째, 리밸런싱이 완료되면 복제본을 다시 인스턴스화하기 위해 전체 클러스터에 엄청난 부하가 걸렸습니다. 현재 버전(1.7.1)에서는 이 단락의 앞부분에서 설명한 것과 일치하도록 이 동작이 변경되었습니다.
리밸런싱 중 Moxi 및 스마트 클라이언트
내부 작동 방식에 대한 자세한 설명은 여기서는 다루지 않지만, 모든 스마트 클라이언트와 Moxi에 대한 기본 아이디어를 이해하고 싶을 것입니다.
클라이언트 또는 Moxi가 시작되면 HTTP를 사용하여 포트 8091을 통해 Membase 노드의 URL에 연결합니다. 필요한 경우 인증하고(기본 버킷에는 필요하지 않음) vbucket 맵을 받습니다. 이를 '스트리밍 연결' 또는 '혜성 스트림'이라고 하며 HTTP 연결은 계속 열려 있습니다(TCP 연결과 마찬가지로). 모든 것이 안정되면 통신이 종료됩니다.
클라이언트가 종료하거나 다시 시작하면 해당 프로세스를 다시 거치게 됩니다. 연결이 끊어지면 재연결을 시도합니다. 처음 대화하던 노드가 응답하지 않으면 목록에 있는 다음 노드로 이동합니다(목록을 제공했다고 가정할 때...여기서는 모범 사례). 응답할 노드가 나타날 때까지 라운드 로빈을 돌며 연결 상태를 유지합니다. 목록에 있는 모든 노드에 연결하지 않고 한 번에 하나씩만 연결한다는 점에 유의하세요.
이제 리밸런싱 중에는 몇 가지 미묘한 차이가 있습니다. 설계상, 각 vbucket 이동은 이 연결을 통해 클라이언트에 다시 전달됩니다. 하지만 실제로는 그렇지 않습니다. 이전 버전에서는 리밸런싱이 끝날 때까지 기다렸다가 새 목록을 푸시했습니다. 이제 업데이트된 목록은 리밸런싱을 수행하기 전에 푸시되며("빨리 감기 맵"이라고 함), 프로세스가 진행되는 동안 '알아내는 것'은 클라이언트의 몫입니다(자세한 내용은 나중에 설명).
클라이언트(또는 Moxi)가 클러스터에 요청을 보낼 때, 키를 가져와서 v버킷 목록과 비교하여 해시합니다. 그런 다음 맵을 살펴보고 해당 v버킷에 대해 활성 상태인 서버를 확인합니다. 맵이 정확하면 서버는 요청을 수락합니다(모든 작업에는 키와 vbucket ID가 있습니다). 클라이언트/Moxi가 서버로 보내는 vbucket ID가 해당 서버에서 활성화되어 있지 않은 경우, '내 vbucket이 아닙니다'라는 오류와 함께 응답합니다. 리밸런싱 중에 클라이언트/Moxi가 제때 업데이트되지 않았거나, 일부 요청이 '비행 중'이거나, 메모를 놓친 경우, 이전 노드는 이 시점 이후의 모든 요청에 대해 '내 vbucket이 아님' 오류로 응답합니다.
엄밀히 말하면 오류이지만, 이 메시지는 클라이언트/Moxi에게 해당 요청에 적합한 위치를 찾아 다시 전송하라는 신호일 뿐입니다. 이는 클라이언트가 두 곳 이상의 위치에서 동일한 문서에 액세스하는 일이 없도록 하기 위한 것입니다. 가장 낮은 수준에서는 요청이 이 서버에 적합하지 않다는 오류일 뿐입니다. 스택 위쪽에서는 클라이언트/Moxi가 올바른 위치를 찾아야 한다는 의미라는 것을 이해해야 합니다.
이 부분에서 구현마다 조금씩 차이가 나기 시작합니다. 예를 들어, Moxi는 원래 클러스터의 모든 서버를 무차별 대입으로 시도했습니다. 응답하는 서버가 없으면 오류를 클라이언트(레거시 멤캐시드)로 보냈고, 클라이언트는 어떻게 해야 할지 몰랐습니다. 이제 우리는 이 문제를 훨씬 더 잘 처리하고 있으며, 현재 구현은 리밸런싱을 시작할 때 얻은 새로운 '빨리 감기' 맵을 Moxi가 참조하도록 하는 것입니다. 기본 아이디어는 스마트 클라이언트가 동일한 로직을 따르되 구현 방식은 조금씩 다르게 하는 것입니다.
일반적으로 클라이언트/Moxi를 위한 이 '관리 채널'을 통한 트래픽은 거의 없습니다. 클라이언트가 연결하거나 리밸런싱할 때만 발생하며, 그마저도 매우 적은 양입니다.
최근에 다음과 같은 질문이 있어서 여기서 답변해드리려고 합니다:
"안녕하세요, 방화벽 규칙 집합이나 연결성 때문에 네트워크가 불안정한 사용자가 있는데, vbucket 맵이 업데이트되었지만 클라이언트 시스템을 사용할 수 없을 때 재조정 중에 어떤 일이 발생하는지 알고 싶다고 하네요. 클러스터가 8091 및 HTTP를 통해 클라이언트에 이와 같은 것을 푸시하는 것을 보지만 지속적인 연결을 유지하나요, 아니면 클러스터가 알아야 할 업데이트가 있을 때만 클라이언트에 연결하나요?
리밸런싱 후 고객이 오래된 맵을 갖게 될까 봐 걱정하고 있으며, 어떻게 처리되는지 더 자세히 알고 싶어 합니다."
그리고 제 답변은 이 두 가지 사항을 자세히 설명했습니다:
- 연결은 항상 열려 있는 상태로 유지됩니다. 클라이언트는 항상 클러스터에 연결하고 그 반대가 아니라 클러스터가 기존 연결에 새 맵을 푸시합니다.
- 클러스터에 대한 연결이 끊어진 클라이언트는 재설정을 시도합니다(구성 가능). 재접속할 때마다(처음이든 아니든) 클러스터에 있는 최신 맵을 가져옵니다. 아이러니하게도 이론상으로는 불안정한 네트워크가 리밸런싱 중에 맵을 지속적으로 업데이트하는 데 도움이 될 수 있지만, 이는 다른 논의의 문제입니다.
요약
여기까지 읽으셨다면, 이제 리밸런싱이 무엇인지 알았을 뿐만 아니라 어떻게 작동하는지도 알게 되셨을 것입니다. 이는 클러스터를 성장시킬 때 서버와 클라이언트 모두에서 이면에서 어떤 일이 벌어지고 있는지 더 잘 이해하는 데 큰 도움이 될 것입니다.
그리고 두 번째 부분 에서는 클러스터와 재조정 진행 상황을 모니터링하는 방법, 장애를 처리하는 방법, 재조정 프로세스 중에 클러스터의 성능이 어떻게 영향을 받을 수 있는지(그리고 어떻게 완화할 수 있는지)를 살펴봅니다.
블로그 게시물에 감사드립니다. 카우치베이스 복제가 어떻게 작동하는지에 대해 조금 더 이해하는 데 정말 유용했습니다. 추가 질문이 있는데, 현재 버전의 카우치베이스 3.0에서 이 기능이 크게 변경되었는지 아니면 블로그 게시물에 설명된 대로 구현이 유지되는 일반적인 아이디어인지 궁금합니다.
저는 현재 우리 회사의 요구 사항에 맞는지 확인하기 위해 카우치베이스를 테스트하고 있습니다. 수백만 개의 문서를 생성하고 거의 즉각적으로 읽기를 수행하고 있습니다. 노드 장애가 발생했을 때 Couchbase가 어떻게 작동하는지 알고 싶었습니다. 테스트 중에 클러스터의 서버 노드 하나를 죽여서 자동 장애 조치를 사용하여 Couchbase가 어떻게 처리하는지 확인했습니다. 리밸런싱을 할 때까지는 요청을 허용 가능한 방식으로 처리 할 수있는 것 같지만 Java 클라이언트 2.0.1에서 연결 오류가 발생합니다. 이런 일이 발생하는 이유가 있나요?
삽입 프로세스가 종료된 후에만 리밸런싱을 해야 하나요? 그렇다면 트랜잭션 수가 엄청나게 많고 장애가 발생하면 노드를 페일오버한 다음 모든 노드를 다시 가동하고 클러스터를 다시 최적의 상태로 만들 때까지 리밸런싱해야 하므로 좋지 않습니다.
건배,
Ivan
Ivan에 글을 써 주셔서 감사합니다!
이 게시물은 상당히 오래되었지만 기본 개념은 그대로 유지됩니다. 3.0의 가장 큰 변화는 TAP의 사용이 중단되고 클러스터 내에서, 다른 클러스터로, 나아가 타사 도구로 데이터를 이동/복제하는 데 훨씬 더 큰 유연성과 기능을 제공하는 데이터베이스 변경 프로토콜(DCP)이 도입되었다는 점입니다. DCP는 기본적으로 위에서 설명한 대로 복제 및 리밸런싱을 위해 TAP와 동일한 방식으로 사용됩니다.
현재 보고 계신 문제와 관련해서는 예상치 못한 문제입니다. 리밸런싱은 애플리케이션에 거의 또는 전혀 영향을 주지 않으면서 수행되어야 하며, 고객들이 많이 사용하고 QA에서 많은 테스트를 거칩니다. 메일링 리스트에 참여하시거나 저에게 직접 연락(perry@couchbase.com)해 주시면 기술 리소스를 확보하여 함께 작업할 수 있도록 도와드리겠습니다.
다시 한 번 감사드리며 도움을 드릴 수 있기를 기대합니다.
Perry
SSL 또는 기타 보안 연결을 사용한 리밸런싱을 지원하나요? 저희는 스택의 노드 간에도 모든 곳에서 SSL에 대한 보안 요구 사항을 적용하고 있습니다.
안녕하세요 Kevin, 글을 보내주셔서 감사합니다. 현재로서는 클러스터 노드 간 암호화 기능은 없지만(클러스터 간 암호화는 지원하지만) 보안 로드맵의 일부로 논의되고 있는 사항입니다. 비공개 이메일을 보내주시면 보안 제품 관리자와 연락하여 요구 사항과 해결 방법을 파악할 수 있도록 도와드리겠습니다.