[Data-Intensive] Ch4. 부호화와 발전
by Jaesang Lim˚
저장소와 검색
‘데이터 중심 애플리케이션 설계’를 읽고 정리하고자함
데이터 부호화 형식
- 프로그램은 보통 최소한 두가지 형태로 표현된 데이터를 사용해 동작함
- 메모리에 object, struct, list, array, hash table, tree 등으로 데이터를 유지함
-
이 데이터 구조는 CPU에서 효율적으로 접근하고 조작할 수 있게 pointer를 통해 최적화함
- 데이터를 파일에 쓰거나, 네트워크를 통해 전송하려면 스스로를 포함한 일련의 바이트열의 형태로 인코딩되어야함
-
포인터는 다른 프로세스가 이해할 수 없으므로, 이 일련의 바이트열은 보통 메모리에서 사용하는 데이터 구조와는 다름
- 두 가지 표현 사이에 일종의 변환관계가 필요함
- 부호화(직렬화/마샬링)
- 인메모리 표현에서 바이트열로의 전환
- 복화화(파싱/역직렬화/언마샬링)
- 바이트열을 인메모리 표현으로 전환
- 부호화(직렬화/마샬링)
-
데이터 전환을 위한 다양한 라이브러리와 부호화형식 이 있음
- 많은 프로그래밍 언어는 인메모리 객체를 바이트열로 부호화하는 기능을 내장함
- 자바 java.io.Serializable
- 루비 Marshal
- 파이썬 pickle
- 서드파티 kryo 등등
프로그래밍 언어에 내장된 부호화 라이브러리의 문제점
- 부호화는 특정 프로그래밍 언어와 묶여, 다른 언어에서 데이터를 읽기가 어려움
- 동일한 객체 유형의 데이터를 복원하려면, 복호화 과정이 임의의 클래스를 인스턴스화할 수 있어야 함
- 보안문제 발생 가능
- 공격자가 임의의 바이트열을 복호화할 수 있는 애플리케이션을 얻느다면 임의의 클래스를 인스턴스화해 원격으로 코드를 실행시킬 수 있음
- 데이터 버전 관리 측면의 어려움
- 데이터를 쉽게 빠르게 부호화하기 위해 상위, 하위 호환성의 불편할 수 있음
- 효율성
- 부호화나 복호화에 소요되는 CPU시간과 부호화된 구조체 크기 등이 문제가 될 수 있음
- 자바의 내장 직렬화는 성능이 좋지 않고 비대해지는 문제가 있음k
JSON과 XML, 이진 변형
- JSON은 수,number의 부호화에 있어 애매함이 있음
- 문자열과 수를 구분하지만 정수와 부동소수점 수를 구별하지 않고 정밀도를 지원하지 않음
- 큰 수를 다룰 때 문제가 될 수 있음
- JSON과 XML은 유니코드 문자열, 즉 사람이 읽을 수 있는 텍스트를 잘 지원함
- 그러나 이진 문자열, binary 문자 부호화가 없는 바이트 열을 지원하지 않음
- 이진 데이터를 Base64를 사용해 텍스트로 부호화해서 사용할 수 있음
- CSV는 스키마가 없어 로우와 컬럼의 의미를 정의하는 작업은 애플리케이션에서 해야함
이진 부호화
- 테라바이트 정도의 데이터가 되면 데이터 타입의 선택이 큰 영향을 줌
- JSON 가능한 이진 부호화 (binary encoding)
- MessagaPack, BSON, BJSON, UBJSON, BISON, Smile 등
- XML 가능한 이진 부호화
- WBXML. Fast Infoset
- 이진 부호화는 작은 공간의 절약이 사람의 가독성을 해칠만큼 가치가 있는지는 확실하지 않음
스리프트(Apache Thrift)와 프로토콜 버퍼(Protocol Buffer)
- 이진 부호화 라이브러리
- 프로토콜 버퍼는 구글, 스리프트는 페이스북에서 개발
-
두 개 모두 부호화할 데이터를 위한 스키마가 필요
- 스리프트는 2가지 다른 이진 부호화 형식이 있음
- 바이너리 프로토콜 (BinaryProtocol)
- 컴팩트 프로토콜 (CompactProtocol)
아브로(Avro)
- 아브로는 스리프트가 하둡 사용 사례에 적합하지 않아 2009년의 하둡 하위프로젝트로 시작
- 아브로로 부호화활 데이터 구조를 지정하기위해 스키마가 필요하고, 두 개의 스키마 언어 존재
- Avro IDL
- JSON기반 언어
- 아브로를 이용해 이진데이터를 파싱하려면 스키마에 나타난 순서대로 필드를 살펴보고 스키마를 이용해 각 필드의 데이터타입을 미리 파악해야함
- 데이터를 읽는 코드가 데이터를 기록한 코드와 정확히 같은 스키마를 사용하는 경우에만 이진 데이터를 올바르게 복호화할 수 있다는 의미
쓰기 스키마와 읽기 스키마
- 쓰기 스키마(writer’s schema)
- 해당 스키마를 애플리케이션에 포함하는 것
- 읽기 스키마(reader’s schema)
- 읽은 데이터를 복호화하길 원한다면 데이터가 특정 스키마로 복호화하길 기대
- 이 스키마를 읽기 스키마라 하고, 애플리케이션 코드는 이 스키마에 의존
- 복호화 코드는 애플리케이션을 빌드하는 동안 스키마로부터 생성
- 아브로의 핵심 아이디어
- 쓰기 스키마와 읽기 스키마가 동일하지 않아도 되며, 단지 호환 가능하기만 하면 된다는 것
- 데이터를 읽을 때, 아브로 라이브러리는 쓰기 스키마와 읽기 스키마를 함께 살펴본 후, 쓰기 스키마에서 읽기 스키마로 변환해 그 차이를 해소함
e.g) 읽기 스키마와 쓰기 스키마는 필드 순서가 달라도 문제없음
- 스키마 해석(Schema evolution)에서는 이름으로 필드를 일치시킴
- 데이터를 읽는 코드가 읽기 스키마에는 없고, 쓰기 스키마에 존재하는 필드를 만나면 이 필드를 무시함
- 데이터를 읽는 코드가 기대하는 어떤 필드가 쓰기 스키마에 없을 시, 읽기 스키마에 선언된 기본값으로 채움
스키마 발전 규칙
- 필드에 null을 허용하려면 유니온 타입(union type)을 사용해야함
- union {null, long, string} field;
- field는 수나 문자열 또는 null 일 수 있다는 뜻
- 기본적으로 null을 갖게하는 것 보다는 장황하지만 null일 수 있는 것, null일 수 없는 것을 명확히 구분 가능
- 프로토콜 버퍼, 스리프트와 같이 optional, required를 가지지 않음
쓰기 스키마란?
- 읽기는 특정 데이터를 부호화한 쓰기 스키마를 어떻게 알 수 있을까?
- 모든 레코드에 전체 스키마를 포함? 스키마가 부호화한 데이터보다 더 클 수 있고, 그러면 이진 부호화에서 얻는 절약된 공간이 의미 없음
- 많은 레코드가 있는 대용량 파일
- 아브로의 일반적인 용도는 모두 동일한 스키마로 부호화된 수백만 개 레코드를 포함한 큰 파일을 저장하는 용도
- 파일의 시작부분에 한번만 쓰기 스키마를 포함시키면 가능
- 이를 위해 아브로는 파일 형식, 객체 컨테이너 파일(object container file)을 명시함
- 개별적으로 기록된 레코드를 가진 데이터베이스
- 모든 부호화된 레코드의 시작부분에 버전 번호를 포함하고 데이터베이스에는 스키마 버전 목록을 유지
- 읽기는 레코드를 가져와 버전 정보를 추출하고 데이터베이스에서 쓰기 스키마를 가져옴
- 네트워크 연결을 통해 레코드 보내기
- 두 프로세스가 양방향 네트워크 연결을 통해 통신할 때, 연결 설정에서 스키마 버전을 헙의할 수 있음
- 연결을 유지하는 동안 합의된 스키마를 사용 (아브로 RPC 프로토콜이 이렇게 작동)
동적 생성 스키마
- 스키마에 태그 번호가 포함되어 있지 않음
- 스키마에 몇 개의 숫자가 있다 한들 무슨 문제가 될까?
- 태그 번호가 없어 동적 생성 스키마에 더 친숙함
e.g) 관계형 데이터베이스의 덤프 데이터를 이진 형식으로 사용한다고 가정
- 아브로를 사용하면 관계형 스키마로부터 아브로 스키마를 생성할 수 있음
- 이 스키마를 이용해, 데이터베이스 내용을 부호화하고, 아브로 객체 컨테이너 파일로 모두 덤프함 (object container file)
- 각 데이터베이스 테이블에 맞게 레코드 스키마를 생성하고 각 칼럼은 해당 레코드의 필드가 됌
- 데이터베이스의 칼럼 이름은 아브로의 필드 이름으로 매핑
- 여기서 데이터베이스 스키마가 바뀐다면..?
- 갱신된 데이터베이스 스키마로부터 새로운 아브로 스키마 생성하고 새로운 아브로 스키마로 데이터를 보냄
- 데이터를 내보내는 과정은 스키마 변경에 신경 쓸 필요 없음 (그저, 스키마 변경이 실행될 때마다 간단하게 수행)
- 새로운 데이터를 읽은 사람은 레코드 필드가 변경된 것을 알지만, 필드는 이름으로 식별되기 때문에 갱신된 쓰기 스키마는
- 여전히 이전 읽기 쓰미가와 매치가 가능함
- 스리프트나 프로토콜 버퍼를 이렇게 사용하려면 수동으로 필드 태그를 할당해야함
스키마의 장점
- JSON, XML, CSV 같은 텍스트 타입이 널리 사용
- 하지만 스리프트, 프로토콜 버퍼, 아브로 등 스키마 언어는 XML,JSON 스키마보다 간단하고 자세한 유효성 감사 규칙 지원
하지만, 스키마 기반으로 한 이진 부호화의 좋은 속성이 많음
- 부호화된 데이터에서 필드 이름을 생략할 수 있기 때문에 ‘이진 JSON’ 변형보다 크기가 훨씬 작을 수 있음
- 스키마 데이터베이스를 유지하면, 스키마 변경이 적용도기 전에 상위, 하위 호환성을 확인할 수 있음
- 정적 타입 프로그래밍 언어에 있어 스키마로부터 코드를 생성하는 기능이 유용함
- 컴파일 타임에 타입 체크할 수 있으니깐
데이터플로 모드
- 메모리를 공유하지 않은 다른 프로세스로 일부 데이터를 보내려면, 네트워크를 통해 또는 파일에 기록할 때는 ‘바이트열로 부호화’ 해야함
- 프로세스 간 데이터를 전달하는 보편적인 방법에 대해 알아봄
- 데이터베이스를 통해
- 서비스호출을 통해 (REST와 RPC)
- 비동기 메세지 전달을 통해
- 개념 정의
- 네트워크를 통해 통신해야하는 프로세스 있을 때는 해당 통신을 배치하는 방법 중 일반적이 방법이 있음
- 일반적인 방법이 바로, 클라이언트와 서버 두 역할로 배치하는 것
- 서버는 네트워크를 통해 API를 공개하고
- 클라이언트는 이 API로 요청을 만들어 서버에 연결할 수 있음
- 이렇게 서버가 공개한 API를 ‘서비스’라고 함
- 서비스와 통신하기 위한 기본 프로토콜이 HTTP를 사용하면 이를 ‘웹 서비스’라고 함
- REST는 프로토콜이 아닌 HTTP의 원칙으로 설계한 철학
- REST 원칙에 따라 설계된 API를 RESTful이라고 함
원격 프로시져 호출(RPC) 문제
- RPC 모델은 원격 네트워크 서비스 요청을 같은 프로세스 안에서 특정 프로그래밍 언어의 함수나 메소드를 통해 호출하는 것과 동일하게 사용하게 해줌
- 이런 추상화 개념을 ‘위치 투명성(location transparency)’
- 하지만, 네트워크 요청은 로컬 함수 호출과 다르게, 예측하기가 어려움
- 네트워크 문제로 요청과 응답이 유실되고 장비가 느려 요청에 응답하지 않을 수 도 있음
문제
- 네트워크 요청은 timeout으로 결과없이 반환될 수 있음
- 이 경우, 응답을 못 받은 것인지, 요청을 제대로 보낸 것인지 무슨 경우인지 알기 어려움
- 실패한 네트워크 요청을 다시 시도할 때, 요청이 실제로는 처리되고 응답만 유실될 수 있음
- 프로토콜에 중복 제거, 멱등성을 적용하지 않으면 재시도는 같은 작업을 여러 번 함
- 네트워크 요청은 로컬 함수보다 훨씬 느리고 지연시간이 다양함
- 로컬 함수는 호출할 때마다 같은 실행시간이 소요됌
- 네트워크 요청하는 경우에는 필요한 모든 매개변수를 네트워크를 통해 전송할 수 있게 바이트열로 부호화해야함
- 로컬함수를 호출하는 경우에는 포인터로 로컬 메모리의 객체를 효율적으로 전달할 수 있음
- 클라이언트와 서비스는 다른 프로그래밍 언어로 구현되어 있을 수 있음
- 따라서 RPC프레임워크는 하나의 언어에서 다른 언어로 데이터 타입을 변환해야함
Subscribe via RSS