ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 4장 - 부호화와 발전
    CS/데이터 중심 애플리케이션 설계 요약 2022. 11. 18. 00:01

    변화하는 애플리케이션

    애플리케이션은 시간이 지나 사용자 요구사항에 따라 변경하거나 비즈니스 환경에 따라 변합니다.

    따라서 변경 사항을 쉽게 적용할 수 있게 시스템을 구축해야 합니다.

     

    대부분 기능을 변경하기 위해서는 저장하는 데이터도 변경해야 합니다.

    새로운 필드나 레코드 유형을 저장해야 하거나 기존 데이터를 새로운 방법으로 제공해야 할 수 있습니다.

     

    서버에서는 한 번에 몇 개의 노드에 새 버전을 배포하고 새로운 버전이 원활하게 실행되는지 확인한 후 모든 노드에서 실행되게 하는 순회식 업그레이드 방식을 사용합니다.

     

    새로운 코드가 예전 코드가 기록한 데이터를 읽을 수 있는 건 어렵지 않습니다.

    하지만 예전 코드는 새로운 코드가 기록한 데이터를 읽는 건 어렵습니다.

     

    이번장에서는 데이터 부호화를 위한 다양한 형식을 살펴보고 어떻게 스키마를 변경하고 예전 버전과 새로운 버전의 데이터와 코드가 공존하는 시스템을 지원하는지 설명합니다.

     

    데이터 부호화 형식

    메모리에 object, struct, list, array, hash table, tree 등으로 데이터가 유지됩니다.

    데이터를 파일에 쓰거나 네트워크를 통해 전송하려면 스스로를 포함한 일련의 바이트열의 형태로 부호화해야 합니다.

    이런 과정을 부호화, 직렬화, 마샬링이라고 하며 반대를 복호화, 역직렬화, 언마샬링이라고 합니다.

     

    예를 들면 자바의 Serializable이 존재합니다.

     

    이런 내장 부호화 라이브러리는 최소한의 추가 코드로 인메모리 객체를 저장하고 복원할 수 있어 매우 편리하지만 문제점들이 존재합니다.

     

    1. 특정 프로그래밍 언어와 묶여서 다른 언어에서 데이터를 읽기 매우 어렵다.

     

    2. 복호화 과정에서 임의의 클래스를 인스턴스화 할 수 있어야 하며, 보안 문제의 원인이 됩니다. -> 공격자가 원격으로 임

    의 코드를 실행할 수 있음

     

    3. 편리하고 빠르게 부호화하기 위해 상위, 하위 호환성의 불편한 문제 또는 효율성이 등한시되곤 함

     

    JSON, XML, 이진 변형

    많은 언어들이 표준화된 부호화로서 JSON과 XML을 사용합니다.

    XML은 종종 불필요하게 복잡하다고 비판받습니다.

     

    이진 부호화

    작은 데이터셋의 경우 부호화 형식 선택으로 얻는 이점이 무시될 수 있습니다.

    하지만 테라바이트 정도 되면 데이터 타입의 선택이 큰 영향을 미칩니다.

    JSON, XML은 이진 형식과 비교했을 때 훨씬 적은 공간을 사용합니다.

     

    따라서 JSON이 BSON, BJSON, UBJSON, BISON, 스마일과 XML용으로 사용 가능한 다양한 이진 부호화의 개발로 이어졌습니다.

    하지만 JSON, XML의 텍스트 버전처럼 널리 채택되진 않았습니다.

     

    필드 태그와 스키마 발전

    스키마는 시간이 따라 변하며 이를 스키마 발전이라 합니다.

    하위 호환성을 상위 호환성을 유지하기 위해서 어떻게 해야 할까요?

    예전 코드에서 새로운 코드로 기록한 데이터를 읽으려는 경우에는 해당 필드를 간단히 무시할 수 있습니다.

    반면에 새로운 필드를 required(필수 값)으로 추가한 경우 예전 코드는 추가한 새로운 필드를 기록하지 않기 때문에 작업이 실패하게 됩니다.

    따라서 optional(선택) 또는 default를 제공해야 하위 호환성을 유지할 수 있습니다.

     

    데이터 타입을 변경하는 경우도 32비트 정수를 64비트로 변경하는 경우에는 누락된 비트는 0으로 채우기 때문에 문제가 없습니다. 하지만 64비트를 32비트로 바꾸는 경우 값이 잘릴 수 있습니다.

     

    스리프트와 프로토콜 버퍼, 아브로

    아파치 스리프트와 프로토콜 버퍼는 같은 원리를 기반으로 한 이진 부호화 라이브러리입니다.

    구글, 페이스북에서 개발했으며 오픈소스입니다.

    둘 다 부호화할 데이터를 위한 스키마가 필요합니다.

     

    아파치 아브로도 스키마를 사용하는 이진 부호화 형식입니다.

     

    읽기 스키마, 쓰기 스키마, 동적 생성 스키마 등의 개념을 책에서 설명하지만 이 부분은 넘어가려고 합니다.

     

     

    데이터플로 모드

    메모리를 공유하지 않는 다른 프로세스로 일부 데이터를 보내고 싶을 때는 바이트열로 부호화해야 합니다.

    이런 작업을 위해 다양한 부호화를 살펴봤습니다.

     

    또한 상위 호환성과 하위 호환성은 발전성에서 매우 중요합니다.

    한 번에 모든 것을 변경할 필요 없이 시스템의 다양한 부분을 독립적으로 업그레이드해 변경 사항을 쉽게 반영합니다.

    이때 호환성은 데이터를 부호화하는 하나의 프로세스와 그것을 복호화하는 다른 프로세스 간의 관계입니다.

     

    프로세스 간 데이터를 전달하는 가장 보편적인 방법

    - DB를 통해

    - 서비스 호출(REST와 RPC)

    - 비동기 메시지 전달

     

    DB를 통한 데이터플로

    DB에 기록하는 프로세스는 데이터를 부호화하고 읽는 프로세스는 데이터를 복호화합니다.

    이때 하위 호환성과 상위 호환성을 잘 지켜주어야 합니다.

    새로운 버전의 애플리케이션이 기록한 데이터를 예전 버전의 애플리케이션이 갱신하는 경우 주의하지 않으면 데이터가 유실될 수 있습니다.

     

    보통 null값을 기본값으로 갖는 새로운 칼럼을 추가하는 간단한 스키마 변경을 허용합니다.

     

    서비스를 통한 데이터플로 : REST와 RPC

    클라이언트와 서버로 구성하여 API를 통해 데이터를 전달합니다.

    여러 가지 면에서 서비스는 DB와 유사하게 사용될 수 있습니다.

    이때 서버와 클라이언트가 사용하는 데이터 부호화는 서비스 API의 버전 간 호환이 가능해야 합니다.

     

    RPC와 로컬 함수

    로컬 함수는 예측이 가능합니다.

    제어 가능한 매개변수에 따라 성공하거나 실패합니다.

     

    하지만 네트워크 요청은 예측이 어렵습니다.

    요청과 응답이 유실되거나 요청에 응답하지 않을 수 있고 이런 문제는 전혀 제어할 수 없습니다.

     

    - timeout으로 결과 없이 반환되면 무슨 일이 있었는지 쉽게 알 수 없습니다.

     

    - 실패한 네트워크 요청을 다시 시도할 때 요청이 실제로는 처리되고 응답만 유실될 수 있습니다(이때 멱등성을 적용하지 않으면 작업이 여러 번 재시도됩니다)

     

    - 로컬 함수는 거의 같은 실행 시간이 소요되지만 네트워크 요청은 혼잡도와 과부하에 따라 시간이 차이가 많이 날 수 있습니다.

     

    - 로컬 함수는 메모리의 객체를 효율적으로 사용할 수 있지만 네트워크의 경우 부호화가 필요합니다. 이때 큰 객체라면 문제가 발생할 수 있습니다.

     

    - 서로 다른 언어에서 데이터 타입을 변환해야 할 때 문제가 발생할 수 있습니다.

     

    데이터 부호화와 RPC의 발전

    서비스 제공자는 클라이언트를 강제로 업그레이드할 수 없기 때문에 호환성은 오랜 시간 동안 (아마도 무한정) 유지되어야 합니다.

    호환성을 깨는 변경이 필요하면 서비스 제공자는 보통 여러 버전의 서비스 API를 함께 유지해야 합니다.

    보통 헤더에 버전 번호를 사용하는 방식이 일반적입니다.

     

    메시지 전달 데이터플로

    비동기 메시지 전달 시스템을 간단히 알아보겠습니다.

    클라이언트 요청을 낮은 지연 시간으로 다른 프로세스에 전달한다는 점에서 RPC와 비슷합니다.

    하지만 직접 네트워크 연결로 전송하지 않고 임시로 메시지를 저장하는 메시지 브로커(메시지 큐)나 메시지 지향 미들웨어라는 중간 단계를 거쳐 전송한다는 점은 DB와 유사합니다.

     

    RPC를 사용하는 방식보다 메시지 브로커를 사용하는 장점

    -  수신자가 사용 불가능하거나 과부하 상태라면 메시지 브로커가 버퍼처럼 동작하기 때문에 시스템 안정성이 향상됩니다.

    - 죽었던 프로세스에 메시지를 다시 전달할 수 있기 때문에 메시지 유실을 방지할 수 있습니다.

    - 송신자가 수신자의 IP 주소나 포트 번호를 알 필요가 없습니다.

    - 하나의 메시지를 여러 수신자로 전송할 수 있습니다.

    - 논리적으로 송신자는 수신자와 분리됩니다. (송신자는 메시지를 publish 소비자는 consume)

     

    RPC와의 다른 점은 일반적으로 단방향이라는 점입니다.

    응답을 기대하지 않으며 비동기로 이루어집니다.

     

     

    메시지 브로커

    래빗MQ, 액티브MQ, 호닛Q, 나츠, 아파치 카프카 같은 오픈소스 구현이 대중화되었습니다.

    프로세스 하나가 메시지를 이름이 지정된 큐나 토픽으로 전송하고 브로커는 해당 큐나 토픽 하나 이상의 소비자 또는 구독자에게 메시지를 전달합니다.

     

    특정 데이터 모델을 강요하지 않으며 부호화가 상하위 호환성을 모두 가진다면 메시지 브로커에서 pub, sub을 독립적으로 변경해 임의 순서로 배포할 수 있는 유연성을 얻게 됩니다.

     

    정리

    데이터 구조를 네트워크나 디스크 상의 바이트열로 변환하는 다양한 방법을 살펴보았습니다.

    이런 부호화의 세부 사항은 효율성뿐만 아니라 애플리케이션의 아키텍처와 배포의 선택 사항에도 영향을 미칩니다.

     

    특히 많은 서비스가 서서히 배포하는 방식이 필요로 합니다.

    이는 무중단 서비스를 출시 가능하게 하고 배포를 덜 위험하게 만듭니다.

    따라서 시스템에 흐르는 모든 데이터는 하위 호환성과 상위 호환성을 제공하는 방식으로 부호화해야 합니다.

     

    댓글

Designed by Tistory.