개선된 노드 간 메시징
개선된 노드 간 메시징 (Improved Internode Messaging)
Apache Cassandra 4.0은 노드 간(internode) 메시징에 몇 가지 새로운 개선 사항을 추가했어요.
출처: 문서
본문
최적화된 노드 간 메시징 프로토콜
노드 간 메시징 프로토콜이 최적화되었습니다(CASSANDRA-14485). 이전에는 초기 연결/세션이 설정될 때 IPAddressAndPort가 한 번 전송됐음에도 불구하고, 보내는 각 메시지마다 발신자의 IPAddressAndPort가 포함됐어요. Cassandra 4.0에서는 IPAddressAndPort가 전송되는 각각의 별도 메시지에서 제거되고, 연결/세션이 시작될 때만 전송됩니다.
또 다른 개선은 여러 지점(아래 나열)에서 고정 4바이트 정수 값이 vint로 대체된 것입니다. vint는 거의 항상 1바이트보다 작기 때문이에요:
- paramSize(헤더의 파라미터 수)
- 각 개별 파라미터 값
- payloadSize
NIO 메시징
Cassandra 4.0에서 peer-to-peer(노드 간) 메시징은 Netty를 통한 비차단 I/O(NIO)로 전환됐어요(CASSANDRA-8457).
직렬화 형식으로 각 메시지는 여러 고정 필드가 있는 헤더, 선택적 key-value 파라미터 섹션, 그리고 메시지 페이로드 자체를 포함합니다. 참고: 헤더의 IP 주소는 IPv4(4바이트) 또는 IPv6(16바이트)일 수 있어요.
아래 다이어그램은 간결함을 위해 IPv4 주소를 보여줍니다.
1 1 1 1 1 2 2 2 2 2 3 3 3 3 3 4 4 4 4 4 5 5 5 5 5 6 6
0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| PROTOCOL MAGIC |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Message ID |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Timestamp |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Addr len | IP Address (IPv4) /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ | Verb /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ | Parameters size /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ | Parameter data /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Payload size |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| /
/ Payload /
/ |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
개별 파라미터는 String 키와 바이트 배열 값을 가져요. 키는 길이(2바이트로 인코딩)와 함께 직렬화된 뒤 문자열의 UTF-8 바이트 인코딩이 이어집니다. 본문은 길이(4바이트로 인코딩)와 함께 직렬화된 뒤 값의 바이트가 이어져요.
큐에 쌓인 메시지의 리소스 한도
메시지의 serializedSize로 측정되는, 큐에 쌓인 아웃바운드 메시지 수에 대한 엄격한 리소스 한도를 적용해 시스템 안정성이 개선되었습니다(CASSANDRA-15066). 실패의 합리적인 조합이 노드 안정성에 영향을 주지 않으면서 항상 진행이 이루어지도록 세 가지 별도의 한도가 동시에 적용됩니다.
- 다른 노드로 전달되도록 큐에 쌓이고 클러스터의 다른 노드에서 도착한 뒤 처리 대기 중인 메시지에는 전역, 엔드포인트별, 연결별 한도가 적용돼요. 이 한도는 전송되거나 수신되는 메시지의 wire 크기에 적용됩니다.
- 기본 per-link 한도는 엔드포인트나 전역 한도가 적용되기 전에 격리되어 소모됩니다. 각 노드 쌍에는 세 개의 링크가 있어요: urgent, small, large. 특정 노드는 노드 간 조정 없이도
N*3 * (internode_application_send_queue_capacity(bytes) + internode_application_receive_queue_capacity(bytes))크기의 메시지 데이터를 큐에 둘 수 있지만, 실제로는 token-aware 라우팅에서 RF*tokens 노드만이 상당한 대역폭으로 통신해야 할 것입니다. - 엔드포인트별 한도는 per-link 한도를 초과하는 모든 메시지에, 전역 한도와 동시에, 클러스터의 단일 노드로/로부터의 모든 링크에 적용됩니다. 전역 한도는 per-link 한도를 초과하는 모든 메시지에, 엔드포인트별 한도와 동시에, 클러스터의 어떤 노드로/로부터의 모든 링크에 적용돼요. 큐에 쌓인 메시지의 리소스 한도를 위해
cassandra.yaml에 다음 구성 설정이 추가되었습니다.
internode_application_send_queue_capacity: 4MiB
internode_application_send_queue_reserve_endpoint_capacity: 128MiB
internode_application_send_queue_reserve_global_capacity: 512MiB
internode_application_receive_queue_capacity: 4MiB
internode_application_receive_queue_reserve_endpoint_capacity: 128MiB
internode_application_receive_queue_reserve_global_capacity: 512MiB
메시징 메트릭용 가상 테이블
노드 간 인바운드 및 아웃바운드 메시징에 대한 메트릭을 가상 테이블로 유지해 메트릭이 개선되었습니다(CASSANDRA-15066). 인바운드 메시징을 위해 다음 메트릭을 유지하는 가상 테이블(internode_inbound)이 추가되었어요:
- 오류로 인해 직렬화되거나 플러시되지 못한 메시지의 바이트와 수
- 예약된 메시지의 바이트와 수
- 성공적으로 처리된 메시지의 바이트와 수
- 성공적으로 수신된 메시지의 바이트와 수
- 스로틀된 메시지의 나노초와 수
- 만료된 메시지의 바이트와 수
- 복구된/복구되지 않은 손상 프레임
아웃바운드 노드 간 메시징을 위해 별도의 가상 테이블(internode_outbound)이 추가되었습니다. 아웃바운드 가상 테이블은 다음 메트릭을 유지해요:
- 보류 중인 메시지의 바이트와 수
- 전송된 메시지의 바이트와 수
- 만료된 메시지의 바이트와 수
- 오류로 인해 전송하지 못한 메시지의 바이트와 수
- 과부하된 메시지의 바이트와 수
- 활성 연결 수
- 연결 시도 횟수
- 성공한 연결 시도 횟수
힌트 메시징
이미 ByteBuffer로 인코딩된 힌트를 받아 그대로 전송하는 특수 버전의 힌트 메시지가 추가되었어요. 이는 힌트 파일을 같은 메시징 버전의 노드로 보낼 때(가장 일반적인 경우)의 최적화입니다. 추가 ByteBuffer 할당과 불필요한 힌트 역직렬화-직렬화 주기 하나를 절약합니다.
노드 간 애플리케이션 타임아웃
애플리케이션 공간에서 연결이 쓰기 불가능할 수 있는 최대 연속 기간에 대한 구성 설정이 cassandra.yaml에 추가되었어요.
# internode_application_timeout_in_ms = 30000
다른 새 기능에는 쿼리 추적을 위한 trace 메시지에 메시지 크기 로깅이 포함됩니다.
로컬 요청에 대한 Paxos prepare/propose 단계 최적화
4.0 이전에는 요청이 로컬에서 처리되더라도 Paxos prepare와 propose 메시지가 항상 Cassandra의 전체 MessagingService 스택을 통과했습니다. 우리는 로컬 요청이 MessagingService를 거치지 않도록 개선할 수 있어요. Cassandra의 다른 곳에서도 로컬 요청에 대해 MessagingService 단계를 건너뛰는 유사한 작업이 이루어집니다.
추적(tracing)을 켜고 라이트웨이트 트랜잭션을 실행할 때 4.0 이전에는 이렇게 보였어요:
Sending PAXOS_PREPARE message to /A.B.C.D [MessagingService-Outgoing-/A.B.C.D] | 2017-09-11
21:55:18.971000 | A.B.C.D | 15045
… REQUEST_RESPONSE message received from /A.B.C.D [MessagingService-Incoming-/A.B.C.D] |
2017-09-11 21:55:18.976000 | A.B.C.D | 20270
… Processing response from /A.B.C.D [SharedPool-Worker-4] | 2017-09-11 21:55:18.976000 |
A.B.C.D | 20372
Propose 단계에도 동일하게 적용됩니다.
버전 4.0에서는 로컬 요청에 대한 Paxos prepare와 propose 단계가 최적화되었습니다(CASSANDRA-13862).
품질 보증 (Quality Assurance)
버전 4.0에서는 몇 가지 다른 품질 보증 개선도 이루어졌습니다(CASSANDRA-15066).
프레이밍 (Framing)
버전 4.0은 모든 노드 간 메시지에 프레이밍을 도입했어요. 즉 메시지를 헤더와 트레일러가 있는 단일 논리적 페이로드로 그룹화합니다. 이러한 프레임은 최대 하나의 메시지(큰 메시지의 경우 자신만의 고유한 프레임 시퀀스로 분할됨)를 포함하거나, 프레임이 완전한 메시지만 포함한다는 것이 보장됩니다.
손상 방지
이전에는 데이터센터 내 노드 간 메시지가 기본적으로 손상으로부터 보호되지 않았어요. LZ4만이 무결성 검사를 제공했기 때문입니다. 4.0 이후 노드로의 모든 메시지는 명시적 프레임으로 기록되며, 이는 다음 중 하나일 수 있어요:
- LZ4 인코딩
- CRC 보호
Unprotected(보호 없음) 옵션도 여전히 사용 가능합니다.
복원력 (Resilience)
복원력을 위해 모든 프레임은 각각 8바이트와 6바이트의 별도 CRC 보호 헤더와 함께 기록됩니다. 이 헤더에 손상이 발생하면 이전과 같이 연결을 재설정해야 해요. 헤더 밖에서 손상이 발생하면 손상된 프레임을 건너뛰어 연결을 유지하고 불필요한 메시지 손실을 피합니다.
이전에는 스트림의 어느 지점에서든 문제가 발생하면 연결이 재설정되고 진행 중이던 메시지가 손실됐어요.
효율성 (Efficiency)
인바운드와 아웃바운드 메시지 모두에서 전반적인 메모리 사용량과 바이트 셔플 수가 줄어듭니다.
아웃바운드에서 Netty LZ4 인코더는 압축 프레임을 만들기 전에 채워지는 청크 크기 버퍼(64KiB)를 유지합니다. 우리 프레임 인코더는 이 중복 복사를 피하고, 엔드포인트당 192KiB를 해제합니다.
인바운드에서 프레임 디코더는 프레임을 파싱하는 데 필요한 바이트 수만 복사하고, 필요한 것보다 많은 바이트를 절대 저장하지 않도록 보장합니다. 이 개선은 LZ4 연결에 두 번 적용되어 메시지 디코드와 LZ4 프레임 디코드 모두를 개선합니다.
인바운드 경로
버전 4.0은 인바운드 경로에 몇 가지 개선을 도입했어요.
특정 연결에 큰 메시지가 올지 작은 메시지가 올지 플래그로 설정된 것에 따라 적절한 메시지 핸들러가 사용됩니다. 이벤트 루프에서 실행되는 NonblockingBufferHandler는 작은 메시지에, 이벤트 루프 밖에서 실행되는 BlockingBufferHandler는 큰 메시지에 사용됩니다. InboundMessageHandler의 단일 구현은 바이트 스트림에서 들어오는 메시지의 크기를 유도해 모든 크기의 메시지를 효과적으로 처리합니다. 스트림에서 메시지 크기를 유도하는 것 외에도, 들어오는 메시지의 만료 시간을 전체 메시지를 역직렬화하려 하기 전에 먼저 읽어냅니다. 메시지가 마주쳤을 때 이미 만료되었다면 바이트 스트림에서 메시지를 그냥 건너뜁니다. 그리고 수신 측에서 메시지 역직렬화가 실패하면(예: table id나 컬럼을 알 수 없는 경우) 연결을 끊고 버퍼링된 모든 메시지를 잃지 않고 바이트를 건너뜁니다. 조정자(coordinator) 노드에 즉시 실패 이유를 담은 응답을 보내며, 조정자 콜백이 만료되기를 기다리지 않아요. 이 로직은 손상된 프레임에도 적용되어, 연결을 끊지 않고 손상된 프레임을 안전하게 건너뜁니다.
인바운드 경로는 메모리 사용에 엄격한 한도를 적용합니다. 구체적으로 파싱됐지만 처리되지 않은 모든 메시지가 차지하는 메모리는 연결별, 엔드포인트별, 전역 기준으로 제한됩니다. 연결이 자체 로컬 미처리 용량을 초과하고 엔드포인트·전역 예비 용량에서 허용(permit)을 빌릴 수 없으면, 충분한 용량을 되찾을 때까지 더 이상 메시지를 처리하지 않아 자연스러운 백프레셔(backpressure)를 제공합니다.
아웃바운드 연결
연결 열기
엔드포인트가 거부하거나, 버전이 호환되지 않거나, 예상치 못한 예외 등 모든 종류의 연결 실패에 대해 일관된 접근 방식을 채택합니다:
- 성공하거나 전달할 메시지가 없을 때까지 계속 재시도.
- 재연결 전에 최대 1초까지 점진적으로 더 긴 시간을 대기.
- 연결 실패 중에는 예비 큐 한도를 획득하지 않음.
연결 닫기
- 전달되기를 기다리는 아웃바운드 메시지를 올바르게 드레인합니다(연결이 끊기고 재연결에 실패하지 않는 한).
- 닫히는 연결에 기록된 메시지는 전달되거나 거부되며, 옛 연결이 회복 불가능하게 닫힌 경우 새 연결이 열립니다.
- 사용되지 않는 연결은 결국 정리됩니다.
재연결 (Reconnecting)
때로는 완벽히 유효한 연결을 다시 연결해야 할 수 있어요. 예를 들어 선호 IP 주소가 변경되는 경우입니다. 연결을 닫고 재연결하기 전에 기본 연결에 진행 중인 작업이 없는지 보장합니다.
메시지 실패
콜백에 즉시 전파되어, 커밋된 메모리를 회수함으로써 과부하를 더 잘 방지합니다.
만료 (Expiry)
- 더 이상 헤드오브라인 차단(head-of-line blocking)이 발생하지 않습니다(예: 버릴 수 없는 메시지가 버릴 수 있는 모든 메시지의 만료를 막는 경우).
- 과부하 중에는 인큐 스레드에서 만료를 적극적으로 시도합니다.
- 연결이 끊긴 동안에는 메시지가 더 이상 전송되지 않지만 만료할 큰 백로그가 있는 경우를 처리하기 위해 정기적 정리를 예약합니다.
과부하 (Overload)
- 메시지 수가 아닌 큐에 쌓인 바이트로 추적합니다.
직렬화 오류 (Serialization Errors)
- 연결이 무효화되지는 않습니다. 메시지는 실패로 완료되고 프레임에서 지워집니다.
- 계산된 직렬화 크기와 실제 크기의 불일치 감지를 포함합니다.
네트워크로의 플러시 실패(예: 연결이 재설정된 경우)는 필요한 정보가 버려졌기 때문에 현재 콜백 핸들러에 통지되지 않습니다. 다만 미래에 그것이 가치 있다고 판단되면 통지할 수는 있어요.
QoS
시스템 안정성에 영향을 주는 작은 메시지를 위해 "Gossip" 연결이 일반 목적의 "Urgent" 연결로 대체되었습니다.
메트릭
가상 테이블과 JMX를 통해 오류로 직렬화/플러시하지 못한 메시지, 과부하나 타임아웃으로 버린 메시지, 보류 중인 메시지, 성공적으로 보낸 메시지의 수와 바이트를 추적하고 노출합니다.
메시지 크기 한도 추가
4.0 이전 Cassandra는 노드 간 Message 객체에 대해 큰 버퍼 할당으로부터 서버를 보호하지 않았어요. 메시지 크기 한도를 추가하면 오작동하는 클러스터 참여자 같은 문제를 처리하는 데 좋습니다. 버전 4.0은 max mutation size와 유사하게, 기본적으로 endpoint reserve capacity로 설정되는 최대 메시지 크기 구성 파라미터를 도입했어요.
노드 간 메시지 역직렬화 시 알 수 없는 테이블에서 복구
CASSANDRA-9289에서 논의된 것처럼, 다른 노드의 메시지에서 알 수 없는 테이블을 만났을 때 우아하게 복구할 수 있으면 좋습니다. 4.0 이전에는 연결을 닫고 재연결했는데, 이는 다른 동시 쿼리를 실패시킬 수 있어요. 버전 4.0은 메시지를 TrackedDataInputPlus로 스트림에 감싸고 UnknownCFException을 잡아 이 메시지의 나머지 바이트를 건너뜀으로써 문제를 해결합니다. TCP는 닫히지 않으며, 다른 메시지를 위해 연결 상태가 유지됩니다.
더 알아보기 (Learn more)
- 가상 테이블 — 노드 간 메시징 메트릭
- cassandra.yaml 설정 — 노드 간 큐 용량 설정