저장소와 검색

‘데이터 중심 애플리케이션 설계’를 읽고 정리하고자함


데이터 부호화 형식

  • 프로그램은 보통 최소한 두가지 형태로 표현된 데이터를 사용해 동작함
  • 메모리에 object, struct, list, array, hash table, tree 등으로 데이터를 유지함
  • 이 데이터 구조는 CPU에서 효율적으로 접근하고 조작할 수 있게 pointer를 통해 최적화함

  • 데이터를 파일에 쓰거나, 네트워크를 통해 전송하려면 스스로를 포함한 일련의 바이트열의 형태로 인코딩되어야함
  • 포인터는 다른 프로세스가 이해할 수 없으므로, 이 일련의 바이트열은 보통 메모리에서 사용하는 데이터 구조와는 다름

  • 두 가지 표현 사이에 일종의 변환관계가 필요함
    • 부호화(직렬화/마샬링)
      • 인메모리 표현에서 바이트열로의 전환
    • 복화화(파싱/역직렬화/언마샬링)
      • 바이트열을 인메모리 표현으로 전환
  • 데이터 전환을 위한 다양한 라이브러리와 부호화형식 이 있음

  • 많은 프로그래밍 언어는 인메모리 객체를 바이트열로 부호화하는 기능을 내장함
    • 자바 java.io.Serializable
    • 루비 Marshal
    • 파이썬 pickle
    • 서드파티 kryo 등등

프로그래밍 언어에 내장된 부호화 라이브러리의 문제점

  1. 부호화는 특정 프로그래밍 언어와 묶여, 다른 언어에서 데이터를 읽기가 어려움
  2. 동일한 객체 유형의 데이터를 복원하려면, 복호화 과정이 임의의 클래스를 인스턴스화할 수 있어야 함
    • 보안문제 발생 가능
    • 공격자가 임의의 바이트열을 복호화할 수 있는 애플리케이션을 얻느다면 임의의 클래스를 인스턴스화해 원격으로 코드를 실행시킬 수 있음
  3. 데이터 버전 관리 측면의 어려움
    • 데이터를 쉽게 빠르게 부호화하기 위해 상위, 하위 호환성의 불편할 수 있음
  4. 효율성
    • 부호화나 복호화에 소요되는 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가지 다른 이진 부호화 형식이 있음
    1. 바이너리 프로토콜 (BinaryProtocol)
    2. 컴팩트 프로토콜 (CompactProtocol)

아브로(Avro)

  • 아브로는 스리프트가 하둡 사용 사례에 적합하지 않아 2009년의 하둡 하위프로젝트로 시작
  • 아브로로 부호화활 데이터 구조를 지정하기위해 스키마가 필요하고, 두 개의 스키마 언어 존재
    1. Avro IDL
    2. 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를 가지지 않음

쓰기 스키마란?

  • 읽기는 특정 데이터를 부호화한 쓰기 스키마를 어떻게 알 수 있을까?
  • 모든 레코드에 전체 스키마를 포함? 스키마가 부호화한 데이터보다 더 클 수 있고, 그러면 이진 부호화에서 얻는 절약된 공간이 의미 없음
  1. 많은 레코드가 있는 대용량 파일
    • 아브로의 일반적인 용도는 모두 동일한 스키마로 부호화된 수백만 개 레코드를 포함한 큰 파일을 저장하는 용도
    • 파일의 시작부분에 한번만 쓰기 스키마를 포함시키면 가능
    • 이를 위해 아브로는 파일 형식, 객체 컨테이너 파일(object container file)을 명시함
  2. 개별적으로 기록된 레코드를 가진 데이터베이스
    • 모든 부호화된 레코드의 시작부분에 버전 번호를 포함하고 데이터베이스에는 스키마 버전 목록을 유지
    • 읽기는 레코드를 가져와 버전 정보를 추출하고 데이터베이스에서 쓰기 스키마를 가져옴
  3. 네트워크 연결을 통해 레코드 보내기
    • 두 프로세스가 양방향 네트워크 연결을 통해 통신할 때, 연결 설정에서 스키마 버전을 헙의할 수 있음
    • 연결을 유지하는 동안 합의된 스키마를 사용 (아브로 RPC 프로토콜이 이렇게 작동)

동적 생성 스키마

  • 스키마에 태그 번호가 포함되어 있지 않음
    • 스키마에 몇 개의 숫자가 있다 한들 무슨 문제가 될까?
  • 태그 번호가 없어 동적 생성 스키마에 더 친숙함

e.g) 관계형 데이터베이스의 덤프 데이터를 이진 형식으로 사용한다고 가정

  • 아브로를 사용하면 관계형 스키마로부터 아브로 스키마를 생성할 수 있음
  • 이 스키마를 이용해, 데이터베이스 내용을 부호화하고, 아브로 객체 컨테이너 파일로 모두 덤프함 (object container file)
  • 각 데이터베이스 테이블에 맞게 레코드 스키마를 생성하고 각 칼럼은 해당 레코드의 필드가 됌
  • 데이터베이스의 칼럼 이름은 아브로의 필드 이름으로 매핑
  • 여기서 데이터베이스 스키마가 바뀐다면..?
  • 갱신된 데이터베이스 스키마로부터 새로운 아브로 스키마 생성하고 새로운 아브로 스키마로 데이터를 보냄
  • 데이터를 내보내는 과정은 스키마 변경에 신경 쓸 필요 없음 (그저, 스키마 변경이 실행될 때마다 간단하게 수행)
  • 새로운 데이터를 읽은 사람은 레코드 필드가 변경된 것을 알지만, 필드는 이름으로 식별되기 때문에 갱신된 쓰기 스키마는
  • 여전히 이전 읽기 쓰미가와 매치가 가능함
  • 스리프트나 프로토콜 버퍼를 이렇게 사용하려면 수동으로 필드 태그를 할당해야함

스키마의 장점

  • JSON, XML, CSV 같은 텍스트 타입이 널리 사용
    • 하지만 스리프트, 프로토콜 버퍼, 아브로 등 스키마 언어는 XML,JSON 스키마보다 간단하고 자세한 유효성 감사 규칙 지원

하지만, 스키마 기반으로 한 이진 부호화의 좋은 속성이 많음

  1. 부호화된 데이터에서 필드 이름을 생략할 수 있기 때문에 ‘이진 JSON’ 변형보다 크기가 훨씬 작을 수 있음
  2. 스키마 데이터베이스를 유지하면, 스키마 변경이 적용도기 전에 상위, 하위 호환성을 확인할 수 있음
  3. 정적 타입 프로그래밍 언어에 있어 스키마로부터 코드를 생성하는 기능이 유용함
    • 컴파일 타임에 타입 체크할 수 있으니깐

데이터플로 모드

  • 메모리를 공유하지 않은 다른 프로세스로 일부 데이터를 보내려면, 네트워크를 통해 또는 파일에 기록할 때는 ‘바이트열로 부호화’ 해야함
  • 프로세스 간 데이터를 전달하는 보편적인 방법에 대해 알아봄
    1. 데이터베이스를 통해
    2. 서비스호출을 통해 (REST와 RPC)
    3. 비동기 메세지 전달을 통해
  • 개념 정의
    • 네트워크를 통해 통신해야하는 프로세스 있을 때는 해당 통신을 배치하는 방법 중 일반적이 방법이 있음
    • 일반적인 방법이 바로, 클라이언트와 서버 두 역할로 배치하는 것
    • 서버는 네트워크를 통해 API를 공개하고
    • 클라이언트는 이 API로 요청을 만들어 서버에 연결할 수 있음
    • 이렇게 서버가 공개한 API를 ‘서비스’라고 함
    • 서비스와 통신하기 위한 기본 프로토콜이 HTTP를 사용하면 이를 ‘웹 서비스’라고 함
    • REST는 프로토콜이 아닌 HTTP의 원칙으로 설계한 철학
    • REST 원칙에 따라 설계된 API를 RESTful이라고 함

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

  • RPC 모델은 원격 네트워크 서비스 요청을 같은 프로세스 안에서 특정 프로그래밍 언어의 함수나 메소드를 통해 호출하는 것과 동일하게 사용하게 해줌
    • 이런 추상화 개념을 ‘위치 투명성(location transparency)’
  • 하지만, 네트워크 요청은 로컬 함수 호출과 다르게, 예측하기가 어려움
    • 네트워크 문제로 요청과 응답이 유실되고 장비가 느려 요청에 응답하지 않을 수 도 있음

문제

  1. 네트워크 요청은 timeout으로 결과없이 반환될 수 있음
    • 이 경우, 응답을 못 받은 것인지, 요청을 제대로 보낸 것인지 무슨 경우인지 알기 어려움
  2. 실패한 네트워크 요청을 다시 시도할 때, 요청이 실제로는 처리되고 응답만 유실될 수 있음
    • 프로토콜에 중복 제거, 멱등성을 적용하지 않으면 재시도는 같은 작업을 여러 번 함
  3. 네트워크 요청은 로컬 함수보다 훨씬 느리고 지연시간이 다양함
    • 로컬 함수는 호출할 때마다 같은 실행시간이 소요됌
  4. 네트워크 요청하는 경우에는 필요한 모든 매개변수를 네트워크를 통해 전송할 수 있게 바이트열로 부호화해야함
    • 로컬함수를 호출하는 경우에는 포인터로 로컬 메모리의 객체를 효율적으로 전달할 수 있음
  5. 클라이언트와 서비스는 다른 프로그래밍 언어로 구현되어 있을 수 있음
    • 따라서 RPC프레임워크는 하나의 언어에서 다른 언어로 데이터 타입을 변환해야함