Computer >> 컴퓨터 >  >> 네트워킹 >> 네트워킹

직렬화(Serialization)란 무엇일까? 개념부터 활용 사례까지 완벽 정리

최근 프로젝트 진행 상황 회의에서 우리 팀은 직렬화(serialization)를 활용해 애플리케이션 간에 데이터를 주고받는 방법에 대해 논의했습니다.

그때 소프트웨어 프로젝트에 더 깊이 관여하고 싶어하던 한 엔지니어가 이 용어를 처음 들어본다고 말했습니다.

직렬화처럼 대규모 프로젝트에 뛰어들기 전까지는 접할 기회가 없는 필수 개념들을 놓치기 쉽습니다. 이 동료도 그랬고, 사실 저도 한때 그랬습니다.

그래서 이 글을 쓰게 되었습니다. 그날 저는 동료에게 직렬화를 설명해 주었고, 오늘은 여러분께 같은 내용을 알려드리려 합니다.

직렬화란 무엇인가?

직렬화는 하나의 서비스가 파이썬의 딕셔너리 같은 데이터 구조를 받아서 포장한 뒤, 다른 서비스로 전송하여 읽을 수 있도록 만드는 과정입니다. 이것이 가장 간단한 정의입니다.

누군가에게 메시지를 보내야 한다고 상상해 보세요. 이미 조립된 퍼즐 위에 텍스트를 적고, 퍼즐 조각들을 분해한 다음, 다시 조립하는 방법에 대한 설명을 덧붙여 보냅니다.

메시지를 받은 사람은 퍼즐 조각들을 받아 원래대로 조립하고, 그제야 제 메시지를 읽을 수 있게 됩니다.

직렬화(Serialization)란 무엇일까? 개념부터 활용 사례까지 완벽 정리
직렬화의 기본 흐름

기술적인 정의는 좀 더 흥미롭습니다. 직렬화란 데이터 객체를 바이트 스트림(byte stream)으로 변환하여, 객체의 상태를 디스크에 저장하거나 네트워크를 통해 전송할 수 있도록 만드는 과정을 말합니다. 이를 통해 필요한 저장 공간을 줄이고 네트워크를 통한 정보 전송도 훨씬 수월해집니다.

직렬화(Serialization)란 무엇일까? 개념부터 활용 사례까지 완벽 정리
직렬화 과정

마샬링(Marshaling)과 직렬화의 차이점은?

마샬링이라는 과정이 떠오를 수 있습니다. 마샬링은 객체의 메모리 표현을 전송에 적합한 형태로 변환하는 과정입니다.

마샬링과 직렬화는 느슨하게는 동의어로 쓰이지만, 결정적인 차이가 존재합니다. 예를 들어 JSON 데이터를 읽어 고(Go) 언어 데이터 구조로 변환하는 프로그램을 작성할 때, 마샬링을 사용해 JSON 키-값 쌍을 Go 언어의 키-값으로 변환할 수 있습니다.

차이점은 마샬링은 데이터를 변환(translate)하는 데 사용될 수 있다는 것이고, 반면 직렬화는 데이터를 바이트 스트림 형태로 보내거나 저장한 뒤 원래 형태로 재조립한다는 점입니다. 두 과정 모두 직렬화를 수행하지만, 의도 면에서 차이가 있는 것입니다.

아래는 트위터 데이터와 상호작용하기 위해 제가 만든 구조체(struct)로, 마샬링이 실제로 동작하는 예시입니다. Go 언어에서는 '태그(tag)'라고 하는 힌트를 지정할 수 있으며, 이를 통해 Go 언어에 내장된 마샬링 기능으로 객체를 손쉽게 JSON 데이터로 변환할 수 있습니다.

직렬화(Serialization)란 무엇일까? 개념부터 활용 사례까지 완벽 정리
JSON 태그를 사용한 Go 언어 구조체

엔디언(Endianness)이란?

엔디언에 대해서도 가볍게 짚고 넘어가겠습니다. 엔디언은 메모리에서 바이트가 저장되는 순서를 설명하는 용어입니다.

메모리를 데이터 조각들이 저장되는 블록이라고 생각해 보세요. 직렬화가 제대로 동작하려면 시스템마다 엔디언 방식이 달라지더라도 바이트 스트림이 데이터 타입을 그대로 전달할 수 있어야 합니다.

아래에서 리틀 엔디안(little-endian)과 빅 엔디안(big-endian)의 차이를 확인할 수 있습니다. 모든 시스템이 비트를 같은 순서로 배치하지 않기 때문에, 시스템 간에 엔디언이 일치하거나 어떤 식으로든 변환되는 것이 매우 중요합니다.

직렬화(Serialization)란 무엇일까? 개념부터 활용 사례까지 완벽 정리
리틀 엔디안과 빅 엔디안 (출처: https://pvs-studio.com/en/blog/lessons/0019/)

직렬화의 활용 사례

저희 프로젝트는 이러한 특성을 최대한 활용합니다. 스캔 중인 하드웨어로부터 정보를 받아, 그 정보를 바이트 스트림으로 패키징한 후 네트워크를 통해 다른 서비스로 전송하면, 해당 서비스가 데이터를 재구성하는 방식입니다.

이렇게 직렬화 과정을 거꾸로 되돌려 데이터를 원래 형태로 재구성하는 과정을 역직렬화(deserialization)라고 부릅니다.

이 외에도 다양한 활용 사례가 있습니다. 예를 들어 REST API나 AMQP 같은 메시징 프로토콜은 직렬화를 활용해 데이터를 압축하고 전송합니다.

AMQP는 AMQP 브로커(broker)에게 메시지를 보내면, 수신 서비스가 이 브로커를 '리스닝(listening)'하며 메시지를 기다리는 메시징 프로토콜입니다. 분산 시스템 내에서 데이터를 주고받을 때 자주 사용되기 때문에 백엔드 엔지니어라면 잘 알고 있을 겁니다.

대부분의 프로그래밍 언어는 직렬화를 손쉽게 구현할 수 있는 기능을 제공합니다. 따라서 직렬화는 특정 언어에 종속되지 않는(universal) 주제입니다.

직렬화 실습 예제

간단한 예제를 살펴보겠습니다. 아래 코드는 kombu 라이브러리를 사용해 AMQP로 메시지를 전송하는 코드입니다. 네트워크를 통해 한 소프트웨어 패키지에서 다른 패키지로 메시지를 보내는 데 활용합니다. 다음은 AMQP 브로커에게 메시지를 전송하는 서비스의 코드입니다:

def send_message(self, payload, sender_serializer):
...
    try:
        producer.publish(
            {'payload': message},
            ...
            serializer = 'json',
            ...
        )
        return

publish 메서드에 주목하세요. 직렬화 방식을 인자로 함께 전달함으로써, 라이브러리가 우리가 넘기는 데이터를 어떻게 직렬화해야 할지 알 수 있게 됩니다.

데이터 메시지는 바이트 스트림으로 변환되는데, 이를 들여다보면 긴 문자와 숫자 나열처럼 보입니다. 그리고 이 메시지를 전송합니다.

수신 측 서비스는 동일한 직렬화 방식을 사용해 데이터를 원래 상태로 재구성합니다. 여러 도구들이 서로 메시지를 주고받아야만 동작하는 툴 suite를 개발하고 있는 저희 입장에서는 이것이 매우 중요한 기능입니다.

직렬화 데이터 포맷

저는 작업 특성상 필요할 때마다 JSON을 직렬화에 사용합니다. 하지만 JSON 외에도 몇 가지 선택지가 있습니다.

JSON은 오버헤드가 꽤 크지만, 사람이 읽기 쉽다는 점에서 저에게는 최적의 선택입니다. 그 밖에도 Protobuf, YAML, XML 등 다양한 데이터 객체 포맷을 사용할 수 있습니다.

마무리

이 내용을 글로 정리하게 되어 다행입니다. 이제 이 주제에 대해 계속 고민하지 않아도 되고, 누군가에게는 유익한 배움이 되었기를 바랍니다.

직렬화는 통신 파이프라인을 구축할 때 필수적인 요소입니다. 이 주제를 미리 알아두면 어떤 도구를 다루더라도 올바른 배경지식을 갖춘 상태에서 자신 있게 접근할 수 있습니다.

- George