////
Search

4장 - 부호화와 발전

Date
2022/12/21 08:41
Tags

1. 부호화와 발전

애플리케이션은 필연적으로 시간이 지남에 따라 변하고 사용자 요구사항이나 비즈니스 환경에 따라 추가되거나 변경된다.
대부분 애플리케이션 기능을 변경하려면 저장하는 데이터도 변경 해야한다.
데이터 타입이나 스키마가 변경될 때 애플리케이션 코드에 대한 변경이 발생한다.
하지만 대규모 애플리케이션에서 코드 변경은 보통 즉시 반영이 힘들다.
이것은 예전 버전의 코드와 새로운 버전의 코드, 이전의 데이터 타입과 새로운 데이터 타입이 동시에 공존할 수 있다는 뜻이다.
시스템이 원활하게 실행되려면 양방향으로 호환성 유지 필요하다.
하위 호환성 = 새로운 코드는 예전 코드가 기록한 데이터를 읽을 수 있어야 한다.
상위 호환성 = 예전 코드는 새로운 코드가 기록한 데이터를 읽을 수 있어야 한다.

2. 데이터 부호화 형식

프로그램은 보통 두 가지 형태로 표현된 데이터를 사용한다.
메모리에 객체, 구조체, 배열, 해시 테이블 등으로 데이터를 유지
데이터를 파일에 쓰거나 네트워크를 통해 전송하기 위해서는 바이트열의 형태로 부호화 필요
인메모리 표현에서 바이트열로의 전환을 부호화(직렬화 또는 마샬링), 반대를 복호화라 한다.

2.1. 언어별 형식

프로그래밍 언어에 내장된 부호화 라이브러리는 편리하지만 문제점도 많다.
부호화는 보통 특정 프로그래밍 언어와 묶여 있어 다른 언어에서 데이터를 읽기가 매우 어렵다.
동일한 객체 유형의 데이터를 복원하려면 복호화 과정이 임의의 클래스를 인스턴스화 할 수 있어야 한다.
보안문제 발생 원인
공격자가 임의의 바이트열을 복호화할 수 있는 애플리케이션을 확보하게 되면 클래스를 인스턴스화 할 수 있고 공격자가 원격으로 임의 코드를 실행할 수 있게 됨
데이터의 버전 관리가 어렵고 효율성 또한 떨어진다.
부호화나 복호화에 소요되는 CPU시간과 부호화된 구조체 크기 등이 문제가 될 수 있음
자바의 내장 직렬화는 성능이 좋지 않음

2.2. JSON과 XML, 이진 변형

많은 프로그래밍 언어에서 읽고 쓸 수 있는 표준화된 부호화로서 JSON과 XML은 확실한 경쟁자다.
JSON과 XML은 널리 알려져 있고 많은 곳에서 지원하지만 그만큼 싫어하기도 한다.
JSON, XML, CSV는 텍스트 형식이기 때문에 어느 정도 사람이 읽을 수 있지만 피상적인 문법적 문제 이외에 일부 미묘한 문제가 있다.
수(number)의 부호화에는 많은 애매함이 있다.
XML과 CSV에서는 수와 숫자(digit)로 구성된 문자열을 구분 할 수 없다
JSON은 문자열과 수를 구분하지만 정수와 부동소수점 수를 구별하지 않고 정밀도를 지정하지 않는다.
이 애매함은 큰 수를 다룰 때 문제가 된다.
JSON과 XML은 유니코드 문자열을 잘 지원하지만 이진 문자열을 지원하지 않아 BASE64를 이용해 문제를 회피한다.
이런 결점에도 JSON, XML, CSV는 다양한 용도에 사용하기에 충분하다.

2.3. 이진 부호화

조직 내에서만 사용하는 데이터라면 최소공통분모 부호화 형식을 사용해야 하는 부담감이 덜하다.
작은 데이터의 경우 부호화 형식 선택으로 얻는 이득이 무시할 정도자만 테라바이트 정도가 되면 데이터 타입의 선택이 큰 영향을 미친다.
JSON은 XML보다 덜 장황하지만 이진 형식과 비교하면 둘 다 훨씬 많은 공간을 사용한다.

2.4. 스리프트와 프로토콜 버퍼

스리프트 컴팩트프로토콜
프로토콜 버퍼
아파치 스리프트와 프로토콜 버퍼는 같은 원리를 기반으로 한 이진 부보화 라이브러리다.
스리프트와 프로토콜 버퍼 모두 부호화할 데이터를 위한 스키마가 필요하다.
스리프트와 프로토콜 버퍼는 스키마 정의를 사용해 코드를 생성하는 도구가 있다.
애플리케이션 코드는 생성된 코드를 호출해 스키마의 레코드를 부호화/복호화할 수 있다.

2.5. 아브로

아브로
아파치 아브로는 프로토콜 버퍼와 스리프트와는 다르지만 이들과 대적할 만한 또 하나의 이진 부보화 형식이다.
아브로에는 두 개의 스키마 언어가 있다.
사람이 편집할 수 있는 아브로 IDL
record Person { string userName; union{null, long} favoriteNumber = null; array<string> interests; }
ABAP
복사
기계가 더 쉽게 읽을 수 있는 JSON기반 언어
{ "type": "record", "name": "Person", "fields": [ {"name": "userName", "type": "string"}, {"name": "favoriteNumber", "type": ["null", "long"], "default": null}, {"name": "userName", "type": {"type": "array", "items": "string"}} ] }
JSON
복사
스키마 태그 번호가 없고 이진 부호화 길이는 32 바이트로 모든 부호화 중 길이가 가장 짧다.
바이트열을 살펴보면 필드나 데이터 타입을 식별하기 위한 정보가 없음을 알 수 있다.
바이트열을 살펴보면 데이터타입을 식별하기 위한 정보가 없고 단순히 연결된 값으로 구성된다.
정수는 가변 길이 부호화를 사용해 부호화된다.
아브로를 통해 이진 데이터를 파싱하려면 스키마 순서대로 필드를 살피고 스키마를 이용해 필드의 데이터타입을 파악해야 한다.
데이터 기록 코드와 정확히 같은 스키마를 사용하는 경우에만 올바르게 복호화 가능
쓰기와 읽기간 스키마 불일치가 발생한다면 어찌 해결할 것인가

2.5.1. 쓰기 스키마와 읽기 스키마

데이터 전송 목적의 스키마를 쓰기 스키마라 하고, 수신 데이터 사용 목적의 스키마를 읽기 스키마라 한다.
아브로는 쓰기와 읽기 스키마가 동일하지 않아도 호환 가능하다.
필드가 포함되어 있지 않는 경우 기본 값으로 채운다.

2.5.2. 스키마 발전 규칙

아브로의 상위 호환성 = 새로운 버전의 쓰기와 예전 버전의 읽기 스키마를 가질 수 있다.
아브로의 하위 호환성 = 새로운 버전의 읽기와 예전 버전의 쓰기 스키마를 기질 수 있다.
아브로는 Null을 기본값으로 허용하지 않기 때문에 기본값이 지정되지 않을 경우 호환성이 깨질 수 있다.
필드에 Null을 허용하려면 유니온 타입을 사용해야 한다.
필드 이름 변경은 하위 호환은 가능, 상위 호환은 불가

2.5.3. 그렇다면 쓰기 스키마는 무엇인가?

많은 레코드가 있는 대용량 파일
아브로의 일반적인 용도(특히 하둡을 사용한다는 맥락에서)는 모두 동일한 스키마로 부호화된 수백만 개 레코드를 포함한 큰 파일을 저장하는 용도다.
이 경우 파일의 쓰기는 파일의 시작 부분에 한번만 쓰기 스키마를 포함 시키면 된다.
개별적으로 기록된 레코드를 가진 데이터베이스
데이터베이스의 다양한 레코드들은 다양한 쓰기 스키마를 사용해 서로 다른 시점에 쓰여질 수 있다.
즉 모든 레코드가 동일한 스키마를 가진다고 가정할 수 없다.
가장 간단한 해결책으로 모든 부호화된 레코드의 시작 부분에 버전번호를 포함하고 데이터베이스에는 스키마 버전목록을 유지한다.
읽기는 레코드를 가져와 버전번호를 추출한 다음 데이터베이스에서 버전번호에 해당하는 쓰기 스키마를가져온다.
가져온 쓰기 스키마를 사용해 남은 레코드를 복호화할 수 있다.
네트워크 연결을 통해 레코드 보내기
두 프로세스가 양방향 네트워크 연결을 통해 통신할때 연결 설정에서 스키마 버전합의를 할 수 있다.
이후 연결을 유지하는 동안 합의된 스키마를 사용한다.
아브로 RPC 프로토콜이 이 처럼 동작한다.

2.5.4. 동적 생성 스키마

프로토콜 버퍼와 스리프트에 비해 아브로는 한가지 장점을 가지는데 이는 스키마에 태그 번호가 포함돼 있지 않다는 점이다.
이 차이는 아브로가 동적 생성 스키마에 더 친숙하기 때문이다.
아브로는 관계형 스키마로 부터 손쉽게 스키마를 생성할 수 있고 이를 통해 DB 내용을 부호화하고 객체 컨테이너 파일로 덤프할 수 있다.
DB 스키마의 갱신에도 발빠르게 대응할 수 있다.
이에 반해 스리프트나 프로토콜 버퍼는 필드 태그를 수동으로 할당해야만 한기 때문에 번거롭고 실수가 발생할 수 있다.

2.5.5. 코드 생성과 동적 타입 언어

스리프트와 프로토콜 버퍼는 코드 생성에 의존하여 프로그래밍 언어별 코드를 생성할 수 있다.
빌드 타임이 없는 언어의 경우 코드 생성은 못마땅 하게 여겨지기도 하고, 코드 생성은 데이터를 가져오는데 불필요한 장애물이다.
아브로는 정적/동적 타입 프로그래밍 언어에 따라 코드 생성을 선택적으로 제공하지만 코드 생성 없이 객체 컨테이너 만으로 JSON 파일을 보는 것과 같이 사용할 수 있다.
이 파일은 필요한 메타데이터를 모두 포함하기 때문에 자기 기술(self-describing)적이다.
이 특성은 아파치 피그와 같은 동적 타입 언어와 함께 사용할때 특히 유용하다.

2.5.6. 스키마의 장점

아브로는 여타 이진 부보화 형식 기술과 같이 스키마를 사용한다.
스키마는 XML, JSON 스키마보다 간편하고, 유효성 검사 규칙을 지원한다.
많은 데이터 시스템이 이진 부보화를 독자적으로 구현하기도 하는데 DB는 질의를 보내고 응답을 받을 수 있는 네트워크 프로토콜이 있다.
해당 프로토콜을 통해 인메모리 데이터 구조로 복호화 하는 드라이버를 제공한다.
이진 부호화에는 좋은 속성이 많이 있다.
부호화된 데이터에서 필드 이름을 생략할 수 있기 때문에 다양한 이진JSON 변형보다 크기가 훨씬 작을 수 있다.
스키마는 유용한 문서화 형식으로 복호화를 할 때 스키마가 필요하기 때문에 스키마가 최신 상태인지를 확신할 수 있다.
스키마 데이터베이스를 유지하면 스키마 변경이 적용되기전에 상위 호환성과 하위 호환성을 확인할 수 있다.
정적 타입 프로그래밍언어 사용자에게 스키마로부터 코드를 생성하는 기능은 유용하다.
컴파일 시점에 타입 체크를할 수 있기 때문이다.

3. 데이터플로 모드

데이터플로는 매우 추상적인 개념으로 하나의 프로세스에서 다른 프로세스로 데이터를 전달하는 보편적인 방법을 살펴본다.

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

데이터베이스에 기록하는 프로세스는 데이터를 부호화하고 읽는 프로세스는 데이터를 복호화 한다.
일반적으로 동시에 다양한 프로세스가 DB에 접근하는 일은 흔하고 다양한 일들을 수행 중일 것이다.
이것은 DB 내 값이 새로운 버전의 코드로 기록된 다음 수행 중인 예전 버전의 코드로 그 값을 읽을 가능성이 있다는 의미다.
따라서 상위 호환성이 필요하다.
레코드 스키마에 필드를 추가하고 새로운 코드는 새로운 필드를 위한 값을 DB에 기록한다면 일반적으로 예전 코드가 해석할 수 없더라도 새로운 필드를 유지하는 것이다.

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

DB는 일반적으로 언제나 값을 갱신할 수 있고 해당 갱신은 언제 이뤄졌을지 알 수 없다.
APP의 새로운 버전을 배포할 때 몇 분 내에 예전 버전을 새로운 버전으로 완전히 대체 가능하지만 DB는 그렇지 못하다.
한번 기록된 데이터는 명시적으로 다시 기록하지 않는 한 원래 부호화를 유지하고 이런 상황을 데이터가 코드보다 더 오래 산다라고 한다.
데이터를 새로운 스키마로 다시 기록(마이그레이션)하는 작업은 가능하지만 값비싼 작업이기 때문에 대부분 이런 상황을 회피한다.
일반적인 RDB는 Null을 기본값으로 칼럼의 추가를 허용하기 때문에 스키마 발전은 기본 저장소가 여러 가지 버전의 스키마로 부호화된 레코드를 포함해도 전체 DB가 단일 스키마로 부호화된 것처럼 보이게 한다.

3.1.2. 보관 저장소

백업/데이터 웨어하우스로 적재하기 위한 스냅숏을 수시로 만든다 가정한다면 데이터의 복사본을 일관되게 부호화 하는 편이 좋다.

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

네트워크를 통해 배치하는 방법으로는 일반적으로 클라이언트와 서버의 두 역할로 배치한다.
서버의 API와 동의를 통해 다양한 전송 프로토콜을 이용할 수 있다.
하나의 서비스가 다른 서비스의 일부 기능이나 데이터가 필요하다면 요청을 보낸다.
전통적으로 서비스 지향 설계(SOA)로 불렸지만 최근엔 마이크로서비스(MSA) 설계란 이름으로 재탄생 했다.
서버와 클라이언트간 데이터 부호화는 API의 버전 간 호환이 가능해야 한다.

3.2.1. 웹 서비스

서비스와 통신하기 위한 기본 프로토콜을 HTTP로 이용할 때 이를 웹서비스라 한다.
일반적인 예시
사용자 디바이스에서 실행하며 HTTP를 통해 서비스에 요청하는 클라이언트 애플리케이션.
보통 이 요청은 공공 인터넷을 통해 전달된다.
서비스 지향/마이크로서비스 아키텍처의 일부로서 대개 같은 데이터센터에 위치한 같은 조직의 다른 서비스에 요청하는 서비스(미들웨어).
보통 인터넷을 통해 다른 조직의 서비스에 요청하는 서비스.
이것은 다른 조직의 백엔드 시스템간 데이터 교환을 위해 사용한다.
이 범주에는 신용카드 처리 시스템같은 온라인 서비스가 제공하는 공개AP나 사용자 데이터의 공유 접근을 위한 OAuth가 포함된다.
일반적인 웹 서비스는 REST와 SOAP 방식이 있으며 서로 정반대의 입장이다.

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

웹 서비스는 네트워크 상에서 API 요청을 하기 위한 여러 기술 중 가장 최신 형상일 뿐이다.
엔터프라이즈 자바빈과 RMI는 자바로 제한되고, 분산 컴포넌트 모델(DCOM)은 소프트 플랫폼으로, 공통 객체 요청 브로커 설계(CORBA)는 복잡하고 상위/하위 호환을 지원하지 않는다.
앞서 이야기한 모델은 원격 프로시저 모델의 아이디어를 기반으로 한다.
RPC가 편리한 것 같지만 RPC 접근 방식은 근본적으로 결함이 있다.
네트워크 문제로 요청/응답이 유실되거나 느려질 수 있고 이런 문제를 제어할 수 없다.
RPC 문제점
로컬 함수 호출은 결과를 반환 하거나 예외를 내거나 반환하지 않을 수 있다.
네트워크 요청은 또 다른 결과가 가능하다.
네트워크 요청은 타임아웃(timeout)으로 결과 없이 반환될 수 있다.
실패한 네트워크 요청을 다시 시도할 때 요청이 실제로는 처리되고 응답만 유실될 수 있다.
이 경우 프로토콜에 중복제거 기법을 적용하지 않으면 재시도는 작업이 여러번 수행되는 원인이 된다.
로컬 함수를 호출할 때마다 보통 거의 같은 실행시간이 소요된다.
네트워크 요청은 함수 호출보다 훨씬 느리고 지연시간은 매우 다양하다.
로컬 함수를 호출하는 경우 참조를 로컬 메모리의 객체에 효율적으로 전달할 수 있다.
모두 부호화 해서 전달해야 하기 때문에 큰 객체라면 즉시 문제가 될 수 있다.
클라이언트와 서비스는 다른 프로그래밍 언어로 구현할 수 있다.
프로그래밍 언어별로 타입의 호환성을 맞춰주어야 한다.

3.2.3. RPC의 현재 방향

여러 문제점이 있지만 RPC는 여전히 사라지지 않고 사용되고 있다.
차세대 RPC 프레임워크는 원격 요청이 로컬 함수 호출과 다르다는 사실을 더욱 분명히 한다.
프레임워크 중 일부는 서비스 찾기를 제공한다.
REST에 비해 이진 부호화 형식을 사용하는 사용자 정의 RPC 프로토콜이 우수한 성능을 제공할지 모르지만 REST는 디버깅에 적합한 이점을 가진다.
RPC 프레임워크의 주요 초점은 같은 데이터센터 내의 같은 조직이 소유한 서비스 간 요청에 있다.

3.2.4. 데이터 부호화와 RPC의 발전

발전성이 있으려면 RPC 클라이언트와 서버를 독립적으로 변경하고 배포할 수 있어야 한다.
RPC가 종종 조직 경계를 넘나드는 통신에 사용된다는 사실은 서비스 호환성 유지를 더욱 어렵게 한다.
호환성을 깨는 변경이 필요하면 서비스 제공자는 여러 버전의 API를 함께 유지해야 한다.

3.3. 메시지 전달 데이터플로

메시지 브로커 사용하면 직접 네트워크 연결로 전송하지 않고 임시로 메시지를 저장하는 중간단계를 거쳐 전송한다.
메시지 브로커의 장점
수신자가 사용 불가능하거나 과부하 상태라면 메시지 브로커가 버퍼처럼 동작할 수 있기 때문에 시스템 안정성이 향상된다.
죽었던 프로세스에 메시지를 다시 전달할 수 있기 때문에 메시지 유실을 방지할 수 있다.
송신자가 수신자의 IP주소나 포트번호를 알 필요가 없다.
하나의 메시지를 여러 수신자로 전송할 수 있다.
논리적으로 송신자는 수신자와 분리된다.

3.3.1. 메시지 브로커

최근 래빗MQ, 액티브MQ, 호닛Q, 나츠, 아파치 카프카 같은 오프소스 구현이 대중화 되었다.
세부적인 전달 시맨틱은 구현과 설정에 따라 다양하다.
프로세스 하나가 메시지를 이름이 지정된 큐/토픽으로 전송하고 브로커는 해당 토픽 하나 이상의 소비자/구독자에게 메시지를 전달한다.
동일한 토픽에 여러 생산자와 소비자가 있을 수 있다.
토픽은 단방향만 지원하며 소비자는 원하면 다른 토픽으로 응답을 게시할 수 있다.
소비자가 다른 토픽으로 메시지를 다시 게시한다면 예전 데이터를 생신하는 중 데이터 유실이 발생할 수 있으므로 주의해야 한다.

3.3.2. 분산 액터 프레임워크

액터 모델은 단일 프로세스 안에서 동시성을 위한 프로그래밍 모델이다.
스레드를 직접 처리하는 대신 로직이 액터에 캡슐화하고 액터는 하나의 클라이언트/엔티티를 나타낸다.
액터는 메시지 전달을 보장하지 않고, 한 번에 하나의 메시지만 처리하기 때문에 스레드 걱정없이 프레임워크 독립적으로 실행 가능
분산 액터 프레임워크는 여러 노드 간의 애플리케이션 확장에 사용된다.
송수신자가 어떤 노드에 있는지 관계 없이 동일한 메시지 전달 구조를 사용한다.
액터 모델을 사용한 경우 로컬과 원격 통신 간 근본적 불일치가 적다.
액터 기반은 예전 버전을 수행하는 노드로 전송하거나 반대의 경우도 있을 수 있으므로 상하위 호환성에 주의해야 한다.