
❐ 0. Description
- 데이터 부호화를 위한 다양한 형식을 살펴본다.
- 스키마를 변경하고 "예전 버전"과 "새로운 버전의 데이터와 코드"가 공존하는 시스템을 어떻게 지원하는지 설명
- 메시지 전달 시스템에서 다양한 데이터 부호화 형식이 데이터 저장과 통신에 어떻게 사용되는지 설명
❐ 1. 데이터 부호화 형식(Formats of Encoding data)
- 프로그램은 보통 두 가지 형태로 표현된 데이터를 사용해 동작
- 두 가지 표현 사이에 전환이 필요함
- 부호화
- 인메모리 표현 ➔ 바이트열
- "직렬화" or "마샬링"이라고도 함.
- 복호화
- 바이트열 ➔ 인메모리 표현
- "파싱" or "역직렬화" or "언마샬링" 이라고도 함.
- 부호화
🌀 1-1. 언어별 형식 (Language-Specific Formats)
많은 프로그래밍 언어는 부호화 기능을 내장.
- java.io.Serializable 등등
매우 편리하지만, 언어에 내장된 부호화를 사용하는 방식은 일반적으로 좋지 않음!
- 프로그래밍 언어에 종속적임
- 다른 언어에서 데이터를 읽기개 매우 어려움
- 다른 시스템과 통합하는데 방해가 될 수 있음.
- 인스턴스화
- 동일한 object types 데이터를 복원하려면,
복호화 과정에서 임의의(arbitrary) 클래스를 인스턴스화 할 수 있어야 함. - 종종 보안 문제의 원인이 됨.
- 동일한 object types 데이터를 복원하려면,
- 호환성
- 데이터 버전 관리는 보통 부호화 라이브러리엣어는 나중에 생각하게 됨.
- 효율성
- 자바의 내장 직렬화는 성능이 좋지 않고 비대해지는 부호화로 유명함.
🌀 1-2. JSON과 XML, 이진 변형 (JSON, XML, and Binary Variants)
- JSON, XML, CSV는 텍스트 형식이라서, 어느 정도 사람이 읽을 수 있음.
- 하지만 피상적인(겉으로 드러나는) 문법적 문제 외에도 일부 미묘한 문제가 있음
JSON, XML, CSV의 미묘한(감지하기 힘든, subtle) 문제점들
- 수(Number)의 부호화에는 많은 애매함이 있음.
- XML과 CSV는 숫자(digit)와 문자 구분 불가 → 정수/부동소수점 구분 X, 정밀도 지정 X.
- JSON은 숫자와 문자열을 구분하지만 정수/부동소수점 구분 X, 정밀도 지정 X.
- 큰 수 표현 문제:
- IEEE 754 부동소수점 한계로 2^53보다 큰 정수는 정확히 표현 불가.
- 예: 트윗 ID(64비트 숫자) → 자바스크립트에서 부정확할 수 있음.
- JSONE 같은 개선안: 숫자를 문자열(10진수)로 표현 → 정밀도 문제 해결.
- JSON / XML의 특징
- JSON / XML은 텍스트(유니코드) 중심 → 사람이 읽고 쓰기 쉽다.
- 하지만 원시 데이터(바이너리) 지원 부족 → Base64 인코딩으로 해결.
- 단점: 크기 33% 증가, 디코딩 필요.
- XML은 문법이 복잡하고 스키마 정의가 어려움.
- JSON은 간단하지만 스키마를 강제하지 않음 → 데이터 무결성 검증이 어려움.
- 따라서 애플리케이션에서 별도 부호화·복호화 로직 필요할 가능성 높음.
- CSV의 한계
- 스키마 없음 → 열(column)의 의미 정의가 애플리케이션에 의존.
- 새로운 컬럼 추가나 변경이 어려움 (수동 코드 수정 필요).
- 매우 모호한 형식:
- 값에 쉼표 포함 시 처리 복잡.
- 이스케이핑 규칙은 존재하지만 구현이 제각각.
- 결국 CSV는 단순하지만 호환성과 정밀성 부족.
이진 부호화
- 데이터를 이진수(0과 1의 비트)로 표현하는 방식
- JSON보다 공간 절약 효과는 크지 않음.
- 장점은 속도 향상과 바이너리 지원.
- 하지만 key 이름 그대로 포함 → 데이터 크기 절약 효과 제한.
- 따라서 텍스트 JSON 대비 가치가 크다고 단언하기 어려움.
🌀 1-3 스리프트와 프로토콜 버퍼
아파치 스리프트 & 프로토콜 버퍼
- 같은 원리를 기반으로 한 이진 부호화 라이브러리
- 스리프트는 두 가지 다른 이진 부호화 형식을 지원함
- Binary Protocol, CompactProtocol
필드 태그와 스키마 발전
- 필드 태그는 절대 변경 불가
- 기존의 모든 부호화된 데이터를 인식 불가능하게 만들 수 있기 때문
- 필드에 새로운 태그 번호를 부여하는 방식으로 스키마엠 새로운 필드 추가할 수 있음.
데이터 타입과 스키마 발전
- 32비트 정수 → 64비트 정수로 변경한다면?
- 옛 코드는 새로운 값을 읽을 수 없음 (32비트 크기 한계).
- 따라서 호환성 깨짐.
- 프로토콜 버퍼는
- 목록 타입(repeated) 지원 → 배열처럼 여러 값 가질 수 있음.
🌀 1-4. 아브로
아브로
- 또 다른 이진 encoding format
- 두 개의 스키마 언어 있음.
- Avro IDL 기반
- JSON 기반
- 스키마에 태그 번호 없음.
- 스키마에 나타난 순서대로 필드를 살펴봐야 함.
쓰기 스키마와 읽기 스키마
- 쓰기 스키마: 데이터를 쓸 때 사용되는 스키마
- 읽기 스키마: 데이터를 복호화 할 때 기대하는 특정 스키마
- 불일치 시 처리 규칙
- 쓰기에는 있지만 읽기에는 없는 필드 → 무시.
- 읽기에는 있지만 쓰기에는 없는 필드 → 기본값으로 채움.
- 타입 불일치 → 가능한 경우 자동 변환.
스키마 발전 규칙
- 기본값 있는 필드만 추가/삭제 가능.
- 기본값 없는 필드 추가/삭제 → 호환성 깨짐.
- Null 허용하려면 유니온(Union) 타입 사용.
- Avro는 optional/required 대신 유니온 타입 + 기본값.
- 필드 타입 변경은 제한적으로만 가능.
- 필드 이름 변경 시 alias로 하위 호환성 유지 가능.
그러면 쓰기 스키마는 무엇인가.
- 모든 레코드에 스키마 전체를 포함하면 공간 낭비 심함.
- 어떤 아브로를 사용하는지에 의존함
- 많은 레코드가 있는 대용량 파일
- 개별적으로 기록된 레코드를 가진 데이터베이스
- 네트워크 연결을 통해 레코드 보내기
동적 생성 스키마
- 아브로는 스키마에 태그 번호가 없음.
- 아브로는 필드 이름 기반 매칭, 프로토콜 버퍼/스리프트는 태그 번호 기반 매칭.
- DB 컬럼이 변해도 Avro 스키마 자동 갱신 가능
코드 생성과 동적 타입 언어
- 스리프트와 프로토콜 버퍼는 코드 생성에 의존
- 반면, 동적 타입 프로그래밍 언어에서는 컴파일 시점에 타입 검사기가 없어
- 코드를 생성하는 것은 중요하지 않음.
- 아브로는 정적 타입 프로그래밍 언어를 위해 코드 생성을 선택적으로 제공함.
- 물론 코드 생성 없이도 사용할 수 있음.
🌀 1-5. 스키마의 장점
- 간단하고 자세한 유효성 검사 규칙을 지원
- 많은 데이터 시스템이 이진 부호화를 독자적으로 구현
이진 부호화의 좋은 속성
- 공간 절약
- 유용한 문서화 형식
- 호환성
- 정적 타입 프로그래밍 언어 지원
❐ 2. 데이터플로 모드
- 데이터플로는 매우 추상적인 개념
- 하나의 프로세스에서 다른 프로세스로 데이터를 전달하는 방법은 아주 많음.
- 이때 누가 데이터를 부호화하고 복호화할까?
- 데이터를 전달하는 가장 보편적인 방법을 살펴보자
🌀 2-1. 데이터베이스를 통한 데이터플로
하위 호환성 필요
- 데이터베이스에 기록하는 프로세스는 데이터를 부호화
- 데이터베이스에서 읽는 프로세스는 데이터를 복호화
- 하위 호환성 필요
상위 호환성 필요
- 동시에 다양한 프로세스가 데이터베이스를 접근
- 데이터베이스 내 값이 새로운 버전의 코드로 기록된 다음,
현재 수행 중인 예전 버전의 코드로 그 값을 읽을 가능성이 있음.
문제 & 해결방안
- 새로운 코드가 이전에는 없던 새 필드(photoURL)를 DB에 저장했다고 가정.
- 이후 예전 코드가 이 데이터를 읽고 다시 기록하면? 새 필드 정보가 유실됨.
- 해결하기 어려운 문제는 아니지만, 애플리케이션 설계 시 주의 필요.
다양한 시점에 기록된 다양한 값
- 데이터가 코드보다 더 오래 산다(data outlives code)
- 데이터를 새로운 스키마로 다시 기록(migration)하는 작업은 가능
- 하지만 이 작업은 비싸기 때문에 가능하면 피하는게 좋음.
보관 저장소
- snap-shot을 수시로 찍으면?
- 데이터 dump는 보통 최신 스키마를 사용해 부호화
🌀 2-2 서비스를 통한 데이터플로 : REST와 RPC
클라이언트와 서버
- 네트워크를 통해 통신해야 하는 프로세스가 있을 때 가장 일반적으로 배치(arrange)하는 방식
- 서버는 API를 공개하고, 클라이언트는 API에 요청을 보내 서버와 상호작용을 함.
- 이때 서비스가 공개한 API를 서비스라고 함.
- 서버 자체가 다른 서비스의 클라이언트일 수 있음(ex. core - onchain 관계)
- service-oriented architecture, SOA
- microservices architecture, MSA
- 여러가지 측면에서 서비스는 데이터베이스와 비슷함
웹 서비스
- 서비스와 통신하기 위한 기본 프로토콜로 HTTP를 사용할 때 이를 웹 서비스라고 함
- 대중적인 두 가지 방법 (REST, SOAP)
REST
- HTTP의 원칙을 토대로한 설계 철학
- REST 원칙에 따라 설계된 API를 RESTful이라고 함.
SOAP (Simple Object Access Protocol)
- 네트워크 API 요청을 위함 XML 기반 프로토콜
- 대부분의 HTTP 기능을 사용하지 않음.
- 대신 WS라고 알려진 웹 서비스 프레임워크를 제공
웹 서비스의 많은 부분이 과장됐고, 여러 심각한 문제들
- EJB, RMI는 자바로 제한
- 분산 컴포넌트 객체 모델(Distributed Component Object Model)은 MSA 플랫폼으로 제한
- 공통 객체 요청 브로커(Common Object Request Broker)는 지나치게 복잡 & 호환성 zero
➔ 웹 서비스는 1970년대 부터 사용한 RPC의 아이디어를 기반으로 함
RPC 모델
- 원격 네트워크 서비스 요청을 같은 프로세스 안에서,
특정 프로그래밍 언어의 함수나 메서드를 호출하는 것과 동일하게 사용 가능하게 해줌.- 이러한 추상화를 위치 투명성(location tranparency)이라 고 함
- 매우 편리한거 같지만, 접근 방식에 근본적인 문제가 있음.
로컬 함수 호출과 네트워크 요청은 다르다!
- 로컬 함수 호출은 예측 가능함
- 네트워크 요청은 예측이 어려움
- 네크워크 문제로 요청과 응답이 유실 or 장비가 느려짐 or 무응답
- 이러한 문제들은 제어할 수가 없음
네트워크로 인해 발생하는 문제에 대한 대응책이 필요하다!
- 네트워크 타입 아웃이 나면 결과 없이 반환될 수 있음
- 응답만 유실 될 수 있음.
- 프로토콜에서 중복 제거기법(멱등성, idempotence)을 적용해야 함.
- 네트워크 요청은 함수 호출보다 훨씬 느리고 지연 시간을 예측할 수 없음.
- 네트워크 요청의 경우, 오든 매개변수는 네크워크를 통해 전송될 수 있게끔 바이트열 인코딩이 필요
- 모든 언어가 같은 타입을 가지는 것이 아니기 때문에 RPC를 사용할 때 신경써야 함.
RPC의 현재 방향
- 그래도 RPC는 계속된다!
- 요즘 RPC 프레임워크는 원격 요청이 로컬 함수 호출과 다르다는 사실을 노골적(explicit)으로 드러냄
- 실패할지도 모를 비동기 작업을 캡슐화하기 위해 future(promise)를 사용함
- gRPC는 하나의 요청과 하나의 응답뿐만 아니라
시간에 따른 일련의 요청과 응답으로 구성된 스트림을 지원함
- 근데 RESTful API가 가지는 다른 중요한 이점이 있음
- 바로 디버깅에 적합하다는 점
- 그리고 모든 주요 프로그래밍 언어와 플랫폼이 지원함(범용성 좋음)
- 결과적으로
- REST는 public API의 우세한(predominant) 스타일로 보이며
- RPC의 주요 초점은 같은 데이터 센터 내의 같은 조직이 소유한 서비스간 요청에 있음
데이터 부호화와 RPC의 발전
- 발전성이 있으려면 RPC 클라이언트와 서버를 독립적으로 변경하고 배포할 수 있어야 함.
- 근데 RPC가 조직 경계를 넘나드는 통신에 사용될 때, 호환성 유지를 어렵게 함
- 서비스 제공자는 클라이언트를 제어할 수 없음.
- 따라서, 호환성을 깨는 변경이 필요하면 서비스 제공자는 보통 여러 버전의 서비스API를 유지해야 함.
- RESTful API는 URL이나 HTTP Accept 헤더에 버전 번호를 사용하는 방식이 일반적
❐ 3. 메시지 전달 데이터플로
- 지금까지는 두 개의 프로세스간 데이터 전달하는 다양한 방법을 살펴봄
- 마지막 절에서는 RPC와 데이터베이스 간 비동기 메시지 전달 시스템을 살펴볼 거임
비동기 메시지 전달 시스템 (Asynchronous message-passing system)
- 이 시스템은 클라이언트 요청을 낮은 지연 시간으로 다른 프로세스에 전달하는 점에서는 RPC와 비슷
- 메시지를 직접 네트워크 연결로 전송하지 않고,
- 임시로 메시지를 저장하는 message borker(또는 message queue)나
- message-oriented middleware라는 중간 단계를 거쳐 전송한다는 점은 데이터베이스와 유사
메시지 브로커를 사용하는 방식의 장점 (비교군: RPC)
- 시스템 안전성 향상
- 메시지 유실 방지
- 송신자가 수신자의 IP 주소나 포트 번호를 알 필요 없음.
- 하나의 메시지를 여러 수신자로 전송할 수 있음.
- 송신자는 누가 소비하는지 상관하지 않음.
메시지 브로커
- 일반적으로 메시지 브로커는 다음과 같이 사용
- 프로세스 하나가 메시지를 이름이 지정된 큐나 토픽으로 전송
- 브로커는 해당 큐나 토픽 하나 이상의 소비자 또는 구독자에게 메시지를 전달
- 동일한 토픽에 여러 생산자와 소비자가 있을 수 있음.
- 보통 특정 데이터 모델을 강요하지 않음.
- 메시지는 일부 메타데이터를 가진 바이트열이므로 모든 부호화 형식을 사용할 수 있음.
- 부호화가 상위 호환성을 모두 가진다면
- 게시자(publisher)와 소비자가 같은 시점에 동시에 배포될 필요가 없고,
- 원하는 순서로 독립적으로 배포할 수 있음.
분산 액터 프레임워크
- 액터 모델(actor model)
- 단일 프로세스 안에서 동시성을 위한 프로그래밍 모델
- 스레드(race condition, locking, deadlock과 연관된 문제들)를 직접 처리하는 대신
로직이 액터에 캡슐화 됨. - 보통 각 액터는 하나의 클라이언트나 엔티티를 나타냄
- 로컬 상태를 가질 수 있고 비동기 메시지의 송수신으로 다른 액터와 통신함
- 메시지 전달을 보장하지 않음. (메시지 유실될 수도)
- 각 액터 프로세스는 한 번에 하나의 메시지만 처리
- 따라서, 스레드에 대해 걱정할 필요없고 각 액터는 프레임워크와 독립적으로 실행 가능
- 여러 노드 간 애플리케이션 확장에 사용
- 송/수신자의 노드 위치는 상관없이 동일한 메시지 전달 구조를 사용
- 다른 노드에 있는 경우 메시지는 명백하게 바이트열로 부호화되고 네트워크를 통해 전송 & 복호화
- 단일 프로세스 안에서도 메시지가 유실될 수 있다고 이미 가정함
- 따라서 위치 투명성은 RPC 보다 액터 모델에서 더 잘 동작함
- 네트워크를 통한 지연 시간이 동일한 프로세스 안에서 더 높을 순 있음.
- 하지만 액터 모델을 사용한 경우 로컬과 원격 통신 간 근본적인 불일치가 적음
- 인기 있는 분산 액터 프레임워크 세 가지 & 메시시 부호화 처리 방식
- 아카(Akka) - 자바의 내장 직렬화
- 올리언스(Orieans) - 기본적으로 사용자 정의 데이터 부호화 형식을 사용
- 얼랭(erlang)
❐ 4. 정리
- 데이터 구조를 네트워크나 디스크 상의 바이트열로 변환하는 다양한 방법을 살펴봄.
- 이런 부호화의 세부 사항은 효율성뿐만 아니라 애플리케이션의 아키텍쳐와 배포의 선택 사항에도 영향을 줌.
- 순회식 업그레이드는 애플리케이션 변경을 쉽게 할 수 있는 발전성에 도움을 줌.
- 시스템을 흐르는 모든 데이터는 상/하위 호환성을 제공하는 방식으로 부호화해야 함.
'Book > 데이터 중심 애플리케이션 설계' 카테고리의 다른 글
| 5장. 복제 (0) | 2025.09.14 |
|---|---|
| Part2. 분산 데이터 (0) | 2025.09.13 |
| 3장. 저장소와 검색 (0) | 2025.08.30 |
| 2장. 데이터 모델과 질의 언어 (0) | 2025.08.23 |
| 1장. 신뢰할 수 있고 확장 가능하며 유지보수하기 쉬운 애플리케이션 (0) | 2025.08.16 |