Encoding and Evolution 데이터 부호화를 위한 다양한 형식으로 스키마가 변경되고 예전 버전과 새로운 버전의 데이터와 코드가 공존하는 시스템을 어떻게 지원하는지 알아보자.
- 하위 호환성: 새로운 코드는 예전 코드가 기록한 데이터를 읽을 수 있어야 한다.
- 상위 호환성: 에전 코드는 새로운 코드가 기록한 데이터를 읽을 수 있어야 한다.
1. 데이터 부호화 형식
- 프로그램은 최소한 두 가지 형태로 표현된 데이터를 사용해 동작
- 객체, 구조체, 목록, 배열, 해시, 트리 등등
- CPU에서 효율적으로 접근하고 조작할 수 있도록 최적화
- 데이터를 파일에 쓰거나 네트워크를 통해 전송하려면 바이트열 형태로 부호화 해야함
- 인 메모리 -> 바이트열: 부호화, 직렬화, 마샬링
- 바이트열 -> 인 메모리: 복호화, 역질렬화, 역마샬링, 파싱
- 바이트열 (바이트 순서 표식)
1.1. 언어별 형식
- 많은 프로그래밍 언어는 인 메모리 객체를 바이트열로 부호화하는 기능을 내장
- 자바: java.io.Serializable
- 루비: Marshal
- 파이썬: pickle
- 문제점
- 부호화는 특정 프로그래밍 언어와 묶여 있어 다른 언어에서 읽기 어려움
- 동일한 객체 유형의 데이터를 복원하려면 복호화 과정이 임의의 클래스를 인스턴스화할 수 있어야하는데 종종 보안 문제의 원인이 됨
- 데이터 버전 관리의 어려움
- 효율성
1.2. JSON과 XML, 이진 변형
- XML은 너무 장황하고 불필요하게 복잡하여 비판 받음
- JSON은 자바스크립트의 지원과 단순하여 많이 사용됨
- 둘다 텍스트 형식으로 사람이 읽을 수 있음
- 문제점
- 수(number) 부호화에 대한 애매함
- JSON은 정수와 부동소수점을 구별하지 않고 정밀도를 지정하지 않음
- 2^53 보다 큰 정수는 IEEE 754 배정도 수 에서 정확하게 표현할 수 없으므로 이런 수는 파싱할 때 부정확해질 수 있음
- 유니코드는 잘 지원하나 바이트는 지원하지 않음
- Base64 인코딩으로 제한을 피하는 방법이 있지만 정공법은 아니고 데이터 크기가 증가함
- 필수는 아니지만 스키마를 지원하지만 구현하기가 상당히 난해함
- 수(number) 부호화에 대한 애매함
1.3. 이진 부호화
- 조직 내에서만 사용하는 데이터라면 최소공통분모 부호화 형식을 사용해야하는 부담감이 덜함
- XML은 장황하고, JSON은 이진 형식에 비해 많은 공간을 사용하여 각 포맷에 사용가능한 이진 부호화가 개발됨
- JSON: 메시지팩, BSON, BJSON, UBJSON, BISON, 스마일 등
- XML: WBXML, 패스트 인포셋
- 틈새 시장에 채택되었으나 널리 채택되지는 않음
데이터타입 셋을 확장했지만 데이터 모델은 변경하지 않고 유지
- 부호화 대상 예제
1 2 3 4 5
{ "userName": "Martin", "favoritNumber": 1337, "interests": ["daydreaming", "hacking"] }
1.4. 스리프트와 프로토콜 버퍼
- 스리프트와 프로토콜 버퍼 모두 부호화할 데이터를 위한 스키마가 필요
- 스키마를 각 라이브러리에 해당하는 형식으로 작성하고 다양한 프로그래밍 언어로 변환한 클래스를 생성하여 어플리케이션에서 부호화/복호화가 가능
JSON을 이진 부호화한 메시지 팩보다 더 다양한 형식으로 부호화가 가능
- 스리프트
- 바이너리 프로토콜
- 59 바이트
- 필드 타입 주석
- 길이 주석
- 문자열 ASCII 또는 UTF-8 부호화
- 필드 이름은 없고 필드 태그
- 컴팩트 프로토콜
- 34 바이트
- 필드 타입과 태그 숫자를 바이트로 줄임
- 가변 길이 정수를 사용해서 부호화
- 1337: 8바이트 -> 2바이트
- 각 바이트의 상위 비트는 앞으로 더 많은 바이트가 있는지 나타냄
- -64 ~ 63: 1바이트 부호화
- -8192 ~ 8191: 2바이트 부호화
- 바이너리 프로토콜
- 프로토콜 버퍼
- 33 바이트
- 컴팩트 프로토콜과 비슷
- required, optional, repeated 등 키워드가 존재함
1.4.1. 필드 태그와 스키마 발전
- 각 필드는 태그 숫자로 식별하고 데이터 타입을 주석으로 작성
- 필드 값을 설정하지 않은 경우는 부호화 레코드에서 생략
- 부호화된 데이터는 필드 이름을 전혀 참조하지 않기 때문에 스키마에서 필드 이름은 변경할 수 있음
- 필드 태그는 모든 부호화된 데이터를 인식 불가능하게 만들 수 있기 때문에 변경할 수 없음
- 새로운 태그 번호를 부여하는 방식으로 새로운 필드 추가 가능
- 예전 코드에서 새로운 코드로 기록한 데이터를 읽으려는 경우 해당 필드를 무시할 수 있음
- 데이터 타입 주석은 파서가 몇 바이트를 건너뛸수 있는지 알려줘 상위 호환성을 유지함
- 각 필드에 고유한 태그 번호가 있으면 태그 번호가 계속 같은 의미를 가지고 있기 때문에 새로운 코드가 예전 데이터를 항상 읽을 수 있음
- 하위 호환성을 유지하려면 새로운 필드를 추가하면 optional로 하거나 기본값을 가져야함
- 필드를 삭제할 때는 상/하위 호완성을 위해 optional 필드만 삭제하고 같은 태그 번호를 사용하지 않음
1.4.2. 데이터타입과 스키마 발전
- 데이터 타입을 변경하는 것은 불가능하지는 않지만 값이 정확하지 않거나 잘릴 위험이 있음
- 32비트 정수에서 64비트 정수로 변경하면?
- 파서가 누락된 비트를 0으로 채울 수 있음
- 새로운 코드는 예전 코드가 기록한 데이터를 읽을 수 있음
- 새로운 코드가 기록한 데이터를 예전 코드가 읽는 경우 예전 코드는 값을 유지하기 위해 32비트 변수를 계속 사용하여 잘리게 됨
- 프로토콜 버퍼에는 목록이나 배열 데이터 타입이 없지만 repeated 표시자가 존재함
- 레코드에 동일한 필드 태그가 여러번 나타남
- optional 필드를 repeated 필드로 변경해도 문제가 없음
- 이진 데이터를 읽는 새로운 코드는 0~1개의 엘리먼트가 있는 목록으로 보게 됨
- 새로운 데이터를 읽는 예전 코드는 목록의 마지막 엘리먼트만 보게 됨
- 덮어 써지는 개념인듯
뭐가 멋진 건지는 진짜 모름
- 스리프트에는 전용 목록 데이터 타입이 있음
- 목록 엘리먼트의 데이터 타입을 매개변수로 받음
- 단일 값에서 다중 값으로 변경을 허용하지 않지만 중첩된 목록을 지원한다는 장점이 있음
1.5. 아브로
- 아브로도 부호화할 데이터 구조를 지정하기 위해 스키마 사용
- Avro IDL, JSON 기반
- 스키마에 태그 번호가 존재하지 않음
- 앞에 살펴본 모든 부호화 중 길이가 가장 짧음
- 데이터 타입을 식별하기 위한 정보가 없음
- 부호화는 단순히 연결된 값으로 구성됨
- 아브로를 이용해 이진 데이터를 파싱하려면 스키마에 나타난 순서대로 필드를 살펴보고 스키마를 이용해 각 필드의 데이터 타입을 미리 파악해야함
- 읽는 코드가 기록한 코드와 정확히 같은 스키마를 사용하는 경우에만 이진 데이터를 올바르게 복호화할 수 있음을 의미
- 읽기, 쓰기 스키마 불일치 시 복호화가 정확하지 않다는 의미
1.5.1. 쓰기 스키마와 읽기 스키마
- 쓰기 스키마: 데이터를 아브로로 부호화할 때 알고 있는 스키마 버전을 사용해 데이터를 부호화
- 읽기 스키마: 데이터를 복호화할 때 데이터가 특정 스키마로 복호화하길 기대
- 어플리케이션 코드는 스키마에 의존함
- 복호화 코드는 애플리케이션을 빌드하는 동안 스키마로부터 생성됨
- 핵심 아이디어
- 쓰기 스카마와 읽기 스키마가 동일하지 않아도 되며 호환만 가능하면 됨
- 쓰기/읽기 스키마를 살펴본 다음 쓰기 스키마에서 읽기 스키마로 데이터를 변환해 그 차이를 해소
- 필드 순서가 달라도 스키마 해석에서 이름으로 필드를 일치시키기 때문에 문제 없음
- 데이터를 읽을 때 읽기 스키마에는 없고 쓰기 스키마에 존재하는 필드를 만나면 무시함
- 읽기 스키마에 존재하지만 쓰기 스키마에 포함되어 있지 않은 필드는 읽기 스키마에 선언된 기본값으로 채움
1.5.2. 스키마 발전 규칙
- 아브로의 상위 호환성: 새로운 버전의 쓰기 스키마와 예전 버전의 읽기 스키마를 가질 수 있음
- 아브로의 하위 호환성: 새로운 버전의 읽기 스키마와 예전 버전의 쓰기 스키마를 가질 수 있음
- 호환성을 유지하기 위해서는 기본값이 있는 필드만 추가, 삭제 가능
- 새로운 스키마에 기본값이 있는 필드를 추가했다면 예전 스키마로 기록된 레코드를 읽으면 기본값으로 채워짐
- 기본값이 없는 필드를 추가하면 새로운 읽기는 에전 쓰기가 기록된 데이터를 읽을 수 없기 때문에 하위 호환성이 깨짐
- 기본값이 없는 필드를 삭제하면 예전 읽기는 새로운 쓰기가 기록된 데이터를 읽을 수 없기 때문에 상위 호환성이 깨짐
- 필드에 null을 허용하여면 유니온 타입을 사용해야 함
- 장황하지만 nullable, notnull을 명확하게 표현 가능
- 유니온 타입과 기본값 여부로 optional, required 표현 가능
- 필드의 데이터 타입 변경 가능
- 필드 이름도 변경 가능하지만 조금 까다로움
- 읽기 스키마는 필드 이름의 별칭을 포함할 수 있고 이를 통해 스키마 필드 이름을 매치할 수 있음
- 즉 필드 이름 변경은 하휘 호환성이 있지만 상위 호환성은 없다는 의미
- 유니온 타입 엘리먼트 추가하는 것도 하위 호환성은 있지만 상위 호환성은 없음
1.5.3. 그러면 쓰기 스키마는 무엇인가?
- 읽기는 특정 데이터를 부호화한 쓰기 스키마를 어떻게 알 수 있을까?
- 모든 레코드에 전체 스키마를 포함시킬 수 없음
- 스키마는 부호화된 데이터보다 훨씬 클 가능성이 있기 때문
- 예시
- 많은 레코드가 있는 대용량 파일
- 동일한 스키마로 부호화된 수백만 개 레코드를 포함한 큰 파일을 저장할 때 파일 시작 부분에 한 번만 쓰기 스키마를 포함 시킴
- 개별적으로 기록된 레코드를 가진 데이터베이스
- 부호화된 레코드의 시작 부분에 버전 번호를 포함하고 데이터베이스에 스키마 버전 목옥을 유지
- 레코드를 가져와 버전 번호를 추출하여 버전 번호에 해당하는 쓰기 스키마를 가져와서 남은 레코드를 복호화 함
- 네트워크 연결을 통해 레코드 보내기
- 연결 설정에서 스키마 버전 합의 가능
- 이후 연결을 유지하는 동안 합의된 스키마를 사용
- Avro RPC
- 많은 레코드가 있는 대용량 파일
1.5.4. 동적 생성 스키마
- 아브로 스키마는 스키마에 태그 번호가 포함되어 있지 않아 동적 생성 스키마에 더 친숙함
- 관계형 데이터베이스 데이터를 이진 형식으로 덤프한다면?
- 아브로 스키마를 JSON 형태로 쉽게 생성 가능
- 각 데이터베이스 테이블에 형태에 맞게 스키마를 생성하고 각 컬럼은 레코드의 필드가 됨
- 데이터베이스의 컬럼 이름은 아브로 필드 이름에 매핑
- 데이터베이스 스키마가 변경된다면 갱신된 데이터베이스 스키마로부터 새로운 아브로 스키마를 생성하고 새로운 아브로 스키마로 데이터를 내보냄
- 새로운 데이터를 읽는 사람은 레코드가 변경된 사실을 알게 되지만 필드 이름으로 식별되기 때문에 갱신된 쓰기 스키마는 여전히 읽기 스키마와 매칭 가능
- 스리프트나 프로토콜 버퍼를 사용한다면 필드 태그를 수동으로 할당해야함
- 데이터베이스 스키마가 변경되면 관리자는 데이터베이스 컬럼 이름과 필드 태그 매칭을 수동으로 갱신해야함
- 동적 생성이 스리프트, 프로토콜 버퍼의 설계 목표가 아니었음
1.5.5. 코드 생성과 동적 타입 언어
- 스리프트, 프로토콜 버퍼는 코드 생성에 의존
- 언어별 효율적인 인메모리 구조, IDE에서 타입 확인 및 자동 완성 가능
- 자바스크립트, 루비, 파이썬 등 동적 타입 언어는 명시적 컴파일 단계가 없기 때문에 코드 생성이 중요하지 않음 -> 좀 귀찮음
- 아브로는 정적 타입 언어를 위한 코드 생성을 선택적으로 제공
- 코드 생성을 하지 않더라도 객체 컨테이너 파일이 있다면 아브로 라이브러리를 사용해 JSON 파일처럼 데이터를 볼 수 있음
- 아파치 피그 같은 동적 타입 데이터 처리 언어와 함께 사용할 때 유용함
- 피그에서는 아브로 파일만 있으면 즉시 분석 가능
- 스키마를 생각하지 않고도 아브로 형식으로 출력 파일에 파생 데이터 기록 가능
1.6. 스키마의 장점
- 프로토콜 버퍼, 스리프트, 아브로 스키마는 XML 스키마나 JSON 스키마보다 간단하며 더 자세한 유효성 검사 규칙 지원 및 광범위한 프로그래밍 언어 지원
- 많은 데이터 시스템이 독자적인 이진 부호화 구현
- 관계형 데이터베이스: ODBC, JDBC API
- 필드 이름 생략 가능하여 크기가 훨씬 작을 수 있음
- 스키마는 유용한 문서화 형식
- 복호화 할 때 스키마가 필요하기 때문에 최신 상태인지 확신할 수 있음
- 스키마 데이터베이스를 유지하면 스키마 변경이 적용되기 전에 상위/하위 호환성을 확인 가능
- 정적 타입 프로그래밍 언어 사용자에게 스키마로부터 코드를 생성하는 기능은 유용함
- 컴파일 시점 타입 체크 가능
- 스키마를 잘 발전 시키면 스키마리스, 문서모델이 제공하는 것처럼 동일한 종류의 유연성 제공, 데이터나 도구 지원도 더 잘 보장됨
위와 같은 도구가 없다면…? http://sjava.net/2015/10/%EC%9E%90%EB%B0%94%EC%97%90%EC%84%9C-unsigned-%ED%83%80%EC%9E%85%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C%EC%9D%84-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0/
2. 데이터플로 모드
- 데이터플로는 매우 추상적인 개념으로서 하나의 프로세스에서 다른 프로세스로 데이터를 전달하는 방법은 아주 많은데 그 중 보편적인 방법을 살펴본다
2.1.데이터베이스를 통한 데이터플로
- 데이터베이스에 쓰는 데이터를 부호화하고 데이터베이스에서 읽는 데이터를 복호화 함
- 읽기는 동일 프로세스의 최신 버전
- 데이터베이스에 저장하는 일: 미래의 자신에게 메시지를 보내는 일
- 하위 호환성이 없으면 이전에 기록한 내용을 미래의 자신이 복호화 할 수 없음
- 애플리케이션이 롤링 배포 등으로 변경되는 환경이라면 데이터베이스의 값이 새로운 버전으로 쓰여지고 예전 코드로 읽을 가능성이 있기 때문에 상위 호환성도 필요함
- 레코드 스키마에 새로운 필드를 추가하고 새로운 코드는 새로운 필드를 위한 값을 데이터베이스에 쓰려고하면 여러 부호화 형식은 알지 못하는 필드 보존은 지원하지만 때로는 애플리케이션 차원에서 신경 써야하는 사레도 있음
- 애플리케이션에서 데이터베이스 값을 모델 객체로 복호화하고 나중에 이 모델 객체를 다시 재부호화 하면 알지 못하는 필드가 유실될 수 있음
2.1.1. 다양한 시점에 기록된 다양한 값
- 애플리케이션이 새로 배포하면 예전 버전을 새로운 버전으로 완전히 대체할 수 있지만 데이터베이스는 그렇지 않음
- 오래된 데이터는 그 이후로 명시적으로 다시 쓰지 않는 한 원래 부호화 상태로 있음
- 데이터가 코드보다 더 오래 산다 (Data outlives code)
- 데이터베이스 마이그레이션은 가능하지만 값비싼 작업이기 때문에 대부분 관계형 데이터베이스는 null을 기본값으로 갖는 새로운 컬럼을 추가하는 간단한 스키마 변경을 허용
- 링크드인 에스프레소 DB는 아브로를 사용함
- 스키마 발전은 기본 저장소가 여러 가지 버전의 스키마로 부호화된 레코드를 포함해도 전체 데이터베이스가 단일 스키마로 부호화된 것처럼 보이게 함
2.1.2. 보관 저장소
- 백업 목적이나 데이터 웨어하우스로 적재하기 위해 데이터베이스 스냅샷을 수시로 만든다면?
- 보통 최신 스키마를 사용해 부호화 해야함
- 데이터를 복사하기 때문에 데이터의 복사본을 일관되게 부호화하는 편이 나음
- 기록 후 변하지 않기 때문에 아브로 객체 컨테이너 파일 같은 형식이 적합
- 파케이와 같은 분석 친화적인 컬럼 지향 형식으로 데이터를 부호화할 좋은 기회
2.2. 서비스를 통한 데이터플로: REST와 RPC
- 서버는 네트워크를 통해 API를 공개하고 이를 서비스라 함
- 웹, 모바일 디바이스, 데스크톱 컴퓨터 등 서버에 네트워크를 요청할 수 있음
- http 프로토콜을 사용할 수 있지만 API는 애플리케이션마다 특화되어 있음
- 다른 서버가 다른 서비스의 클라이언트일 수 있음
- 서비스 지향 설계 (Service-Oriented Architecture, SOA)
- 마이크로서비스 설계
- 서비스 지향 및 마이크로서비스 설계의 핵심 목표는 서비스를 배포와 변경에 독립적으로 만들어 애플리케이션 변경과 유지보수를 더 쉽게 할 수 있게 만드는 것
- 각 서비스가 자주 새로운 버전을 출시(배포)할 수 있기 때문에 서버와 클라이언트가 사용하는 데이터 부호화는 서비스 API의 버전 간 호환이 가능해야함
2.2.1. 웹 서비스
- REST
- HTTP 원칙을 토대로 한 설계 철학
- 간단한 데이터 타입을 강조
- URL을 사용해 리소스 식별
- 캐시 제어, 인증, 콘텐츠 유형 협상에 HTTP 기능 사용
- REST 원칙에 따라 설계된 API: RESTful
- Swagger로 알려진 오픈 API 같은 정의 형식을 사용해 문서를 기술함
- SOAP
- XML 기반 프로토콜
- HTTP 상에서 사용되지만 HTTP와 독립적이며 대부분 HTTP 기능을 사용하지 않음
- 다양한 기능을 추가한 광범위하고 복잡한 여러 관련 표준 제공 (WS-** 이라고 알려진 웹 서비스 프레임워크)
- 웹 서비스 기술 언어 또는 WSDL(XML 기반 언어) 사용하여 기술
- 정적 타입 프로그래밍 언어에는 유용하지만 동적 타입 언어에는 유용성이 떨어짐
- WSDL은 사람이 읽을 수 있게 설계하지 않았고 SOAP를 수동으로 구성하기 복잡하기 때문에 도구 지원, 코드생성, IDE에 의존함
- SOAP 벤더가 지원하지 않는 프로그래밍 언어는 SOAP 서비스와 통합이 어려움
2.2.2. 원격 프로시저 호출(RPC) 문제
- 다양한 웹 서비스는 RPC 아이디어를 기반으로 함
- RPC 모델은 원격 네트워크 서비스 요청같은 프로세스 안에서 특정 프로그래밍 언어의 함수나 메서드를 호출하는 것과 동일하게 사용 가능하게 해줌 (위치 투명성: Location Transparency)
- 함수를 호출하는 것 처럼 보이지만 내부적으로는 네트워크 요청이므로 로컬 함수 호출과 다름
- 결함
- 네트워크 요청은 타임아웃으로 결과 없이 반환될 수 있음
- 원격 서비스로부터 응답을 받지 못한다면 요청을 제대로 보냈는지 아닌지 알 수 있는 방법이 없음
- 실패한 네트워크 요청을 다시 시도할 때 요청이 실제로 처리되고 응답만 유실될 수 있음
- 프로토콜에 멱등성을 적용하지 않으면 재시도가 여러번 수행되는 원인이 됨
- 네트워크 요청은 함수 호출보다 느리고 원격 서버 상황에 따라 지연 시간이 매우 다양함
- 네트워크 요청일 경우 모든 매개 변수를 바이트열로 부호화 해야함
- RPC 프레임워크는 하나의 언어에서 다른 언어로 데이터타입을 변환해야하는데 모든 언어가 같은 타입을 가지는 것이 아니기 때문에 깔끔하지 않은 모습이 될 수 있음
2.2.3. RPC 현재 방향
- 스리프트와 아브로는 RPC 지원 기능을 내장하고 있음
- gRPC는 프로토콜 버퍼를 이용한 RPC 구현
- 차세대 RPC 프레임워크는 원격 요청이 로컬 함수 호출과 다르다는 사실을 더욱 분명히 함
- 피네글, Rest.li: Future, Promise로 실패 가능한 비동기 작업 캡슐화
- gRPC: 하나의 요청과 하나의 응답뿐 아니라 시간에 따른 일련의 요청과 응답으로 구성된 스트림 지원
- 서비스 찾기 (Service Discovery) 제공
- 클라이언트가 특정 서비스를 찾을 수 있도록 IP 주소와 포트 번호 제공
- RPC 디버깅이 어려움
- 최근 많은 지원이 있지만 도구에 대한 의존성이 강함
2.2.4. 데이터 부호화와 RPC의 발전
- 발전성이 있으려면 RPC 클라이언트와 서버를 독립적으로 변경하고 배포가 가능해야함
- 모든 서버를 먼저 갱신하고 나서 모든 클라이언트를 갱신해도 문제가 없다고 가정함
- 요청: 하위 호환성만 필요
- 응답: 상위 호환성만 필요
- 스리프트, gRPC, Avro RPC는 각 부호화 형식의 호환성 규칙에 따라 발전할 수 있음
- SOAP에서 요청과 응답은 XML 스키마로 지정
- 발전 가능하지만 일부 미묘한 함정이 있음 (참고)
- RESTful은 JSON, form 형태를 요청/응답 형태로 사용
- 선택적 요청 매개변수 추가나 응답 객체의 새로운 필드 추가는 대게 호환성을 유지하는 변경으로 간주
2.3. 메시지 전달 데이터 플로
- 비동기 메시지 전달 시스템
- 클라이언트 요청(메시지)을 낮은 지연 시간으로 다른 프로세스에 전달한다는 점에서 RPC와 비슷
- 메시지를 직접 전송하지 않고 임시로 메시지 브로커, 메시지 지향 미들웨어라는 중간 단계를 거쳐 전송하는 점은 데이터베이스와 유사함
- 일반적으로 단방향 통신으로 응답을 기대하지 않음
- 응답을 전송하는 것은 가능하지만 별도 채널에서 수행
- 장점
- 수신자가 사용 불가능하거나 과부화 상태라도 메시지 브로커가 버퍼 처럼 동작하여 시스템 안정성 향상
- 죽었던 프로세스에 다시 메시지 전달이 가능하여 메시지 유실 방지 가능
- 송신자의 IP, 포트 등을 알 필요 없음
- 하나의 메시지를 여러 수신자로 전송 가능
- 논리적으로 송신자(producer), 수신자(consumer) 분리
2.3.1. 메시지 브로커
- 과거
- 팁코, IBM 웹스피어, 웹메소즈 처럼 상용 기업 소프트웨어가 우위를 차지함
- 최근
- RabbitMQ, ActiveMQ, HornetQ, NATS, Apache Kafka 등 오픈소스 구현 대중화
- 하나의 프로세스가 메시지를 큐/토픽으로 전송
- 브로커는 큐/토픽의 컨슈머/구독자에게 메시지를 전달
- 동일한 토픽에 여러 프로듀서와 컨슈머가 존재할 수 있음
- 토픽은 단방향 데이터플로만 제공
- 메시지 브로커는 특정 데이터 모델을 강요하지는 않음
- 메시지는 일부 메타 데이터를 가진 바이트열이므로 부호화 형식을 사용할 수 있음
- 부호화가 상하위 호환성을 모두 가진다면 프로듀서와 컨슈머를 독립적으로 변경해 배포할 수 있는 유연성을 갖게 됨
2.3.2. 분산 액터 프레임워크
- 액터 모델은 단일 프로세스 안에서 동시성을 지원하기 위한 프로그래밍 모델
- 스레드를 직접 처리하는 대신 로직이 액터에 캡슐화 함
- 각 액터는 하나의 클라이언트나 엔티티를 나타냄
- 액터는 로컬 상태를 가질 수 있고 비동기 메시지의 송수신으로 다른 액터와 통신함
- 액터는 메시지 전달을 보장하지 않음
- 에러에 따라 유실될 수 있음
- 엑터는 한 번에 하나의 메시지만 처리하기 때문에 스레드 걱정은 필요없고 각 액터는 프레임워크와 독립적으로 실행 가능
- 분산 액터 프레임워크를 사용하여 여러 노드 간의 애플리케이션 확장에 사용됨
- 다른 노드에 있는 경우 메시지는 바이트열로 부호화되고 네트워크를 통해 전송되며 다른쪽에서 복호화 함
- 액터 모델은 단일 프로세스 안에서도 메시지가 유실될 수 있다고 가정하고 있기 때문에 위치 투명성은 RPC 보다 더 잘 동작 함
- 분산 액터 프레임워크는 메시지 브로커와 액터 프로그래밍 모델을 단일 프레임워크에 통합
- 액터 기반 애플리케이션에서 롤링 배포를 수행하면 상하위 호환성에 주의해야함
- 프레임워크 비교
- 아카
- 자바 내장 직렬화를 사용하지만 상하위 호환성을 제공하지 않음
- 프로토콜 버퍼 같은 부호화 형식으로 대체 가능
- 올리언스
- 사용자 정의 데이터 부호화 형식을 사용하지만 호환성 제공하지 않음
- 사용자 정의 직렬화 플러그인 사용 가능
- 얼랭 OTP
- 레코드 스키마를 변경하는 일은 의외로 어려움
- 순회식 업그레이드가 가능하지만 신중하게 계획해야 함
- maps 데이터 타입은 롤링 배포를 더 쉽게 할 수 있게 만들 것
- 아카