4장 부호화와 발전
어플리케이션의 변경과 변화에 대응하는 방법을 소개하는 장. 일단 먼저 대규모 어플리케이션 변화에 있어 장애물이 되는 부분은 크게 아래와같다.
- 데이터타입이나 스키마가 변경되는 경우 서버측에서는 순회식 업그레이드를 진행한다.
- 클라이언트측 어플리케이션은 사용자에 전적으로 좌우된다. 어떤 사용자는 한동안 업그레이드를 하지 않을 수도 있다.
즉 예전버전의 코드와 새로운 버전의 코드, 이전의 데이터타입과 새로운 데이터타입 이 시스템에 공존 할 수 있다는 것이다. 그래서 시스템에는 양방향의 호환성이 필요하다.
- 하위 호환성 : 새로운 코드는 이전 코드가 기록한 데이터를 읽을 수 있어야 한다.
- 상위 호환성 : 이전 코드는 새로운 코드가 기록한 데이터를 읽을 수 있어야 한다.
데이터 부호화 형식
데이터는 크게 두가지 형식으로 다뤄진다.
- 메모리에서 : 데이터는 메모리 위에서 표현되며, CPU가 효율적으로 읽고 처리할 수 있도록 구조화되고 최적화된다.
- 디스크나 네트워크에서 : 데이터는 바이트 스트림으로 저장되며, 이는 다양한 시스템에서 호환되도록 한다.
그래서 이 두가지 형식에서 일련의 전환이 필요한데 그것을 부호화라고 한다. (일반적으로 직렬화라고 더 많이 하지만 트랜잭션에서 동일한 용어가 사용되어 부호화로 표현)
언어별 부호화 형식
- 기본적으로 언어별로 부호화 하는 기능들이 존재한다.
- 다만 이러한 부호화 형식은 언어별로 다르기 때문에 서로 호환되지 않고, 보안 이슈가 발생 할 수 있으며, 특정 언어는 성능이슈도 있다 (Java의 직렬화)
JSON, XML, 그리고 이진 변형
- JSON, XML은 가장 많이 사용되는 부호화 형식이다.
- 싫어하는 사람만큼 좋아하는 사람도 많다.
- 텍스트 형식이기 때문에 사람이 어느 정도 읽을 수 있고, JSON같은경우는 사용처가 압도적으로 많으며 쉽다는 장점이 있다.
- 다만 아쉬운점도 여러가지가 있는데, 대표적으로
- 수와 숫자로 구성된 문자열을 구분 할 수 없으며, 부동소수점의 정확도가 떨어진다.
- 유니코드 문자열은 지원하지만, 이진 문자열은 지원하지 않는다.
이진 부호화 형식
- 이러한 단점을 보완하기 위해 이진 부호화 형식이 등장했다.
{
"userName": "Martin",
"favoriteNumber": 1337,
"interests": ["daydreaming", "hacking"]
}
- 위와 같은 JSON(81바이트)을 이진 부호화 형식으로 변환하면 아래와 같다.
03 A8 75 73 65 72 4E 61 6D 65 06 4D 61 72 74 69 6E 0D 66 61 76 6F 72 69 74 65 4E 75 6D 62 65 72 04 0D
간단하게 타입 정의로 데이터를 표현해서 15 바이트를 줄였다.
스리프트와 프로토콜 버퍼
스리프트와 프로토컬 버퍼는 기본적으로 스키마를 정의한다.
message Person {
1: required string userName;
2: optional i32 favoriteNumber;
3: repeated string interests;
}
이 상태로 데이터를 직렬화 하는데, 방식이 두가지가 있다. (바이너리 프로토콜, 컴팩트 프로토콜)
-
단순히 스키마를 기반으로 필드 이름을 태그로 표현해서 절약하는 방식.
-
여기서 컴팩트 프로토콜은 필드태그나 숫자 데이터 형식까지 단일 바이트 가변길이 정수로 인코딩한다.
필드 태그와 스키마 발전
- 그렇다면 스키마가 변경되는 경우에 어떻게 상위호환성과 하위호환성을 유지할 수 있을까? 결론은 문제가 없다.
- 부호화된 레코드는 부호화된 필드의 연결일 뿐이다.
- 새로운 필드가 추가되면 이전 버전의 코드는 새로운 필드를 무시하고, 버퍼를 건너뛰면 된다.
- 반대로 새로운 코드가 이전 버전의 레코드를 읽을 때는 태그 번호가 같은 의미를 유지하기 때문에 문제가 없다.
- 다만 추가되는 필드가 required인 경우에는 문제가 발생하기에 이런 경우에는 optional 또는 default 값을 사용해야 한다.
- 필드를 삭제하는 경우도 마찬가지인데, 이 경우에는 optional필드만 삭제가 가능하고, 기존 태그번호는 변경하지 않는다.
데이터 타입과 스키마 발전
- 이건 그냥 32비트 정수를 64비트 정수로 바꾸는 경우를 생각해보면 간단한데, 이 경우에는 하위호환성이 보장되지 않는다. (새로운 코드는 이전 버전의 데이터를 읽지만, 이전 버전의 코드는 새로운 데이터를 읽을 수 없다.)
아브로
아브로는 스리프트와 다른 하둡의 부호화 형식이다.
아브로의 예제 스키마는 아래와 같다.
{
"type": "record",
"name": "Person",
"fields": [
{"name": "userName", "type": "string"},
{"name": "favoriteNumber", "type": ["int", "null"]},
{"name": "interests", "type": {"type": "array", "items": "string"}}
]
}
- 아브로는 태그 번호를 사용하지 않고, 스키마의 순서에 의존한다.
- 이러한 방식의 문제점은 읽는코드와 쓰는 코드가 정확히 같은 스키마를 사용해야만 복호화 할 수 있다는 것이다.
- 이러한 문제점을 아브로는 Avro Reader라는 계층을 두고 쓰기와 읽기 스키마를 분리해서 Avro Reader가 읽기스키마에 맞게 데이터를 변환해준다.
- 더 정확히는 Avro Reader는 쓰기 스키마와 읽기 스키마를 비교해서 변환을 해준다.
- 먼저 쓰기 스키마에 있는데 읽기 스키마에는 없는 필드는 무시한다.
- 반대로 쓰기 스키마에 없는데 읽기 스키마에 있는 필드는 기본값을 사용한다.
스키마 발전 규칙
아브로에서 상위 호환성은 새로운 버전의 쓰기 스키마와 예전 버전의 읽기 스키마를 가질 수 있음을 의미하고, 하위 호환성은 예전 버전의 쓰기 스키마와 새로운 버전의 읽기 스키마를 가질 수 있음을 의미한다.
- 기본값이 있는 필드만 추가하거나 삭제 할 수 있다. (새로운 필드를 가진 읽기스키마는 예전 데이터를 만나면 기본값으로 채워넣으면 그만)
- 기본값이 없는 필드를 추가하면 새로운 읽기는 예전 쓰기가 기록한 데이터를 읽을 수 없다.
- 기본값이 없는 필드를 삭제하면 예전 읽기 스키마는 새로운 쓰기 스키마가 기록한 데이터를 읽을 수 없다.
- null 대신 union을 이용한다
()
- 필드의 타입을 변경할 수 있다. (int -> long) (스키마의 타입 변경을 지원)
- 필드의 이름을 변경할 수 있다. (근데 별칭을 통해서 하위 호환성을 유지할 수 있는 방식으로 지원하기에, 상위 호환성은 없다)
그러면 쓰기 스키마는 무엇인가?
근데 대충 얼버무렸지만, 쓰기 스키마를 언제 제공할건데?
- 먼저 모든 레코드에 쓰기 스키마를 포함하는건 말이 안된다.
- 그래서 사실 방법에 따라서 다르게 제공한다.
- 많은 레코드가 있는 대용량 파일 -> 파일의 시작에 쓰기 스키마를 저장한다.
- 개별적으로 기록된 레코드를 가진 데이터베이스 -> 데이터베이스에 스키마 버전 목록을 저장한다, 그리고 레코드마다 스키마 버전을 저장한다.
- 네트워크 연결 -> 클라이언트와 서버가 합의해서 스키마를 공유한다. (진짜 프로토콜이네!)
동적 생성 스키마
프로토콜 버퍼와 스리프트에 비해 아브로 방식은 한 가지 장점이 있따. 스키마에 태그번호가 포함돼 있지 않다는 것이다. 이 차이는 아브로가 동적 생성 스키마에 더 친숙하다는 것을 의미한다.
- 아브로에서는 스키마가 변경되면, 갱신된 새로운 아브로 스키마를 생성하고 새로운 아브로 스키마로 데이터를 내보낸다.
- 데이터 내보내는 과정은 스키마 변경에 신경 쓸 필요가 없다.
- 그리고 이렇게 나온 데이터는 필드가 이름으로 식별되기 때문에, 여전히 이전 읽기 스키마로 읽을 수 있다.
- 스리프트나 프로토콜 버퍼는 관리자가 스키마 변경될 때마다 태그의 매핑을 수동으로 갱신해야 한다.
코드 생성과 동적 타입 언어
- 스리프트와 프로토콜 버퍼는 코드 생성에 의존한다. (코드 생성을 통해 스키마를 사용하는 코드를 생성한다.)
- 정적 타입 언어에서는 코드 생성이 매우 효과적이다.
- 문제는 동적 타입언어는 타입체크가 안되기도 하고 명시적 컴파일 단계가 없기 때문에 코드 생성이 어렵다.
- 아브로에서는 코드생성을 선택적으로 사용한다.
아브로에서는 쓰기 스키마를 포함한 객체 컨테이너 파일이 있다면 아브로 라이브러리를 사용해 간단히 열어 JSON파일을 보는 것 과 같이 데이터를 볼 수 있지만, 메타 데이터를 요구하기 때문에 자기 기술적이다.
스키마의 장점
- 요약하자면 장점은 부호화를 통한 데이터 절약과 호환성이다.
많은 데이터 베이스 시스템은 이진 부호화를 독자적으로 구현하기도 한다. 예를 들어 대부분의 관계형 데이터베이스에는 질의를 데이터베이스로 보내고 응답을 받을 수 있는 네트워크 프로토콜이 이따. 이 프로토콜은 일반적으로 특정 데이터베이스에 특화되고 데이터베이스 벤더는 데이터베이스 네트워크 프로토콜로부터 응답을 받아 인메모리 데이터 구조로 복호화 하는 드라이버를 제공한다.(JDBC, ODBC)
데이터 플로 모드
데이터 플로우는 매우 추상적인 개념으로서 하나의 프로세스에서 다른 프로세스로 데이터를 전달하는 방법은 아주 많다. 누가 데이터를 부호화하고 누가 그것을 복호화 할까? 이번장의 나머지 부분에서는 프로세스간 데이터를 전달하는 가장 보편적인 방법을 살펴본다.
- 데이터베이스를 통해
- RPC를 통해(서비스 호출을 통해)
- 비동기 메세지 전달을 통해