데이터 중심 애플리케이션 설계04
부호화와 발전
데이터 구조가 메모리 표현에서 바이트 시퀀스로 변환되는 방법과 그 역변환 방법을 다룸
인코딩과 디코딩
- 인코딩: 인메모리 데이터를 바이트 시퀀스로 변환하는 과정. 직렬화(serialization) 또는 마셜링(marshalling)이라고 부릅니다.
- 디코딩: 바이트 시퀀스를 다시 인메모리 표현으로 변환하는 과정으로 파싱(parsing), 역직렬화(deserialization), 또는 언마샬링(unmarshalling)이라고 부릅니다.
- 언어별 인코딩의 문제점: Java의 Serializable, Python의 pickle 등은 해당 언어에만 국한되며, 보안 문제와 버전 호환성 문제를 가집니다.
텍스트 기반 형식(JSON, XML, CSV)
- XML과 CSV에서는 외부 스키마 없이는 숫자와 문자열을 구분할 수 없습니다. JSON은 문자열과 숫자를 구분하지만, 정수와 부동 소수점 숫자, 정밀도에 이슈가 있습니다.
- 큰 숫자를 다룰 때 JSON이 문제가 되고, 이는 JavaScript와 같이 부동 소수점 숫자를 사용하는 언어에서 부정확하게 파싱됩니다.
- 바이너리 문자열 처리: JSON과 XML은 바이너리 문자열을 직접 지원하지 않아 Base64 인코딩을 사용해야 하며, 이는 데이터 크기를 33% 증가시킵니다.
- 스키마 지원: JSON Schema, XML Schema, CSV Schema 등이 있지만 선택사항이며, 많은 JSON 기반 도구에서 스키마를 사용하지 않습니다.
이진 인코딩
MessagePack, BSON 등
- JSON의 이진 버전들로, 공간 효율성은 개선되지만 스키마가 없다는 근본적 한계는 동일합니다.
- MessagePack: JSON 호환 이진 직렬화 형식
- BSON: MongoDB에서 사용하는 이진 JSON 확장
스키마 기반 이진 형식(Thrift, Protocol Buffers, Avro)
- 이 형식들은 동일한 예시 레코드를 압축된 형태로 인코딩할 수 있습니다.
- 컴팩트한 표현: 스키마를 통해 필드명을 생략하고 태그 번호나 순서를 사용하여 데이터 크기를 대폭 줄입니다.
Thrift와 Protocol Buffers
- 데이터 인코딩을 위해서 스키마를 사용합니다.
- required 및 optional 마커를 사용하는데, 런타임 검사에 사용되지만 인코딩 방식에는 영향을 미치지 않습니다.
- 하위 호환성: 새로운 태그 번호를 가진 필드를 추가해서 이전 버전의 코드가 무시할 수 있습니다.
- 상위 호환성: 이전 버전에서 작성된 데이터를 새 버전에서 읽을 수 있습니다.
- 필드 제거 시 주의사항: required 필드는 제거할 수 없으며, 태그 번호는 재사용하면 안 됩니다.
- 데이터 타입 변경: 일부 타입 변경은 가능하지만(int32 → int64), 정밀도 손실이나 값 손실 위험이 있습니다.
Avro
- 스키마를 사용하고, Avro IDL 또는 JSON 기반입니다.
- 인코딩된 데이터에는 필드를 식별하는 정보가 없고, 값들이 연결되어 있습니다. 어떤 값이 문자열인지 정수인지는 스키마를 통해 결정됩니다.
- 스키마 진화 규칙: reader 스키마와 writer 스키마 간의 호환성 차이를 해결합니다. 기본값이 있는 필드만 추가 및 제거해서, 이전 버전의 reader가 새로운 writer가 작성한 데이터를 읽을 수 없는 현상을 방지합니다.
- 동적 스키마 생성: 데이터베이스 스키마 변경에 따라 Avro 스키마를 자동으로 생성할 수 있어 더 유연합니다.
- 스키마 해상도: 필드는 이름으로 매칭되며, 별칭(alias) 기능을 통해 필드명 변경이 가능합니다.
스키마의 장점
- 효율적인 압축: 스키마가 있으면 필드명을 인코딩할 필요가 없어 공간 절약
- 스키마 진화: 하위/상위 호환성을 유지하면서 스키마 변경 가능
- 문서화: 스키마 자체가 문서 역할을 하며, 코드 생성 도구의 기반
- 정적 타입 체크: 컴파일 타임에 타입 안전성 확보
데이터플로우 모드
데이터베이스를 통한 데이터플로우
- 스키마 진화: 데이터베이스에 저장된 데이터는 여러 버전의 스키마로 작성될 수 있음
- 데이터 마이그레이션: 모든 데이터를 새 스키마로 변환하는 것은 비용이 크므로, 점진적 마이그레이션이 일반적
서비스를 통한 데이터플로우 - 웹 서비스
- 웹 서비스: HTTP를 기반 프로토콜로 사용합니다.
- REST: HTTP 원칙에 기반한 설계 철학입니다. 프로토콜이 아닙니다. 간단한 데이터 형식, URL을 통한 리소스 식별, HTTP 메서드 활용을 강조합니다. OpenAPI(구 Swagger)는 REST API의 문서화에 도움을 줍니다.
- SOAP: REST와 철학적으로 대조되는 방식으로, XML 기반의 프로토콜입니다. WSDL을 통한 스키마 정의를 지원합니다.
RPC (Remote Procedure Call)
- RPC의 개념: 원격 네트워크 서비스를 로컬 함수 호출처럼 보이게 하려는 시도입니다.
- RPC의 문제점:
- 네트워크 호출은 로컬 함수 호출과 근본적으로 다름 (지연시간, 실패 가능성)
- 네트워크 실패, 타임아웃, 재시도 등의 복잡성
- 매개변수 직렬화의 어려움
- 현대적 RPC 프레임워크: gRPC(Protocol Buffers 기반), Thrift, Avro RPC 등
- RPC vs REST: RPC는 액션 중심, REST는 리소스 중심의 설계 철학
비동기 메시지 전달
- 메시지 브로커: RabbitMQ, Apache Kafka, Amazon SQS 등
- 장점:
- 수신자가 사용 불가능해도 메시지 버퍼링
- 시스템 간 결합도 감소
- 확장성과 안정성 향상
- 메시지 패턴:
- 일대일 (큐)
- 일대다 (토픽/발행-구독)
- 분산 액터 프레임워크: Akka, Orleans, Erlang OTP 등에서 액터 모델 구현
스키마 진화와 호환성 요약
- 하위 호환성: 새 코드가 이전 코드로 작성된 데이터를 읽을 수 있음
- 상위 호환성: 이전 코드가 새 코드로 작성된 데이터를 읽을 수 있음
- 양방향 호환성: 하위/상위 호환성을 모두 만족
실제 적용시 고려사항
- 배포 전략: 롤링 업데이트 시 여러 버전의 코드가 동시에 실행될 수 있음
- 스키마 레지스트리: Confluent Schema Registry 등을 통한 중앙화된 스키마 관리
- 테스트: 서로 다른 스키마 버전 간의 호환성 테스트 필요
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.