티스토리 뷰

04. 부호화와 발전

p113-114

대부분의 경우 애플리케이션 기능을 변경하려면 저장하는 데이터도 변경해야 한다.

  • 서버 측 애플리케이션에서는 한 번에 몇 개의 노드에 새 버전을 배포하고 새로운 버전이 원활하게 실행되는지 확인한 다음 서서히 모든 노드에서 실행되게 하는 순회식 업그레이드(rolling upgrade) 방식이 있다.
  • 클라이언트 측 애플리케이션은 사용자에 전적으로 좌우된다.

이것은 예전 버전의 코드와 새로운 버전의 코드, 이전의 데이터 타입과 새로운 데이터 타입이 어쩌면 모든 시스템에 동시에 공존할 수 있다는 의미다. 시스템이 계속 원활하게 실행되게 하려면 양방향으로 호환성을 유지해야 한다.

  • 하위 호환성 : 새로운 코드는 예전 코드가 기록한 데이터를 읽을 수 있어야 한다.
  • 상위 호환성 : 예전 코드는 새로운 코드가 기록한 데이터를 읽을 수 있어야 한다.

데이터 부호화 형식

p115

데이터를 파일에 쓰거나 네트워크를 통해 전송하려면 스스로를 포함한 일련의 바이트열의 형태로 부호화해야 한다.

인메모리 표현에서 바이트열로의 전환을 부호화(직렬화, 마샬링)라고 하며, 그 반대를 복호화(파싱, 역직렬화, 언마샬링)이라고 한다.

언어별 형식

p115-116

많은 프로그래밍 언어는 인메모리 객체를 바이트열로 부호화하는 기능을 내장한다.

예) 자바의 java.io.Serializable, 루비의 Marshal, 파이썬의 pickle

내장된 부호화 라이브러리의 단점

  • 다른 언어에서 데이터를 읽기 어려움
  • 복호화 과정이 임의의 클래스를 인스턴스화할 때 발생할 수 있는 보안 문제 (https://cwe.mitre.org/data/definitions/502.html)
  • 데이터 버전 관리(상위, 하위 호환성) 문제 등한시
  • 효율성(부호화나 복호화에 소요되는 CPU 시간과 부호화된 구조체의 크기) 등한시

JSON과 XML, 이진 변형

p116-117

JSON, XML, CSV의 단점

  • XML과 CSV에서는 수와 숫자(digit)로 구성된 문자열을 구분할 수 없다. JSON은 문자열과 수를 구분하지만 정수와 부동소수점 수를 구별하지 않고 정밀도를 지정하지 않는다. 예) 2^53보다 큰 정수는 파싱할 때 부정확해질 수 있음.
  • JSON과 XML은 유니코드 문자열은 지원하지만 이진 문자열은 지원하지 않는다.
  • XML/JSON 스키마를 사용하지 않는 애플리케이션은 필요한 부호화/복호화 로직을 하드코딩해야 한다.
  • CSV는 스키마가 없으므로 각 로우와 칼럼의 의미를 정의하는 작업은 애플리케이션이 해야 한다.

이진 부호화

p117-119

텍스트 버전의 JSON 및 XML보다 적은 공간 사용을 위해 예) 메시지팩

이번 장에서 다양한 형식으로 이진 부호화할 레코드 예 (텍스트 JSON 부호화 시 81바이트)

{
    "userName": "Martin",
    "favoriteNumber": 1337,
    "interests": ["daydreaming", "hacking"]
}

메시지팩

  • 스키마를 지정하지 않기 때문에 부호화된 데이터 안에 모든 객체의 필드 이름을 포함해야 함
  • 예제 66바이트로 부호화

스리프트와 프로토콜 버퍼

p119-124

아파치 스리프트와 프로토콜 버퍼는 같은 원리를 기반으로 한 이진 부호화 라이브러리다. 원래 프로토콜 버퍼는 구글에서 개발했고 스리프트는 페이스북에서 개발했지만, 둘 다 2007년에서 2008년에 걸쳐 오픈소스가 됐다.

스리프트와 프로토콜 버퍼는 스키마 정의를 사용해 코드를 생성하는 도구가 있다.

스리프트 - 바이너리프로토콜

  • 필드 이름이 없고 필드 태그를 포함
  • 예제 59바이트로 부호화

스리프트 - 컴팩트프로토콜

  • 필드 타입과 태그 숫자를 단일 바이트로 줄이고 가변 길이 정수를 사용해서 부호화
  • 예제 34바이트로 부호화

프로토콜 버퍼

  • 비트를 줄여 저장하는 처리 방식, required나 optioanl 표시가 있다는 점 등 약간의 차이는 있으나 부호화 방식은 스리프트의 컴팩트프로토콜과 매우 비슷
  • 예제 33바이트로 부호화

필드 태그와 스키마 발전

스키마 발전 : 스키마는 필연적으로 시간이 지남에 따라 변한다.

필드 태그 : 필드를 식별하기 위한 태그 숫자, 변경 불가능

  • 예전 코드에서 새로운 필드 읽을 시 무시
  • 새로운 필드는 required가 아닌 optional로 하거나 기본값을 가져야 함
  • optional 필드만 삭제 가능
  • 같은 태그 번호는 절대 다시 사용할 수 없음

데이터타입과 스키마 발전

필드의 데이터타입 변경은 불가능하지는 않지만 값이 정확하지 않거나 잘릴 위험이 있다. 예) 32비트 정수를 64비트 정수로 변경 시 하위 호환성 문제

프로토콜 버퍼의 경우 (단일 값인) optional 필드를 (다중 값인) repeated 필드로 변경해도 문제가 없다.

스리프트의 목록 데이터타입은 단일 값에서 다중 값으로의 변경을 허용하지 않지만 중첩된 목록을 지원한다는 장점이 있다.

아브로

p124-125

스리프트가 하둡의 사용 사례에 적합하지 않아 2009년 하둡의 하위 프로젝트로 시작

두 개의 스키마 언어 지원

  • 아브로 IDL
  • JSON 기반 언어

스키마에 태그 번호가 없음

예제 32바이트로 부호화

데이터타입을 식별하기 위한 정보 없음

정수는 가변 길이 부호화를 사용해서 부호화 (스리프트의 컴팩트프로토콜과 동일)

스키마에 나타난 순서대로 필드를 살펴보고 각 필드의 데이터타입을 미리 파악해야 함. 즉, 읽기와 쓰기가 정확히 같은 스키마를 사용해야 함.

쓰기 스키마와 읽기 스키마

p125

스키마 발전 아브로의 핵심 아이디어는 쓰기 / 읽기 스키마가 동일하지 않아도 호환 가능하면 된다. 이름으로 필드를 일치시키기 때문에 쓰기 / 읽기 스키마의 필드 순서가 달라도 문제 없다. (그림 4-6)

스키마 발전 규칙

p127

상위 호환성 : 새로운 버전의 쓰기 스키마와 예전 버전의 읽기 스키마를 가질 수 있음

하위 호환성 : 새로운 버전의 읽기 스키마와 예전 버전의 쓰기 스키마를 가질 수 있음

기본값이 있는 필드만 추가하거나 삭제 할 수 있음

필드에 널을 허용하려면 유니온 타입을 사용해야 함. 예) union { null, long, string } field;

필드의 데이터타입 변경 가능

필드 이름 변경은 조금 까다롭지만 별칭을 포함할 수 있음 (별칭에 예전 쓰기 스키마 필드 이름 매치 가능)

필드 이름 변경과 유니온 타입에 엘리먼트를 추가하는 것은 하위 호환성은 있지만 상위 호환성은 없음

그러면 쓰기 스키마는 무엇인가?

p128

  • 많은 레코드가 있는 대용량 파일 : 파일의 시작 부분에 한 번만 쓰기 스키마 포함
  • 개별적으로 기록된 레코드를 가진 데이터베이스 : 모든 부호화된 레코드의 시작 부분에 버전 번호를 포함하고 데이터베이스에는 스키마 버전 목록을 유지
  • 네트워크 연결을 통해 레코드 보내기 : 연결을 유지하는 동안 합의된 스키마 사용

동적 생성 스키마

p128-129

프로토콜 버퍼와 스리프트에 비해 아브로 방식이 갖는 한 가지 장점

  • 스키마에 태그 번호가 포함돼 있지 않음 (동적 생성 가능)

코드 생성과 동적 타입 언어

p129-130

스리프트와 프로토콜 버퍼는 자바, C++, C# 같은 정적 타입 언어에서 유용

아브로는 자바스크립트, 루비, 파이썬 같은 동적 타입 프로그래밍 언어에서 유용 (단, 정적 타입 프로그래밍 언어를 위한 코드 생성도 선택적으로 제공함.)

스키마의 장점

p130-131

스키마 언어(프로토콜 버퍼, 스리프트, 아브로)는 XML 스키마나 JSON 스키마보다 훨씬 간단하며 더 자세한 유효성 검사 규칙을 지원한다.

  • 부호화된 데이터에서 필드 이름을 생략할 수 있기 때문에 다양한 "이진 JSON" 변형보다 크기가 훨씬 작을 수 있다.
  • 스키마는 유용한 문서화 형식이다. 복호화를 할 때 스키마가 필요하기 때문에 스키마가 최신 상태인지를 확신할 수 있다.
  • 스키마 데이터베이스를 유지하면 스키마 변경이 적용되기 전에 상위/하위 호환성을 확인할 수 있다.
  • 컴파일 시점에 타입 체크를 할 수 있다.

데이터플로 모드

p131

발전성 : 한 번에 모든 것을 변경할 필요 없이 시스템의 다양한 부분을 독립적으로 업그레이드해 변경 사항을 쉽게 반영하는 능력

호환성 : 데이터를 부호화하는 하나의 프로세스와 그것을 복호화하는 다른 프로세스 간의 관계

프로세스 간 데이터를 전달하는 가장 보편적인 방법

  • 데이터베이스를 통해
  • 서비스 호출을 통해
  • 비동기 메시지 전달을 통해

데이터베이스를 통한 데이터플로

p131-132

데이터베이스에 뭔가를 저장하는 일을 미래의 자신에게 미시지를 보내는 일처럼 생각할 수 있다.

다양한 시점에 기록된 다양한 값

p133

데이터가 코드보다 더 오래 산다(data outlives code)

대부분의 관계형 데이터베이스는 기존 데이터를 다시 기록하지 않고 널을 기본값으로 갖는 새로운 칼럼을 추가하는 간단한 스키마 변경을 허용한다. 따라서 스키마 발전은 기본 저장소가 여러 가지 버전의 스키마로 부호화된 레코드를 포함해도 전체 데이터베이스가 단일 스키마로 부호화된 것처럼 보이게 한다.

보관 저장소

p134

데이터베이스의 스냅숏

서비스를 통한 데이터플로: REST와 RPC

p134-135

  • 서비스 지향 설계(SOA)
  • 마이크로서비스 설계(MSA)

목적 : 서비스를 배포와 변경에 독립적으로 만들어 애플리케이션 버전과 유지보수를 더 쉽게 할 수 있게 만드는 것 --> 예전 버전과 새로운 버전의 서버와 클라이언트가 동시에 실행되기를 기대함

웹 서비스

p135-136

  • REST
  • SOAP

원격 프로시저 호출(RPC) 문제

p137-138

RPC 모델 : 원격 네트워크 서비스 요청을 같은 프로세스 안에서 특정 프로그래밍 언어의 함수나 메서드를 호출하는 것과 동일하게 사용 가능하게 해준다.

네트워크 문제로 발생할 수 있는 문제점

  • 타임아웃으로 결과 없이 반환
  • 요청이 실제로는 처리되고 응답만 유실
  • 함수 호출보다 훨씬 느리고 지연 시간 다양
  • 비트열로 부호화 시 큰 객체는 문제가 될 수 있음
  • 클라이언트와 서비스가 다른 프로그래밍 언어로 구현될 경우, 데이터타입 변환해야 함

RPC의 현재 방향

p138

스리프트와 아브로는 RPC 지원 기능 내장

gRPC는 프로토콜 버퍼를 이용한 RPC 구현

차세대 RPC 프레임워크는 원격 요청이 로컬 함수 호출과 다르다는 사실을 더욱 분명히 함

  • 퓨처(future, promise) : 병렬로 여러 서비스에 요청을 보내야 하는 상황 간소화 및 요청 결과 취합
  • GRPC : 하나의 요청과 하나의 응답뿐만 아니라 시간에 따른 일련의 요청과 응답으로 구성된 스트림 지원

REST API는 실험과 디버깅에 적합하고 다양한 도구 생태계를 지원하기 때문에 공개 API에 적합

RPC 프레임워크는 같은 데이터 센터 내의 같은 조직이 소유한 서비스 간 요청에 적합

데이터 부호화와 RPC의 발전

p139

RPC 스키마의 상하위 호환 속성은 사용된 모든 부호화로부터 상속된다.

  • 스리프트, gRPC(프로토콜 버퍼), 아브로 RPC는 각 부호화 형식의 호환성 규칙에 따라 발전
  • SOAP에서 요청과 응답은 XML 스키마로 지정
  • RESTful API는 응답에 JSON을 일반적으로 사용, 요청에는 JSON이나 URI 부호화/폼 부호화(form-encoded) 사용

메시지 전달 데이터플로

p139-140

비동기 메시지 전달 시스템

메시지 브로커(메시지 큐)나 메시지 지향 미들웨어를 거쳐 비동기로 단방향 전달

RPC 대비 장점

  • 시스템 안정성 향상
  • 메시지 유실 방지
  • 송신자가 수신자의 IP 주소나 포트 번호 알 필요 없음
  • 하나의 메시지를 여러 수신자로 전송 가능
  • 송신자와 수신자는 논리적으로 분리

메시지 브로커

p140-141

분산 액터 프레임워크

p141-142

댓글