데이터 중심 애플리케이션 설계 ch 4. 부호화와 발전

작성일: 2021-09-23 23:29

데이터 타입이나 스키마가 변경될 때 애플리케이션 코드에 대한 변경이 종종 발생한다. 하지만 대규모 애플리케이션에서 코드 변경은 대개 즉시 반영할 수 없다.

  • 서버 측 애플리케이션은 새로운 버전이 잘 동작하는지 몇 개의 노드에서만 확인 후 순차적으로 모든 노드에 실행되게 하는 rollout 방식을 주로 사용한다.
  • 클라이언트 측 애플리케이션은 사용자에게 전적으로 좌우된다. 사용자가 업데이트 하지 않으면 적용할 수 없다.

이것은 예전 버전의 코드와 새로운 버전의 코드가 공존할 수 있다는 의미다. 시스템이 계속 원할하게 실행되게 하기 위해 양뱡향으로 호한성을 유지해야 한다.

  • 하위 호환성: 새로운 코드는 예전 코드가 기록한 데이터를 읽을 수 있어야 한다.
  • 상위 호환성: 예전 코드는 새로운 코드가 기록한 데이터를 읽을 수 있어야 한다.

새로운 코드는 예전 버전의 코드가 기록한 데이터의 형식을 명확히 알 수 있기 때문에 하위 호환성은 일반적으로 어렵지 않다.

하지만 예전 코드가 새로운 코드가 기록한 데이터의 형식을 예측할 수 없기 때문에 상위 호환성은 일반적으로 다루기 더 어렵다.

# 데이터 부호화 형식

프로그램은 보통 두 가지 형태로 표현된 데이터를 사용해 동작한다.

  • 메모리에 객체, 배열등의 데이터를 CPU에서 효율적으로 접근하고 조작할 수 있게(보통은 포인터를 이용해) 최적화된다.
  • 데이터를 파일에 쓰거나 네트워크를 통해 전송하기 위해선 일련의 바이트열(JSON 같은)의 형태로 부호화해야 한다.
  • 포인터는 다른 프로세스가 이해할 수 없기 때문에 이 일련의 바이트열은 보통 메모리에서 사용하는 데이터구조와는 상당히 다르다.

두 가지 표현 사이의 일종의 전환이다.

  • 인메모리 표현에서 바이트열로의 전환을 부호화(직렬화, 마샬링)라고 한다.
  • 바이트열에서 인메모리 표현으로의 전환을 복호화(역직렬화, 언마샬링)라고 한다.

# 언어별 부호화 기능

  • 많은 프로그래밍 언어는 인메모리 객체를 바이트열로 부호화하는 기능을 내장한다.(대표적으로 Java의 Serializable)
  • 이러한 부호화 라이브러리는 편리하지만 심각한 문제점들이 많이 때문에 일반적으로 이를 사용하는건 좋지 않다.
    • 특정 언어에 결합되어 있어 다른 언어에 지원이 좋지 못하다.
    • 동일한 객체 유형의 데이터를 복원하기 위해 복호화 과정에서 클래스를 인스턴스화할 수 있어야하고 이것은 보안의 큰 문제가 될 수 있다.

# JSON과 XML

JSON과 XML은 널리 알려져 사용되는 부호화방식으로 사람이 읽을 수 있는 텍스트 형식으로 부호화한다. 다양한 용도에 사용하기 충분하지만 몇 가지 결점이 있다.

  • 수(number) 부호화에 많은 애매함이 존재한다. XML은 숫자로 구성된 문자열을 구분할 수 없다. JSON은 문자열과 수를 구분할 수있으나 부동소수점을 구별하지 않는다.
  • 유니코드 문자열을 잘 지원하지만 이진 문자열을 지원하지 않는다. JSON이나 XML로 이진 문자열을 보내기 위해 Base64를 사용해 텍스트로 부호화해야 한다.

# 이진 부호화

프로토콜 버퍼가 대표적인 이진 부호화 라이브러리이다. 프로토콜 버퍼는 부호화할 데이터를 위한 스키마가 필요하다.

스키마를 정의하면 라이브러리를 통해 언어별 적절한 인터페이스를 생성해주고 이를 통해 부호화 및 복호화가 가능하다.

  • 스키마를 정의할 때 필드 태그가 포함되는데 부호화 및 복호화 시 실제 필드명이 아닌 이 필드 태그를 사용하여 효율적으로 동작할 수 있게 한다.

# 필드 태그와 스키마 발전

  • 부호화 시 실제 필드명이 아닌 필드 태그를 활용하므로 필드 태그는 부호화된 데이터를 해석하기 위해 매우 중요하다.
  • 부호화된 데이터는 필드명을 참조하지 않으므로 스키마에서 필드명은 변경할 수 있으나 필드 태그는 기존 모든 부호화된 데이터에 영향을 주므로 변경할 수 없다.
  • 필드에 새로운 태그 번호를 부여하여 스키마에 새로운 필드를 추가할 수 있다. 예전 코드에서는 새로운 스키마로 기록된 데이터를 읽더라도 새로운 필드는 간단히 무시할 수 있으므로 상위 호환성을 유지한다.
  • 각 필드의 고유한 태그 번호는 변경되지 않으므로 새로운 필드는 required를 설정할 수 없도록 하면 새로운 코드가 예전에 기록된 데이터를 읽는데도 문제가 없어 하위 호환성 또한 유지할 수 있다.
    • 즉, 하위 호환성을 위해서는 새롭게 추가된 필드는 반드시 optional이거나 기본값을 가져야 한다.
  • 필드 삭제인 경우 optional 필드만 삭제가 가능하고 삭제된 필드와 동일한 태그 번호는 절대 다시 사용할 수 없다.

# 데이터타입과 스키마 발전

  • 필드의 데이터타입을 변경하는건 불가능하지 않지만 값이 정확하지 않거나 잘릴 위험이 있다.
    • 32비트 정수를 64비트 정수로 변경하려고 하는 경우 하위 호환성을 유지할 수 있으나 상위 호환성을 유지하지 못한다.(새로운 스키마로 64bit 정수로 작성된 데이터를 예전 코드에서는 32bit 정수로 읽게 된다.)
  • 프로토콜 버퍼는 배열 타입은 없는 대신 다중 값을 표현하는 repeated 표시자가 존재한다.
    • 다중 값의 특징에 따라 단일 값을 표현하는 optionalrepeated로 변경해도 문제가 없다.

# 스키마의 장점

  • 부호화된 데이터에서 필드 이름을 생략할 수 있기 때문에 크기를 대폭 줄일 수 있다.
  • 스키마는 유용한 문서화 형식으로 복호화를 할 때 스키마가 필요하기 때문에 스키마가 최신 상태인지 확신할 수 있다.
  • 스키마 데이터베이스를 유지하면 스키마 변경이 적용되기 전 상위 호환성과 하위 호환성을 확인할 수 있다.
  • 정적 타입 프로그래밍 언어에서 스키마로부터 코드를 생성하는 기능은 컴파일 타임 체크가 가능하므로 매우 유용하다.

# 데이터플로 모드

데이터플로는 매우 추상적인 개념으로 하나의 프로세스에서 다른 프로세스로 데이터를 전달하는 방법은 아주 많다. 누가 데이터를 복호화하고 누가 그것을 부호화할까?

데이터플로는 데이터베이스를 통해서, 서비스 호출을 통해서, 비동기 메시지 전달을 통해서 등등 다양하게 이루어질 수 있다.

# 데이터베이스를 통한 데이터플로

  • 데이터베이스에 기록하는 프로세스는 데이터를 부호화하고 데이터베이스에서 읽는 프로세스는 데이터를 복호화한다.
  • 이전에 기록한 내용을 미래에서 복호화 가능해야 하므로 하위 호환성은 반드시 필요하다.
  • 새로운 버전의 코드로 기록된 값을 예전 버전의 코드가 읽을 가능성 또한 존재하므로 데이터베이스에서 상위 호환성도 대게 필요하다.
    • 새로운 버전 코드에서 기록된 값을 예전 버전의 코드가 읽어서 갱신 후 다시 기록할 때 상황에 따라 새로운 버전에 추가된 필드가 제거될 수 있다. 이런 경우 애플리케이션에서 주의가 필요하다.
  • 보통 스키마가 변경되었을 때 기존 데이터를 마이그레이션 하는건 비용이 크기때문에 기존 데이터를 유지하고 새로운 필드에 null을 기본값을 갖도록 하여 스키마 변경을 가능하게 한다.(MySQL은 항상 전체 테이블을 다시 기록한다.)

# 서비스 호출(REST와 RPC)을 통한 데이터플로

  • 서비스 지향 및 MSA의 핵심 설계 목표는 서비스를 배포와 변경에 독립적으로 만들어 애플리케이션의 유지보수와 확장을 더 쉽게 할 수 있게 만드는 것이다.
  • 그러므로 서버와 클라이언트가 사용하는 데이터 부호화는 서비스 API 버전 간 호환이 가능해야 한다.

# 비동기 메시지 전달을 통한 데이터플로

  • 메시지를 직접 네트워크 연결로 전송하지 않고 임시로 메시지를 저장하는 메시지 브로커라는 중간 단계를 거쳐 전송한다는 점에서 데이터베이스와 유사하다.
  • 메시지를 낮은 지연 시간으로 다른 프로세스에 전달한다는 점에서는 RPC와 비슷하지만 메시지 브로커 방식은 RPC 방식보다 여러 장점이 있다.
    • 수신자가 사용 불가 상태에 빠져도 메시지 브로커가 버퍼로 동작할 수 있기 때문에 시스템 안정성이 향상된다.
    • 메시지 브로커가 메시지를 저장하므로 메시지 유실을 방지할 수 있다.
    • 송신자와 수신자가 서로 알 필요가 없으므로 서로간에 의존성이 없다.
    • 하나의 메시지를 여러 수신자로 전송할 수 있다.
  • 추가적으로 메시지 전달 방식은 송신 프로세스가 응답을 기대하지 않으므로 단방향이다.