들어가기 전에
발전성은 변경 사항을 쉽게 적용할 수 있게 시스템을 구축해야 한다는 개념이다.
이번 장의 핵심은 서버와 클라이언트가 사용하는 데이터 부호화는
서비스 API의 버전 간 호환이 가능해야 한다는 점이다.
[개념 사전]
단어 | 의미 |
순회식 업그레이드(rolling upgrade, = 단계적 롤아웃(staged rollout)) | 한 번에 몇 개의 노드에 새 버전을 배포하고 새로운 버전이 원활하게 실행된다면 서서히 모든 노드에서 실행되게 하는 방식 |
최소공통분모 부호화 형식(lowest-common-denominator encoding format) | 다양한 환경에서 최대한의 호환성을 보장하기 위한 가장 기본적인 포맷 |
바이너리프로토콜(BinaryProtocol) | 필드 타입과 태그 숫자를 압축 없이 원시 이진 데이터 형태이면서 고정된 크기로 부호화 |
컴팩트프로토콜(CompactProtocol) | 필드 타입과 태그 숫자를 단일 바이트로 줄이고 가변 길이 정수(variable-length integer)를 사용해 부호화 |
필드 태그(field tag) | 각 필드를 식별하는 숫자(정수 값) |
스키마 발전(schema evolution) | 스키마는 필연적으로 시간이 지남에 따라 변한다는 개념 |
데이터플로(data flow) | 데이터가 시스템 내에서 이동하고 변형되는 과정 (Input, Processing, Output) |
자바 원격 메서드 호출(RMI, Remote Method Invocation) | 네트워크를 통해 다른 JVM에서 실행 중인 객체의 메서드를 호출하는 기술 |
분산 컴포넌트 객체 모델(DCOM, Distributed Component Object Model) | 네트워크 상의 분산된 객체들이 서로 통신할 수 있도록 설계된 모델 |
공통 객체 요청 브로커 설계(CORBA, Common Object Request Broker Architecture) | 이기종 시스템 간의 분산 객체 호출을 가능하게 하는 표준 |
원격 프로시저 호출(RPC, Remote Procedure Call) | 네트워크를 통해 원격 시스템의 함수(프로시저)를 호출하는 기술 (Java RMI, gRPC, Thrift, SOAP 등이 RPC 기반 기술) |
새롭게 알게된 점(New)
[1] 이진 부호화(Binary Encoding)
JSON은 XML보다 덜 장황하지만 이진 형식과 비교하면 둘 다 훨씬 많은 공간을 사용한다.
이런 관찰이 사용 가능한 다양한 이진 부호화의 개발로 이어졌다.
JSON | 메시지팩(MessagePack), BSON, BJSON, 스마일(Smile) |
XML | WBXML, 패스트 인포셋(Fast Infoset) |
- 텍스트 JSON 부호화로 얻은 91바이트보다 이진 부호화의 길이가 66바이트로 약간 작다.
- 작은 공간의 절약과 파싱 속도 향상이 사람의 가독성을 해칠 만큼 가치가 있는지는 확실치 않다.
[2] 이진 부호화 라이브러리
이름 | 개발사 |
아파치 스리프트(Apache Thrift) | 페이스북 |
프로토콜 버퍼(Protocol Buffers, protobuf) | 구글 |
- 각 필드는 태그 숫자로 식별하고 데이터타입을 주석으로 단다.
- 상위 호환성 유지 : 필드에 새로운 태그 번호를 부여하는 방식으로 스키마에 새로운 필드를 추가할 수 있다.
- 하위 호환성 유지 : 스키마의 초기 배포 후 추가되는 모든 필드는 optional로 설정하거나 기본값을 가져야 한다.
- 데이터 타입 변경 시 프로토콜 버퍼에서는 repeated 표시자로 동일한 필드 태그를 여러 번 나타나게 하여 마지막 엘리먼트를 가리키게 하고, 스리프트에서는 목록 데이터타입을 사용한다.
[3] 아파치 아브로
아브로는 스리프트가 하둡의 사용 사례에 적합하지 않아 2009년 하둡의 하위 프로젝트로 시작했따.
- 스키마에 태그 번호가 없으며, 아브로 이진 부호화 길이는 앞선 부호화 중 길이가 가장 짧다.
- 바이트열에 필드나 데이터타입을 식별하기 위한 정보가 없다.
1️⃣ 읽기 스키마와 쓰기 스키마
- 쓰기 스키마와 읽기 스키마가 동일하지 않아도 되며 단지 호환가능하면 된다.
- 스키마 해석(schema resolution)에서 이름으로 필드를 일치시키기 때문에 순서가 달라도 문제없다.
- 호환성
- 데이터를 읽는 코드가 읽기 스키마에는 없고 쓰기 스키마에 존재하는 필드를 만나면, 이 필드는 무시한다.
- 데이터를 읽는 코드가 기대하는 어떤 필드가 쓰기 스키마에는 포함돼 있지 않은 경우에는 읽기 스키마에 선언된 기본값으로 채운다.
- 하위 호환성 유지 : 필드 이름 변경 시 별칭을 포함해 예전 스키마 필드 이름을 매치시키거나, 유니온 타입(union type)에 null 엘리먼트를 추가한다.
🧐 읽기는 특정 데이터를 부호화한 쓰기 스키마를 어떻게 알 수 있을까?
⏺ 많은 레코드가 있는 대용량 파일 : 파일의 시작 부분에 한 번만 쓰기 스키마를 포함시킨다.
⏺ 개별적으로 기록된 레코드를 가진 데이터베이스 : 모든 부호화된 시작 부분에 버전 번호를 포함하고 데이터베이스에는 스키마 버전 목록을 유지한다.
⏺ 네트워크 연결을 통해 레코드 보내기 : 합의된 스키마를 사용한다.
2️⃣ 동적 생성 스키마
동적 생성 스키마를 지원하므로 데이터를 내보낼 때 스키마 변경에 신경 쓸 필요가 없다. 변경 될 때마다 새로운 아브로 스키마를 생성하고 새로운 스키마로 데이터를 내보낸다. (이름으로 필드 매치 다시 한번 기억하기!)
3️⃣ 자기 기술(self-describing)적
쓰기 스키마를 포함한 객체 컨테이너 파일이 있으면 데이터를 볼 수 있기 때문에 정적 타입 프로그래밍 언어를 위한 코드를 생성하지 않아도 사용할 수 있다.
[4] SOAP
네트워크 API 요청을 위한 XML 기반 프로토콜
- SOAP 웹 서비스의 API는 웹 서비스의 기술 언어(WSDL, Web Services Description Language)라고 부르는 XML 기반 언어를 사용해 기술한다.
- SOAP 사용자는 도구 지원과 코드 생성과 IDE에 크게 의존하며 SOAP 벤더가 지원하지 않는 프로그래밍 언어 사용자의 경우 SOAP 서비스와의 통합은 어렵다.
[5] RPC
RPC 모델은 원격 네트워크 서비스 요청을 같은 프로세스 안에서 특정 프로그래밍 언어의 함수나 메서드를 호출하는 것과 동일하게 사용 가능하게 해준다. (location transparency)
gRPC | 프로토콜 버퍼를 이용한 RPC 구현 |
피네글(Finagle) | 스리프트 사용 |
Rest.li | HTTP 위에 JSON 사용 |
- 네트워크 문제가 있음에도 불구하고 RPC는 계속해서 개발되고 있다.
- 퓨처(future)(프라미스(promise))
- 피네글과 Rest.li가 실패할지도 모를 비동기 작업을 캡슐화하기 위해 사용한다.
- 병렬로 여러 서비스에 요청을 보내야 하는 상황을 간소화하고 요청 결과를 취합한다.
- gRPC는 하나의 요청과 응답뿐만 아니라 시간에 따른 일련의 요청과 응답으로 구성된 스트림을 지원한다.
- 일부 프레임워크는 서비스 찾기(service discovery)를 제공해 클라이언트가 특정 서비스를 찾을 수 있는 IP 주소와 포트 번호를 제공한다.
1️⃣ RESTfule API의 이점
- 실험과 디버깅에 적합하다.
- 코드 생성이나 소프트웨어 설치 없이 웹 브라우저나 커맨드 라인 도구인 curl을 사용해 간단히 요청을 보낼 수 있다.
- 주요 프로그래밍 언어와 플랫폼이 지원하고 사용 가능한 다양한 도구 생태계가 있다.
- 서버, 캐시 로드 밸런서, 프락시, 방화벽, 모니터링, 디버깅 도구, 테스팅 도구 등
2️⃣ 주요 초점
보통 같은 데이터센터 내의 같은 조직이 소유한 서비스 간 요청에 있다.
[6] 분산 액터 프레임워크 - 액터 모델(actor model)
- 단일 프로세스 안에서 동시성을 위한 프로그래밍 모델이며, 각 액터는 하나의 클라이언트나 엔티티를 나타낸다.
- 여러 노드 간의 애플리케이션 확장에 사용되며 송신자와 수신자가 어떤 노드에 있든지 관계없이 동일한 메시지 전달 구조를 사용한다.
- 스레드를 직접 처리하는 대신 로직이 액터에 캡슐화된다.
- 메시지 전달을 보장하지 않으므로 유실 가능성이 존재한다.
- 각 액터 프로세스는 한 번에 하나의 메시지만 처리하기 때문에 스레드에 대해 걱정하지 않아도 된다.
- 각 액터는 프레임워크와 독립적으로 실행할 수 있다.
- 인기 있는 분산 액터 프레임워크 TOP 3 { 아카(Akka), 올리언스(Orleans), 얼랭(erlang) }
어려웠거나 이해하지 못한 부분(Difficulty)
- 초반 부호화 예제가 눈에 잘 들어오지 않았지만 이진 부호화를 사용하게 된 의의는 파악 완료!
- 들어봤지만 아직은 낯선 주제들
- RPC
- 분산 액터 프레임워크
추가 내용(Amendment)
💌 백엔드_g 채널의 임성후님 글::gRPC 의문과 해답
- 사내 마이크로서비스 간 통신시 gRPC를 사용 중인 상태에서 HTTP와 gPRC 선택에 대한 의문이 생겨 여러 주제에 대한 질문과 답변으로 정리하는 글.
- Q. gRPC는 어떻게 HTTP 보다 빨라질 수 있는가?
- A. 직렬화 시 데이터 크기를 줄일 수 있어 더 빠른 전송이 가능하다.
- A. 텍스트 기반의 JSON 파싱은 비용이 높은 편이지만, 바이너리 기반의 Protobuf는 상대적으로 낮다.
- A. REST API는 HTTP/1.1을 사용하지만, gRPC는 HTTP/2 위에서 동작해 여러 개의 호출을 단일 연결에서 동시에 처리할 수 있으며 HPACK 알고리즘으로 헤더 크기를 최소화하고 네트워크 대역폭을 크게 절약한다.
- Q. MSA 내부 통신에서는 무조건 gRPC를 써야 하나?
- A. gRPC를 선택하는 것이 좋은 경우
- 트래픽이 많고 응답 속도가 중요한 경우
- 서로 다른 언어로 개발된 서비스 간 공통 인터페이스를 통해 통신하는 경우
- A. gRPC를 선택하는 것이 좋은 경우
💌 백엔드_a 채널의 한서영님 글::우당탕탕 gPRC 적용기
- 통화 상담 내용을 실시간으로 받아 텍스트로 제공하는 신규 프로젝트에 gRPC를 적용한 이유와 로드 밸런싱 문제를 해결한 글.
- gRPC를 사용한 이유
- 실시간성을 보장할 수 있는 성능
- 양방향 스트리밍
- 다양한 언어 지원
- 코드 생성 및 도구 지원
- 테스트 시 gRPC 요청 수가 특정 Pod에 몰려 서버가 죽는 현상 발생
- gRPC는 pick_first으로 첫 번째 서버와 연결이 성공하면 해당 서버로만 계속 요청을 보내는 것이 기본 설정이다.
- 첫 번째 해결 방법. round robin으로 설정했더니 새로운 파드로는 아무 요청이 가지 않는다.
- 두 번째 해결 방법. gRPC 클라이언트에서 자체적으로 채널을 생성, 삭제하여 서버와의 연결을 관리하도록 변경하여 최신 파드 목록을 재반영하고 여러 파드로 요청을 자동으로 분산되게 전달.
- 추가 고민점
- 매번 채널을 새로 생성하여 연결하고 종료하는 것이 시스템에 미칠 영향도
- DNS 캐싱에 대해 서비스 메시와 Linkerd, Istio를 사용하지 않은 이유에 대해 묻는 댓글이 있음!
'DB' 카테고리의 다른 글
[DDIA] 03장. 저장소와 검색 (3) | 2025.05.18 |
---|---|
[DDIA] 02장. 데이터 모델과 질의 언어 (1) | 2025.05.06 |
[DDIA] 01장. 신뢰할 수 있고 확장 가능하며 유지보수하기 쉬운 애플리케이션 (0) | 2025.03.16 |
[jOOQ] ad-hoc 방식을 통한 One-to-One, One-to-Many 조회 (0) | 2025.02.02 |
[PostgreSQL] Docker를 활용한 Streaming Replication 설정 가이드 (2) | 2024.10.31 |