📖 오늘 읽은 범위
4장. 부호화와 발전
만물은 변한다. 그대로 있는 것은 아무것도 없다.
- 대부분 애플리케이션 기능을 변경하려면 저장하는 데이터도 변경 해야 함
- 데이터 타입이나 스키마가 변경될 때 애플리케이션 코드에 대한 변경이 발생함. 하지만 대규모 애플리케이션에서 코드 변경은 보통 즉시 반영이 힘듬
- 이것은 예전 버전의 코드와 새로운 버전의 코드, 이전의 데이터 타입과 새로운 데이터 타입이 동시에 공존할 수 있다는 뜻
- 시스템이 원활하게 실행되려면 양방향으로 호환성 유지 필요
- 하위 호환성: 새로운 코드는 예전 코드가 기록한 데이터를 읽을 수 있어야 한다.
- 상위 호환성: 예전 코드는 새로운 코드가 기록한 데이터를 읽을 수 있어야 한다.
- 하위 호환성을 일반적으로 어렵지 않지만 상위 호환성은 예전 버전의 코드가 새 버전의 코드에 의해 추가된 것을 부시할 수 있어야 하므로 어려움
데이터 부호화 형식
- 프로그램은 보통 두 가 지 형태로 표현된 데이터를 사용
- 메모리에 객체, 구조체, 배열, 해시 테이블 등으로 데이터를 유지
- 데이터를 파일에 쓰거나 네트워크를 통해 전송하기 위해서는 바이트열의 형태로 부호화 필요
- 인메모리 표현에서 바이트열로의 전환을 부호화(직렬화 또는 마샬링)라고 함
언어별 형식
- 프로그래밍 언어에 내장된 부호화 라이브러리는 편리하지만 문제점도 많음
- 부호화는 보통 특정 프로그래밍 언어와 묶여 있어 다른 언어에서 데이터를 읽기가 매우 어려움
- 동일한 객체 유형의 데이터를 복원하려면 복호화 과정이 임의의 클래스를 인스턴스화 할 수 있어야 함
- 보안문제 발생 원인
- 공격자가 임의의 바이트열을 복호화할 수 있는 애플리케이션을 확보하게 되면 클래스를 인스턴스화 할 수 있고 공격자가 원격으로 임의 코드를 실행할 수 있게 됨
- 데이터의 버전 관리의 어려움
- 효율성 문제
- 부호화나 복호화에 소요되는 CPU시간과 부호화된 구조체 크기 등이 문제가 될 수 있음
- 자바의 내장 직렬화는 성능이 좋지 않음
JSON과 XML, 이진 변형
- XML과 CSV는 수와 숫자로 구성된 문자열을 구분할 수 없음
- JSON은 문자열과 수를 구분하지만 정수와 부동소수점을 구별하지 않고 정밀도를 지정하지 않음
- 이 문제는 큰 수를 다룰 때 문제가 됨
- JSON, XML은 유니코드 문자열을 지원하지만, 이진 문자열을 지원하지 않기 때문에 Base64를 사용해 텍스트로 부호화 가능
- CSV는 스키마가 없으므로 칼럼의 의미를 애플리케이션에서 직접 처리해야 함
스리프트와 프로토콜 버퍼
- 아파치 스리프트(Apache Thrift)와 프로토콜 버퍼(Protocol Buffers)는 이진 부호화 라이브러리
- 두 라이브러리 모두 부호화할 데이터를 위한 스키마가 필요
- 스리프트는 바이너리 프로토콜과 컴팩트 프로토콜 방식으로 이진 부호화를 진행함
아브로(Avro)
- 2009년 하둡의 하위 프로젝트로 시작
- 부호화할 데이터 구조를 지정하기 위해 스키마를 사용
- 두개의 스키마 언어가 존재
- 아브로 IDL(Avro IDL): 사람이 편집할 수 있음
- JSON 기반 언어: 기계가 더 쉽게 읽을 수 있음
- 이진 데이터를 파싱하려면 스키마에 나타난 순서대로 필드를 살펴보고 스키마를 이용해 각 필드의 데이터타입을 미리 파악해야 함
스키마의 장점
- 프로토콜 버퍼, 스리프트, 아브로는 XML, JSON 스크마에 비해 훨씬 간단하고 더 자세한 유효성 검사 규칙을 지원
- 이진 부호화에는 좋은 속석이 많음
- 부호화된 데이터에서 필드 이름을 생략이 가능해서 이진JSON 변형보다 크기가 훨씬 작음
- 복호화를 할 때 스키마가 필요하기 때문에 스키마가 최신 상태인지를 확인 가능
- 스키마 데이터베이스를 유지하면 스키마 변경이 적용되기 전에 상위 호환성과 하위 호환성을 확인 가능
- 정적 타입 프로그래밍 언어에서 컴파일 시점에 타입체크를 할 수 있기 때문에 스키마에서 코드를 생성하는 기능은 유용
데이터플로 모드
- 하나의 프로세스에서 다른 프로세스로 데이터를 전달하는 여러가지 방법
데이터베이스를 통한 데이터플로
- 데이터를 부호화하고 데이터베이스에서 읽는 프로세스는 데이터를 복호화함
- 대용량 데이터를 마이그레이션 하는 작업은 비싼 비용의 작업이기 때문에 대부분의 관계형 데이터베이스는 기존 데이터를 다시 기록하지 않고 null을 기본값으로 갖는 새로운 칼럼을 추가함
- 백업 목적이나 데이터 웨어하우스 저장 목적의 데이터 덤프는 복사본을 일관되게 부호화 하는 편이 나음
서비스를 통한 데이터플로: REST와 RPC
- 일반적인 방법으로는 서버-클라이언트 방식
- 서버가 공개한 API를 서비스 라고 부름
- 웹 브라우저만 클라이언트가 아니라 서버 자체가 다른 서비스의 클라이언트 일 수 있음
- 이러한 애플리케이션 개발 방식을 마이크로서비스 설계(microservices architecture, MSA)라고 부름
- MSA의 핵심 설계 목표는 서비스를 배포와 변경에 독립적으로 만들어 애플리케이션의 변경과 유지보수를 더욱 쉽게 만드는 것
- REST란?
- REST는 프로토콜이 아니라 HTTP의 원칙을 토대로 한 설계 철학
- 간단한 데이터 타입을 강조하며 URL을 사용해 리소스를 식별하고 캐시 제어, 인증, 콘텐츠 유형 협상에 HTTP 기능을 사용
- REST 원칙에 따라 설계된 API를 RESTful 이라고 부름
원격 프로시저 호출(RPC)
- RPC(remote procedure call) 모델은 원격 네트워크 서비스 요청을 같은 프로세스 안에서 특정 프로그래밍 언어의 함수나 메서드를 호출하는 것과 동일하게 사용 가능
- 편리한 것 같지만 근본적인 결함이 존재
- 로컬 함수 호출은 결과를 반환하거나 예외를 내거나 반환하지 않을 수 있음
- 하지만 네트워크 요청은 타임아웃으로 결과 없이 반환될 수 있음
- 실패한 요청을 retry 할때 실제로는 처리되고 응답만 유실 될 가능성 존재
- 이 경우 프로토콜에 멱등성을 적용하지 않으면 retry가 여러 번 수행될 수 있음
- 네트워크 요청은 로컬 함수 호출에 비해 훨씬 느림
- 로컬 함수 호출의 경우 포인터를 로컬 메모리 객체에 효율적으로 전달 가능한 반면 네트워크 요청은 모든 매개변수는 네트워크를 통해 전송해야 하기 때문에 바이트열로 부호화를 해야함
- 클라이언트와 서비스는 다른 프로그래밍 언어로 구현 가능하므로 RPC 프로임워크는 하나의 언어에서 다른 언어로 데이터타입을 변환해야 함
- RPC 프레임워크의 주요 목적은 같은 데이터센터 내의 같은 조직이 소유한 서비스 간 요청
비동기 메시지 전달 시스템
- 수신자가 사용 불가능하거나 과부하 상태일 경우 메시지 브로커가 버퍼처럼 동작할 수 있기 때문에 시스템 안정성이 향상
- 장애가 발생했던 수신자에 다시 메시지를 전달함으로써 메시지 유실 방지
- 송신자가 수신자의 IP나 포트를 몰라도 됨
- 가상 장비를 이용하는 클라우드 시스템에서 유용)
- 하나의 메시지를 여러 수신자로 전송 가능
- 논리적으로 송신자와 수신자는 분리됨
- 송신자는 메시지를 게시(publish)할 뿐이고 누가 소비(consume)하는지 상관하지 않음
- 일반적으로 단방향이라는 점이 RPC와의 차이점
- 메시지 브로커는 보통 특정 데이터 모델을 강요하지 않음
분산 액터 프레임워크
- 액터 모델(actor model)은 단일 프로세스 안에서 동시성을 위한 프로그래밍 모델
- 스레드(경쟁 조건, locking, deadlock과 연관된 문제들)를 직접 처리하는 대신 로직이 액터에 캡슐화 됨
- 보통 각 액터는 하나의 클러이언트나 엔티티를 뜻함
- 액터는 로컬 상태를 가질 수 있고 비동기 메시지의 송수신으로 다른 액터와 통신
- 각 액터 프로세스는 한번에 하나의 메시지만 처리하기 때문에 thread-safe함
- 분산 액터 프레임워크는 메시지 브로커와 액터 프로그래밍 모델을 단일 프레임워크에 통합
- 단, 액터 기반 애플리케이션의 rolling-upgrade 를 수행하려면 메시지가 새로운 버전을 수행하는 노드에서 예전 버전을 수행하는 노드로 전송할 수 있으므로 상하위 호환성에 주의해야 함
- 분산 액터 프레임워크의 예로는 Akka, Orleans, erlang 이 존재