Skip to content

직렬화: 데이터의 "번역"

🎯 핵심 질문

데이터는 어떻게 네트워크를 통해 전송될까? 마치 한 사람의 말을 다른 사람이 어떻게 이해할 수 있는지 묻는 것과 같습니다. 직렬화는 바로 "데이터 번역" 문제를 해결합니다——메모리 속 객체를 전송 가능한 형식으로 번역하는 것입니다.


직렬화 데이터의 필요성

프론트엔드와 백엔드가 상호작용하는 과정에서 데이터는 여러 번 "변형"을 거쳐야 서버에서 클라이언트로 전달됩니다.

시나리오 1: 프론트엔드가 받은 데이터가 "변했다"

javascript
// 백엔드 전송
Date birth = new Date(1990, 5, 15)

// 프론트엔드 수신
{ "birth": "1990-06-15T00:00:00Z" }  // 문자열이다!

프론트엔드에서 .getFullYear()를 사용하려 했더니 오류가 발생했습니다——왜냐하면 이것은 Date 객체가 아니라 문자열이기 때문입니다.

시나리오 2: 한글 깨짐

json
// 기대
{ "name": "홍길동" }

// 실제 수신
{ "name": "í ™ê¸¸ë ™" }

문자 인코딩 문제로 한글이 깨져서 표시됩니다.

시나리오 3: 성능 병목

json
// 10,000개 상품 리스트를 포함한 응답
{
  "products": [
    { "id": 1, "name": "...", "description": "...", ... },
    // ... 9999 more
  ]
}
// 크기: 5.2 MB, 전송 시간: 3.5초

JSON 형식의 중복성으로 인해 데이터 패킷이 너무 커져 성능에 심각한 영향을 미칩니다.


직렬화는 마치 "번역"과 같습니다——메모리 객체를 전송 가능한 형식으로 "번역"하고, 수신 측에서 다시 "번역"하여 되돌립니다.


1. 직렬화/역직렬화란?

직렬화(Serialization)는 객체를 전송 가능한 형식으로 변환하는 과정입니다.

역직렬화(Deserialization)는 전송 형식을 다시 객체로 복원하는 과정입니다.

1.1 택배 발송에 비유하기

택배 발송직렬화설명
물품 포장직렬화물건을 상자에 넣고 라벨 부착
운송네트워크 전송택배 차량이 목적지까지 운송
포장 해체 및 물품 꺼내기역직렬화수령인이 상자를 열고 물건을 꺼냄

1.2 왜 직렬화가 필요한가?

이유설명예시
네트워크 전송네트워크는 바이트 스트림만 전송 가능API 호출, RPC 통신
영속적 저장디스크는 바이트만 저장 가능객체를 파일, 데이터베이스에 저장
크로스 언어각 언어의 데이터 구조가 다름Java 객체 → Python 사전
분산 캐시Redis/Memcached는 바이트 저장사용자 정보 캐싱

2. 일반적인 직렬화 형식

👇 직접 사용해 보세요: 아래 버튼을 클릭하여 다양한 언어의 직렬화 과정을 관찰하세요:

🔄Serialization Demo
📦In-memory object
const user = {
  id: 123,
  name: "Alice",
  email: "alice@example.com",
  age: 28
};
An object in memory, usable only by the current process
Serialize
{}JSON string68 bytes
{
  "id": 123,
  "name": "Alice",
  "email": "alice@example.com",
  "age": 28
}
Can be sent over the network and used across languages
Transfer
💻Binary52 bytes
Hex encoding (MessagePack):
\xa7 id 7b
\xa4 name \xa5 Alice
\xa5 email \xb1 alice@example.com
\xa3 age 1c
Protobuf/MessagePack, smaller and faster
📊 Format comparison
Format
Size
Speed
Readability
Cross-language
JSON
★★★☆☆
★★★☆☆
★★★★★
★★★★★
XML
★★☆☆☆
★★☆☆☆
★★★★★
★★★★★
Protobuf
★★★★★
★★★★★
★☆☆☆☆
★★★★☆
MessagePack
★★★★☆
★★★★☆
★★☆☆☆
★★★★★

2.1 JSON: 가장 범용적

장점:

  • 가독성 우수, 디버깅 편리
  • 모든 언어 지원
  • 브라우저 네이티브 지원(JSON.parse / JSON.stringify)

단점:

  • 용량이 큼({} "" 마크가 많음)
  • 풍부한 데이터 타입 미지원(Date, Map, Set은 문자열로 변환됨)

적용 시나리오:

  • 공개 API
  • 프론트엔드-백엔드 통신
  • 설정 파일

2.2 XML: 한때 주류였던

xml
<?xml version="1.0" encoding="UTF-8"?>
<user>
  <id>123</id>
  <name>홍길동</name>
  <email>hong@example.com</email>
  <age>28</age>
</user>

장점:

  • 구조가 명확하고 주석 지원
  • 복잡한 중첩 구조 지원
  • Schema 검증(XSD) 가능

단점:

  • 용량이 크고 파싱이 느림
  • 태그 중복(<open></close>)

적용 시나리오:

  • 설정 파일(Spring, MyBatis)
  • SOAP 프로토콜
  • 복잡한 데이터 교환

2.3 Protobuf: 가장 효율적

protobuf
// user.proto
syntax = "proto3";
message User {
  int32 id = 1;
  string name = 2;
  string email = 3;
  int32 age = 4;
}

장점:

  • 용량이 작음(JSON보다 30-50% 작음)
  • 속도가 빠름(파싱 속도 5-10배 빠름)
  • 하위 호환성(새 필드 추가가 이전 버전에 영향 없음)

단점:

  • 읽을 수 없음(바이너리 형식)
  • .proto 파일 정의 필요
  • 동적 타입 미지원

적용 시나리오:

  • 마이크로서비스 내부 통신
  • 고성능 시나리오(게임, 실시간 통신)
  • 모바일 앱(트래픽 절약)

2.4 MessagePack: 가독성과 성능 모두 고려

json
// MessagePack은 JSON의 바이너리 버전
// 동일 데이터, MessagePack이 JSON보다 약 30% 작음

장점:

  • JSON보다 작고 빠름
  • JSON의 데이터 모델 유지
  • 모든 JSON 타입 지원

단점:

  • 읽을 수 없음
  • Protobuf만큼 효율적이지 않음

적용 시나리오:

  • 성능이 필요하지만 Protobuf를 사용하고 싶지 않을 때
  • Redis 캐시
  • WebSocket 메시지

3. 각 언어별 직렬화 방식 비교

언어JSON 라이브러리Protobuf 라이브러리XML 라이브러리
JavaScriptJSON.stringify()protobuf.jsfast-xml-parser
Pythonjson.dumps()protobufxmltodict
JavaJackson / Gsonprotobuf-javaJAXB
Goencoding/jsonprotoencoding/xml
C++nlohmann/jsonprotobuftinyxml2
C#System.Text.JsonGoogle.ProtobufSystem.Xml

💡 선택 제안

  • 프론트엔드-백엔드 통신: JSON(디버깅 편리)
  • 마이크로서비스 내부: Protobuf(성능 최적)
  • 설정 파일: JSON 또는 YAML
  • 레거시 시스템 연동: XML(선택의 여지가 없을 수 있음)

4. 성능 비교

4.1 크기 비교(사용자 객체 예시)

형식크기JSON 대비
JSON68 bytes100%
XML142 bytes209%
Protobuf38 bytes56%
MessagePack52 bytes76%

4.2 속도 비교(10,000회 직렬화)

형식소요 시간JSON 대비
JSON45 ms100%
XML120 ms267%
Protobuf8 ms18%
MessagePack28 ms62%

💡 성능 테스트 결론

  • Protobuf가 가장 빠름: 고성능 시나리오에 적합
  • MessagePack이 그 다음: JSON보다 약 40% 빠름
  • JSON이 가장 느림: 하지만 대부분의 시나리오에서 충분

5. 자주 발생하는 문제

5.1 날짜 직렬화 문제

문제: Date 객체 직렬화 후 문자열로 변환됨

javascript
// 직렬화 전
const date = new Date('2024-01-01')

// 직렬화 후
JSON.stringify(date)  // "2024-01-01T00:00:00.000Z"

해결책:

javascript
// 방안 1: 타임스탬프로 변환
{ createdAt: date.getTime() }  // 1704067200000

// 방안 2: ISO 문자열로 변환
{ createdAt: date.toISOString() }  // "2024-01-01T00:00:00.000Z"

// 방안 3: 커스텀 직렬화
JSON.stringify(obj, (key, value) => {
  if (value instanceof Date) {
    return { __type: 'Date', value: value.toISOString() }
  }
  return value
})

5.2 순환 참조 문제

문제: 객체 순환 참조 시 오류 발생

javascript
const obj = { name: 'test' }
obj.self = obj
JSON.stringify(obj)  // TypeError: Converting circular structure to JSON

해결책:

javascript
// 방안 1: 순환 참조 필터링
const seen = new WeakSet()
JSON.stringify(obj, (key, value) => {
  if (typeof value === 'object' && value !== null) {
    if (seen.has(value)) return
    seen.add(value)
  }
  return value
})

// 방안 2: flatted 라이브러리 사용
import { parse, stringify } from 'flatted'
stringify(obj)  // 순환 참조 자동 처리

5.3 한글 깨짐 문제

문제: 한글 직렬화 후 깨짐

원인:

  • 문자 인코딩 불일치(UTF-8 vs EUC-KR)
  • BOM 마크

해결책:

python
# Python UTF-8 사용 보장
import json
json.dumps(data, ensure_ascii=False)  # 한글 이스케이프 안 함
javascript
// Node.js 응답 헤더 설정
res.setHeader('Content-Type', 'application/json; charset=utf-8')

6. 실전: 전자상거래 시스템 직렬화 방안

6.1 시나리오 분석

시나리오형식 선택이유
App → 백엔드 APIJSON디버깅 편리, 프론트엔드-백엔드 통일
백엔드 → 백엔드 RPCProtobuf성능 최적, 트래픽 절약
Redis에 캐싱MessagePackJSON보다 작고 복잡한 객체 직렬화 가능
로그 기록JSON로그 분석 도구 파싱 용이

6.2 코드 예시

javascript
// API 응답(JSON)
app.get('/api/products/:id', async (req, res) => {
  const product = await db.getProduct(req.params.id)
  res.json({
    code: 0,
    data: product
  })
})

// 마이크로서비스 통신(Protobuf)
// product.proto
syntax = "proto3";
message Product {
  int32 id = 1;
  string name = 2;
  int32 price = 3;
}

// 서버 측
const proto = require('./product.proto')
const message = proto.Product.create(product)
const buffer = proto.Product.encode(message).finish()

// 클라이언트 측
const decoded = proto.Product.decode(buffer)

// Redis 캐시(MessagePack)
const msgpack = require('msgpack-lite')
await redis.set(
  `product:${id}`,
  msgpack.encode(product)
)
const cached = msgpack.decode(await redis.get(`product:${id}`))

7. AI를 활용한 직렬화 방안 선택

AI는 시나리오에 따라 적절한 직렬화 형식을 선택하는 데 도움을 줄 수 있습니다.

7.1 프롬프트 템플릿

당신은 시니어 시스템 아키텍트로서 데이터 직렬화 기술에 정통합니다. 적절한 직렬화 방안을 선택해 주세요.

## 비즈니스 시나리오
[시나리오 설명, 예: 전자상거래 앱, 게임 백엔드, 마이크로서비스 등]

## 기술 요구사항
[제약 조건 나열, 예:
- 프론트엔드-백엔드 분리(Vue + Node.js)
- 높은 성능 요구(QPS > 10000)
- 트래픽 민감(모바일, 트래픽 절약 필요)
- 크로스 언어 필요(Java + Python + Go)]

## 데이터 특징
[데이터 특징 설명, 예:
- 데이터 양이 큼(단일 응답 > 1MB)
- 구조가 복잡(다층 중첩)
- 날짜, 바이너리 데이터 등 특수 타입 포함]

## 출력 형식
다음 형식으로 출력:
1. 추천 방안(JSON/Protobuf/MessagePack/XML)
2. 이유
3. 성능 비교(예상 크기와 속도)
4. 코드 예시

7.2 실전 예시

입력 프롬프트:

당신은 시니어 시스템 아키텍트입니다. 우리 전자상거래 앱의 상품 리스트 API 성능을 최적화해야 합니다.

## 비즈니스 시나리오
- 전자상거래 앱의 상품 리스트 API
- 단일 응답에 100개 상품 반환
- 각 상품은 id, name, price, image, description 등 20개 필드 포함
- 현재 JSON 사용, 응답 크기 약 200KB, 소요 시간 800ms

## 기술 요구사항
- 프론트엔드는 Vue.js
- 백엔드는 Node.js + Go 마이크로서비스
- 성능과 개발 효율성 모두 고려 필요

## 최적화 목표
- 응답 시간 300ms 이내로 단축
- 데이터 패킷 크기 30% 감소

AI 출력:

추천 방안: 하이브리드 방안

1. **API 계층**: JSON 계속 사용(프론트엔드 친화적)
2. **마이크로서비스 통신**: Protobuf로 전환(성능 향상)
3. **정적 데이터**: gzip 압축 활성화

### 이유
- JSON은 프론트엔드에 가장 친화적이며 추가 파싱 불필요
- 백엔드 마이크로서비스에 Protobuf 사용 시 데이터 양 40% 감소
- gzip 압축은 텍스트에 효과 뚜렷(JSON 70% 압축 가능)

### 성능 비교
| 방안 | 크기 | 소요 시간 |
|------|------|------|
| 현재(JSON) | 200 KB | 800 ms |
| + gzip | 60 KB | 350 ms |
| + Protobuf | 50 KB | 280 ms |

### 코드 예시
[구체적 구현 코드...]

용어 빠른 참조

용어영어설명
직렬화Serialization객체 → 바이트 스트림
역직렬화Deserialization바이트 스트림 → 객체
JSONJavaScript Object Notation가장 널리 사용되는 텍스트 형식
XMLExtensible Markup Language마크업 언어, 과거 주류
ProtobufProtocol BuffersGoogle 오픈소스 고효율 형식
MessagePack-JSON의 바이너리 버전
인코딩Encoding문자 → 바이트
디코딩Decoding바이트 → 문자