DevSprout / data-oriented-architecture

데이터 중심 애플리케이션 설계
4 stars 0 forks source link

04장: 부호화와 발전 #4

Open MinJunKweon opened 8 months ago

MinJunKweon commented 8 months ago

끄적끄적

정리 ### Serialization이 필요한 이유 - 인메모리 상의 바이트를 그대로 파일이나 네트워크 통신에 사용하기 어렵기 때문 - e.g.) 포인터나 레퍼런스 같은 경우는 인메모리 상에서만 사용 가능하기 때문에 파일이나 네트워크 통신에서 사용하기 위해 변환 과정이 필요하다. ⇒ 부호화(Serialization) ### 프로그램 내장 Serialization을 쓰지 않는 걸 권장하는 이유 - 많은 프로그래밍 언어에서 직렬화/역직렬화 라이브러리를 내장하고 있음 - Java의 `java.io.Serializable`, Python의 `pickle`, Ruby의 `Marshal` - 하지만, 모종의 이유로 쓰지 않는 것을 권장함 - **너무 프로그래밍 언어에 종속적인 부호화는 다른 프로그램과의 호환성이 떨어질 수 있음** ⇒ 향후 마이그레이션이 어려울 수 있음. 또는 다른 프로그래밍 언어와 통신에 사용하기 어려움 - **보안 이슈가 발생할 수 있음** ⇒ 공격자가 임의의 바이트열을 복호화하여 인스턴스를 만들 수 있고, 원격 코드 실행이 이루어질 수 있음 - **데이터 버전 관리가 어려움** ⇒ 데이터 버전 관리에 대해 고려가 안되고 설계되었을 수 있음 - **효율성이 떨어질 수 있음** ⇒ CPU나 메모리 자원을 많이 사용할 수 있음 ## 많이 사용되고 있는 Serialization 방식 - JSON, XML, CSV 포맷이 보통 표준화되어 사용됨 - 각 데이터 포맷들은 각자 장단점을 가지고 있고, 문제점을 해결하기 위해 파생되는 직렬화 방식이 많음 - 이 포맷들의 대표적인 문제점 - **Number를 변환하는 과정에 많은 애매함이 있음** ⇒ 문자열과 구분하기 어렵거나 정수, 부동소수점을 구분하지 않는다거나 - **큰 수를 다룰 때 부정확 할 수 있음** ⇒ 부동소수점이나 큰 수를 표현하는 방식이 달라서 정확도가 떨어질 수 있음 - Twitter에서는 트윗 ID를 64bit unsigned bit로 표현하는데, 이 숫자를 역직렬화할 때 오버플로우가 발생하는 언어들이 몇개 있음 (e.g. JavaScript) - 트위터는 이 문제를 해결하기 위해 ID를 문자열로 만들어서 별개 필드로 넘겨줌 - **바이너리 직렬화에 대한 지원이 부족함** ⇒ 바이너리를 전송하려면 Base64 같이 Printable Character로 변환해서 전송해야함. 이는 크기가 33% 증가하는 오버헤드가 발생함 - **CSV는 스키마가 없고 로우와 컬럼의 의미를 파악해서 역직렬화하는 부분은 전부 애플리케이션의 책임** ⇒ 이스케이핑 규칙 같은 것들도 파서에서 지원을 안할 수 있음 ## 이진 부호화(Binary Serialization) - JSON, XML과 다르게 별개로 여러가지 이진 부호화 방식이 있음 - 사람이 알아보기 어렵다는 단점이 있지만, 그만큼 효율적으로 크기를 줄일 수 있음 ### Thrift와 Protocol Buffers - 둘다 메시지에 대한 스키마를 정의해야함 - 스키마 정의 언어는 IDL(Interface Definition Language)라고 부름 - https://en.wikipedia.org/wiki/Interface_description_language ```protobuf // Thift struct Person { 1: required string userName, 2: optional i64 favoriteNumber, 3: optional list interests } // protobuf message Person { required string user_name = 1; optional int64 favorite_number = 2; repeated string interested = 3; } ``` - Thift는 `BinaryProtocol`과 `CompactProtocol`로 나뉨 - 프로토콜 프로토콜은 메시지팩과 비슷하게 바이트열로 나눠서 필드와 값을 부호화함 - 컴팩트 프로토콜은 반복되는 정보를 줄여서 부호화함 - Protocol Buffers는 스리프트 컴팩트 프로토콜과 매우 비슷함 - `required`, `optional` 항목을 통해 포함되는 항목인지 아닌지 확인이 가능함 ## 스키마 발전 - 시간이 지남에 따라 스키마가 변경되면 하위 호환성과 상위 호환성을 보장해주어야함 - **하위 호환성** : 이전 메시지를 스키마가 지원하는지 여부 - **상위 호환성** : 새로운 스키마의 메시지를 읽을 수 있는지 여부 - 필드 태그를 통해 이 필드가 IDL에서 어떤 필드를 의미하는지 매핑할 수 있음 - 메시지 부호화할 때, 태그를 통해서 필드를 구분하기 때문에 필드명이 바뀌는 것은 호환성에 문제가 되지 않음 - 필드 삭제 시, `required` 필드가 삭제되면 호환성이 깨지므로 호환성을 안전하게 지킬 수 있음 - 데이터 타입 변경 시, 데이터가 잘리거나 다르게 인식되는 이슈가 발생할 수 있음 ## Avro - Avro는 스키마 언어가 2가지 - **Avro IDL** : 사람이 읽고 편집할 수 있음 - **JSON 기반 언어** : 기계가 더 쉽게 읽을 수 있음 - **스키마에 태그 번호가 없음** ⇒ 스리프트와 프로토콜버퍼, 메시지팩 보다 길이가 가장 짧음 - 단순히 값이 연결된 형태이기 때문에 읽기 위해서는 스키마를 알고있어야함 - 즉, 정확히 같은 스키마를 사용하는 경우에만 복호화가 가능함 - Avro는 Write 스키마와 Read 스키마를 나누어서 사용함 - 버전 호환성을 지키기 위해 스키마를 나눔 - 복호화 시점에 Write 스키마와 Read 스키마 모두 확인하여 Resolution 단계에서 필드를 일치 시킴 ### Avro 스키마 발전 규칙 - Avro는 Union Type을 사용해야만 null 값을 가질 수 있는 필드임 - optional 필드는 Union Type으로 지정해야함 - Avro는 읽기 스키마에서 필드에 별칭을 지정할 수 있음. 이를 통해 필드 명칭 변경이 가능함 ### Avro의 동적 생성 스키마 - **Avro는 스키마에 대한 정보를 메시지에 포함하지 않음** ⇒ 스키마 정보에 대해서 크기를 절약할 수 있음. 대용량 데이터의 경우 이 오버헤드가 꽤 큼 - Avro는 RDB 같이 스키마를 추출할 수 있는 곳에서는 손쉽게 스키마를 추출해낼 수 있음 - 추출한 스키마로 메시지를 발행하면 이에 대한 호환성은 복호화하는 과정에서 담당하기 때문에 쓰기 스키마가 동적으로 생성되는 장점이 있음 - 스리프트나 프로토콜 버퍼는 수동으로 필드 태그를 할당해야함 ### 코드 생성과 동적 타입 언어 - 스리프트와 프로토콜 버퍼는 스키마를 통해 특정 프로그래밍 언어로 스키마를 구현한 코드를 생성할 수 있음 - 정적 타입 언어에서 타입 체크나 IDE 상에서 자동 완성을 지원함 - 그러나 동적 타입 프로그래밍 언어는 굳이 필요하지 않음. (명시적인 컴파일 단계가 없기 때문) - **Avro는 코드 생성을 선택적으로 제공함** ⇒ 없어도 사용가능 ## 데이터플로 모드 - 하나의 프로세스에서 다른 프로세스로 데이터를 전달하는 방법 - 데이터베이스를 통해 - 서비스 호출을 통해 - 비동기 메시지 전달을 통해 ### 데이터베이스를 통한 데이터플로 - 데이터를 DB에 저장할 때 Serializing, 읽을 때 Deserializing - 데이터는 코드보다 더 오래 산다 - 코드가 수정되어도 기존에 쓰여진 데이터는 함께 변경되지 않기 때문에 호환성이 중요 - 데이터를 새로운 스키마로 다시 기록하는 작업 ⇒ 마이그레이션 - 마이그레이션은 비용이 매우 비싸다. (기존 데이터를 모두 재작성해야하기 때문) ### 서비스를 통한 데이터플로: REST와 RPC - 클라이언트와 서버로 나누어 네트워크 통신을 통해 데이터를 전달 - 서버가 공개한 API를 통해 클라이언트가 요청함 ⇒ **API = 서비스** - 대용량 애플리케이션의 기능을 소규모 서비스로 나눔 **⇒ 마이크로서비스 아키텍처(MSA)** - 서비스에서 기능을 제공하기 위해 다른 서비스의 데이터가 필요하다면 다른 서비스에 요청을 보냄 - 각 서비스 API는 버전 간 호환이 가능해야함 ⇒ **데이터 부호화를 하기 때문** - 서비스 통신을 위해 HTTP를 기본 프로토콜로 사용한다면, **웹 서비스** - 웹 서비스는 웹 뿐만 아니라 다양한 상황에서 사용되는 중 - 웹 서비스의 대중적인 방법 2가지 - REST - SOAP - REST는 HTTP의 원칙을 토대로 한 설계 철학 - 데이터 타입을 강조하며 URL을 사용해 리소스를 식별하고, 캐시 제어, 인증, 콘텐츠 유형 협상 등 HTTP 기능을 씀 - REST 원칙에 따라 설계된 API를 RESTful API라고 부름 - SOAP는 네트워크 API 요청을 위한 XML 기반 프로토콜 - HTTP 상에서 사용되지만, HTTP 기능을 사용하지 않음 - XML 기반 언어를 사용함 ⇒ WSDL(Web Services Description Language) - WSDL은 클라이언트가 로컬 클래스와 메소드 호출로 원격 서비스에 접근하는 코드를 생성할 수 있음 - WSDL은 사람이 읽을 수 있게 설계하지 않았음 - 그렇기 때문에 코드 생성과 IDE에 크게 의존함 - 그러므로 SOAP가 지원하지 않는 프로그래밍 언어는 SOAP 서비스와 통합하기 어려움 ### RPC(Remote Procedure Call) 문제 - 로컬 메소드 호출처럼 네트워크 요청을 하는 방식 ⇒ **이런 추상화를 위치 투명성(Location Transparency)라고 부름** - 하지만, 네트워크 요청은 로컬 메소드 호출과 매우 다름 - 로컬 메소드 호출은 예측이 가능하지만, 네트워크 요청은 예측이 불가능함 - 요청과 응답이 유실되거나 응답하지 않는 경우 등 네트워크 문제는 제어가 불가능함 - 발생 가능한 문제 - 네트워크 요청은 타임아웃으로 인해 결과가 존재하지 않을 수 있음 - 원격 서비스가 요청을 제대로 받았는지조차 알 수 없음 - 실제로 요청이 처리되었으나 응답만 유실되는 경우가 존재할 수 있음 - 응답이 유실되어 재시도를 하게되면, 서비스가 멱등성이 보장되지 않으면 작업이 여러번 수행되어 문제가 발생할 수 있음 - 함수 실행 시간이 매우 불규칙적임 - 포인터와 같은 매개변수를 사용할 수 없음. 반드시 부호화가 되어야함 - 다른 언어로 작성된 서비스라면 데이터 타입이 호환되지 않을 수 있음 - 하지만, RPC는 사라지지 않음 - 스리프트와 Avro는 RPC 지원 기능을 내장하고 있음 - gRPC는 프로토콜 버퍼를 이용한 RPC 구현임 - 차세대 RPC는 Future 혹은 Promise를 사용함 - 실패할지 모르는 비동기 작업을 다루기 위함 - 서버와 클라이언트를 독립적으로 변경하고 배포할 수 있어야함 ⇒ 호환성 - 호환성이 깨지는 변경이 필요하면 API를 새로 파는 식으로 해결함 - 클라이언트를 식별하는 API 키를 발급시키고, API 키에 따라 클라이언트가 사용하는 버전을 구분하는 방식도 많이 사용함 ### 메시지 전달 데이터플로 - 메시지를 직접 네트워크 연결로 전송하지 않고 임시로 메시지를 저장하는 메시지 브로커를 사용함 - RPC에 비해 가지는 장점 - 수신자가 사용 불가능하거나 과부하 상태라면 메시지 브로커가 버퍼처럼 동작함 - 죽었던 프로세스에 메시지를 전달 할 수 있기 때문에 메시지 유실을 방지할 수 있음 - 송신자가 수신자에 대한 정보를 알 필요가 없음 - 하나의 메시지를 여러 수신자에 전송할 수 있음 - 송신자와 수신자를 논리적으로 구분 가능. (Publisher - Consumer) - e.g. 카프카, RabbitMQ 등 ### 분산 액터 프레임워크 - 액터 모델은 단일 프로세스 안에서 동시성을 위한 프로그래밍 모델 - 액터(= 하나의 클라이언트나 엔티티)는 로컬 상태를 가질 수 있고, 비동기 메시지의 송수신으로 다른 액터와 통신함 - 액터는 메시지 전달을 보장하지 않음 ⇒ 메시지 유실 가능 - 액터 모델은 단일 프로세스 안에서도 메시지가 유실될 수 있다고 가정하기 때문에 위치 투명성이 RPC보다 더 잘 동작함 - 분산 액터 프레임워크 - 아카(Akka) - 올리언스(Orleans) - 얼랭(Erlang)
LOG-INFO commented 8 months ago

끄적끄적

minkukjo commented 8 months ago

끄적 끄적