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