MessagePack

シリアライゼーションバイナリマルチ言語高性能コンパクトリアルタイム

ライブラリ

MessagePack

概要

MessagePackは、効率的なバイナリシリアライゼーション形式です。JSONと同様の使いやすさを保ちながら、より高速でコンパクトなデータ表現を実現します。50以上の言語で実装され、高いポータビリティを誇り、IoT、モバイルアプリケーション、リアルタイム通信などでデータサイズの削減が重要な用途で継続的に選ばれています。特にゲーム開発、マイクロサービス、高頻度データ交換が求められる現代のアプリケーションで重要な役割を果たしています。

詳細

MessagePack 2025年版は、JSONと比較して30-50%のサイズ削減と10倍以上の処理速度向上を実現する成熟したバイナリ形式です。JSON形式の制約を排除し、バイナリデータの直接格納、非UTF-8エンコード文字列のサポート、任意の型のマップキー(配列や数値を含む)を可能にします。Redis、Pinterest、SignalRなどの大規模サービスで採用実績を持ち、エンタープライズ環境での信頼性が証明されています。

主な特徴

  • コンパクト性: JSONより30-50%小さいデータサイズ
  • 高速性: 10倍以上高速なシリアライゼーション・デシリアライゼーション
  • 多言語対応: 50以上の言語での実装とクロスプラットフォーム互換性
  • 型の柔軟性: バイナリデータ、非UTF-8文字列、任意型キーをサポート
  • 実績: Redis、Pinterest等の大規模サービスでの採用実績
  • 標準化: 明確な仕様と複数の実装による互換性

メリット・デメリット

メリット

  • JSONと比較して大幅なデータサイズ削減(30-50%)と高速処理(10倍以上)
  • 50以上の言語での実装により、マルチプラットフォーム環境での高い互換性
  • バイナリデータと非UTF-8文字列の直接サポートでデータ表現の制約がない
  • IoTデバイスやモバイル環境でのバッテリー消費とデータ通信費の削減
  • ゲーム、金融取引、リアルタイム通信での実証済みパフォーマンス
  • シンプルな仕様により実装の学習コストが低い

デメリット

  • バイナリ形式のため人間による直接的な読み書きができない
  • JSONと比較してツールやエディタサポートが限定的
  • デバッグ時のデータ内容確認にはデコード処理が必要
  • 配列や整数サイズに実装依存の制限がある
  • 一部のライブラリでは機能や最適化レベルに差がある
  • 小さなデータでは圧縮効果が限定的

参考ページ

書き方の例

基本的なセットアップ

# JavaScript/Node.js
npm install msgpack5
# または
npm install @msgpack/msgpack

# Python
pip install msgpack

# Java
# Maven
<dependency>
    <groupId>org.msgpack</groupId>
    <artifactId>msgpack-core</artifactId>
    <version>0.9.8</version>
</dependency>

# C# (.NET)
dotnet add package MessagePack

# Go
go get github.com/vmihailenco/msgpack/v5

# Rust
# Cargo.toml
[dependencies]
msgpack = "1.0"

JavaScript/Node.js での基本的な使用法

// @msgpack/msgpack を使用(推奨)
import { encode, decode } from '@msgpack/msgpack';

// 基本的なデータ構造
const userData = {
  id: 123,
  name: "田中太郎",
  email: "[email protected]",
  tags: ["admin", "user"],
  metadata: {
    createdAt: new Date(),
    isActive: true,
    balance: 1234.56
  }
};

// エンコード(シリアライゼーション)
const encoded = encode(userData);
console.log('Encoded size:', encoded.length, 'bytes');
console.log('Encoded data:', encoded);

// デコード(デシリアライゼーション)
const decoded = decode(encoded);
console.log('Decoded data:', decoded);

// JSONとのサイズ比較
const jsonString = JSON.stringify(userData);
const jsonBytes = new TextEncoder().encode(jsonString);

console.log('JSON size:', jsonBytes.length, 'bytes');
console.log('MessagePack size:', encoded.length, 'bytes');
console.log('Size reduction:', 
  ((jsonBytes.length - encoded.length) / jsonBytes.length * 100).toFixed(1), '%');

// バイナリデータの処理
const binaryData = {
  filename: "document.pdf",
  content: new Uint8Array([0x25, 0x50, 0x44, 0x46]), // PDF header
  metadata: {
    size: 1024,
    type: "application/pdf"
  }
};

const encodedBinary = encode(binaryData);
const decodedBinary = decode(encodedBinary);
console.log('Binary data preserved:', decodedBinary);

// 配列データの処理
const timeSeriesData = [];
for (let i = 0; i < 1000; i++) {
  timeSeriesData.push({
    timestamp: Date.now() + i * 1000,
    value: Math.random() * 100,
    status: i % 10 === 0 ? 'alert' : 'normal'
  });
}

const encodedTimeSeries = encode(timeSeriesData);
console.log('Time series data encoded:', encodedTimeSeries.length, 'bytes');

Python での高度な使用法

import msgpack
import datetime
import numpy as np
from typing import Any, Dict, List

# カスタムオブジェクトのシリアライゼーション
class CustomObject:
    def __init__(self, name: str, value: int):
        self.name = name
        self.value = value
        self.created_at = datetime.datetime.now()

# カスタムエンコーダー
def custom_encoder(obj):
    if isinstance(obj, CustomObject):
        return {
            '__type__': 'CustomObject',
            'name': obj.name,
            'value': obj.value,
            'created_at': obj.created_at.isoformat()
        }
    elif isinstance(obj, datetime.datetime):
        return {
            '__type__': 'datetime',
            'value': obj.isoformat()
        }
    elif isinstance(obj, np.ndarray):
        return {
            '__type__': 'numpy_array',
            'dtype': str(obj.dtype),
            'shape': obj.shape,
            'data': obj.tobytes()
        }
    raise TypeError(f"Object of type {type(obj)} is not MessagePack serializable")

# カスタムデコーダー
def custom_decoder(obj):
    if isinstance(obj, dict) and '__type__' in obj:
        if obj['__type__'] == 'CustomObject':
            custom_obj = CustomObject(obj['name'], obj['value'])
            custom_obj.created_at = datetime.datetime.fromisoformat(obj['created_at'])
            return custom_obj
        elif obj['__type__'] == 'datetime':
            return datetime.datetime.fromisoformat(obj['value'])
        elif obj['__type__'] == 'numpy_array':
            return np.frombuffer(
                obj['data'], 
                dtype=obj['dtype']
            ).reshape(obj['shape'])
    return obj

# 複雑なデータ構造
complex_data = {
    'users': [
        CustomObject('田中太郎', 100),
        CustomObject('佐藤花子', 200)
    ],
    'timestamp': datetime.datetime.now(),
    'matrix': np.array([[1, 2, 3], [4, 5, 6]]),
    'config': {
        'debug': True,
        'timeout': 30.5,
        'features': ['auth', 'logging', 'metrics']
    }
}

# エンコード
packed_data = msgpack.packb(complex_data, default=custom_encoder)
print(f"Packed size: {len(packed_data)} bytes")

# デコード
unpacked_data = msgpack.unpackb(packed_data, object_hook=custom_decoder, raw=False)
print(f"Unpacked data: {unpacked_data}")

# ストリーミング処理
def stream_processing():
    # ストリーミングエンコード
    packer = msgpack.Packer(default=custom_encoder)
    
    # 大量データのストリーミング処理
    stream_data = []
    for i in range(10000):
        item = {
            'id': i,
            'name': f'Item {i}',
            'timestamp': datetime.datetime.now(),
            'data': np.random.rand(10)
        }
        stream_data.append(packer.pack(item))
    
    # 連結して一つのストリームに
    combined_stream = b''.join(stream_data)
    print(f"Stream size: {len(combined_stream)} bytes")
    
    # ストリーミングデコード
    unpacker = msgpack.Unpacker(object_hook=custom_decoder, raw=False)
    unpacker.feed(combined_stream)
    
    decoded_items = []
    for item in unpacker:
        decoded_items.append(item)
    
    print(f"Decoded {len(decoded_items)} items from stream")
    return decoded_items

stream_processing()

# パフォーマンス測定
import time
import json

def performance_benchmark():
    # テストデータ生成
    test_data = []
    for i in range(10000):
        test_data.append({
            'id': i,
            'name': f'User {i}',
            'email': f'user{i}@example.com',
            'active': i % 2 == 0,
            'score': i * 1.5,
            'tags': [f'tag{j}' for j in range(i % 5)]
        })
    
    # MessagePack エンコード
    start_time = time.time()
    msgpack_data = msgpack.packb(test_data)
    msgpack_encode_time = time.time() - start_time
    
    # MessagePack デコード
    start_time = time.time()
    msgpack_decoded = msgpack.unpackb(msgpack_data, raw=False)
    msgpack_decode_time = time.time() - start_time
    
    # JSON エンコード
    start_time = time.time()
    json_data = json.dumps(test_data).encode()
    json_encode_time = time.time() - start_time
    
    # JSON デコード
    start_time = time.time()
    json_decoded = json.loads(json_data.decode())
    json_decode_time = time.time() - start_time
    
    # 結果出力
    print(f"MessagePack encode: {msgpack_encode_time:.4f}s")
    print(f"MessagePack decode: {msgpack_decode_time:.4f}s")
    print(f"MessagePack size: {len(msgpack_data)} bytes")
    
    print(f"JSON encode: {json_encode_time:.4f}s")
    print(f"JSON decode: {json_decode_time:.4f}s")
    print(f"JSON size: {len(json_data)} bytes")
    
    print(f"Encode speedup: {json_encode_time / msgpack_encode_time:.1f}x")
    print(f"Decode speedup: {json_decode_time / msgpack_decode_time:.1f}x")
    print(f"Size reduction: {(len(json_data) - len(msgpack_data)) / len(json_data) * 100:.1f}%")

performance_benchmark()

Java での使用法

// Maven依存関係を追加済みと仮定
import org.msgpack.core.MessageBufferPacker;
import org.msgpack.core.MessagePack;
import org.msgpack.core.MessageUnpacker;
import org.msgpack.value.Value;
import org.msgpack.value.Variable;

import java.io.IOException;
import java.util.*;

public class MessagePackExample {
    
    // 基本的なシリアライゼーション
    public static void basicSerialization() throws IOException {
        // データの準備
        Map<String, Object> userData = new HashMap<>();
        userData.put("id", 123);
        userData.put("name", "田中太郎");
        userData.put("email", "[email protected]");
        userData.put("active", true);
        userData.put("balance", 1234.56);
        userData.put("tags", Arrays.asList("admin", "user"));
        
        Map<String, Object> metadata = new HashMap<>();
        metadata.put("createdAt", System.currentTimeMillis());
        metadata.put("version", "1.0");
        userData.put("metadata", metadata);
        
        // エンコード
        MessageBufferPacker packer = MessagePack.newDefaultBufferPacker();
        packer.packValue(MessagePack.newDefaultPacker().packValue(userData).build());
        byte[] packed = packer.toByteArray();
        packer.close();
        
        System.out.println("Packed size: " + packed.length + " bytes");
        
        // デコード
        MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(packed);
        Value value = unpacker.unpackValue();
        unpacker.close();
        
        System.out.println("Unpacked value: " + value);
    }
    
    // 高性能なストリーミング処理
    public static void streamingProcessing() throws IOException {
        // 大量データのストリーミング書き込み
        MessageBufferPacker packer = MessagePack.newDefaultBufferPacker();
        
        // 配列の開始
        packer.packArrayHeader(10000);
        
        for (int i = 0; i < 10000; i++) {
            packer.packMapHeader(4);
            packer.packString("id").packInt(i);
            packer.packString("name").packString("User " + i);
            packer.packString("timestamp").packLong(System.currentTimeMillis());
            packer.packString("active").packBoolean(i % 2 == 0);
        }
        
        byte[] packed = packer.toByteArray();
        packer.close();
        
        System.out.println("Stream packed size: " + packed.length + " bytes");
        
        // ストリーミング読み込み
        MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(packed);
        
        int arraySize = unpacker.unpackArrayHeader();
        System.out.println("Processing " + arraySize + " items");
        
        int processedCount = 0;
        while (unpacker.hasNext() && processedCount < arraySize) {
            int mapSize = unpacker.unpackMapHeader();
            Map<String, Object> item = new HashMap<>();
            
            for (int i = 0; i < mapSize; i++) {
                String key = unpacker.unpackString();
                Value value = unpacker.unpackValue();
                item.put(key, value);
            }
            
            processedCount++;
            
            // 100件ごとに進捗表示
            if (processedCount % 100 == 0) {
                System.out.println("Processed: " + processedCount + " items");
            }
        }
        
        unpacker.close();
        System.out.println("Total processed: " + processedCount + " items");
    }
    
    // バイナリデータの処理
    public static void binaryDataHandling() throws IOException {
        MessageBufferPacker packer = MessagePack.newDefaultBufferPacker();
        
        // バイナリデータの作成
        byte[] binaryData = new byte[1024];
        for (int i = 0; i < binaryData.length; i++) {
            binaryData[i] = (byte) (i % 256);
        }
        
        // バイナリデータを含むオブジェクト
        packer.packMapHeader(3);
        packer.packString("filename").packString("data.bin");
        packer.packString("size").packInt(binaryData.length);
        packer.packString("content").packBinaryHeader(binaryData.length);
        packer.addPayload(binaryData);
        
        byte[] packed = packer.toByteArray();
        packer.close();
        
        System.out.println("Binary data packed size: " + packed.length + " bytes");
        
        // バイナリデータの読み込み
        MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(packed);
        
        int mapSize = unpacker.unpackMapHeader();
        String filename = null;
        int size = 0;
        byte[] content = null;
        
        for (int i = 0; i < mapSize; i++) {
            String key = unpacker.unpackString();
            switch (key) {
                case "filename":
                    filename = unpacker.unpackString();
                    break;
                case "size":
                    size = unpacker.unpackInt();
                    break;
                case "content":
                    int binarySize = unpacker.unpackBinaryHeader();
                    content = new byte[binarySize];
                    unpacker.readPayload(content);
                    break;
            }
        }
        
        unpacker.close();
        
        System.out.println("Filename: " + filename);
        System.out.println("Size: " + size);
        System.out.println("Content length: " + (content != null ? content.length : 0));
        System.out.println("Binary data integrity: " + 
            (content != null && content.length == binaryData.length && 
             Arrays.equals(content, binaryData) ? "OK" : "FAILED"));
    }
    
    public static void main(String[] args) {
        try {
            System.out.println("=== Basic Serialization ===");
            basicSerialization();
            
            System.out.println("\n=== Streaming Processing ===");
            streamingProcessing();
            
            System.out.println("\n=== Binary Data Handling ===");
            binaryDataHandling();
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

ウェブアプリケーション統合とリアルタイム通信

// WebSocket + MessagePack でのリアルタイム通信
import { encode, decode } from '@msgpack/msgpack';

class MessagePackWebSocket {
    constructor(url) {
        this.ws = new WebSocket(url);
        this.ws.binaryType = 'arraybuffer';
        this.messageHandlers = new Map();
        
        this.ws.onmessage = (event) => {
            try {
                const data = decode(new Uint8Array(event.data));
                this.handleMessage(data);
            } catch (error) {
                console.error('Failed to decode MessagePack data:', error);
            }
        };
    }
    
    send(type, payload) {
        if (this.ws.readyState === WebSocket.OPEN) {
            const message = {
                type,
                payload,
                timestamp: Date.now()
            };
            
            const encoded = encode(message);
            this.ws.send(encoded);
        }
    }
    
    on(messageType, handler) {
        this.messageHandlers.set(messageType, handler);
    }
    
    handleMessage(data) {
        const handler = this.messageHandlers.get(data.type);
        if (handler) {
            handler(data.payload);
        }
    }
}

// ゲームデータの高速同期
class GameStateSync {
    constructor(websocket) {
        this.ws = websocket;
        this.gameState = {
            players: new Map(),
            entities: new Map(),
            worldState: {}
        };
        
        // プレイヤー状態更新の処理
        this.ws.on('player_update', (data) => {
            this.updatePlayer(data);
        });
        
        // エンティティ状態更新の処理
        this.ws.on('entity_update', (data) => {
            this.updateEntity(data);
        });
        
        // 定期的な状態同期(60FPS)
        setInterval(() => {
            this.syncState();
        }, 1000 / 60);
    }
    
    updatePlayer(playerData) {
        this.gameState.players.set(playerData.id, {
            position: { x: playerData.x, y: playerData.y, z: playerData.z },
            rotation: { x: playerData.rx, y: playerData.ry, z: playerData.rz },
            health: playerData.health,
            timestamp: playerData.timestamp
        });
    }
    
    updateEntity(entityData) {
        this.gameState.entities.set(entityData.id, {
            type: entityData.type,
            position: entityData.position,
            state: entityData.state,
            timestamp: entityData.timestamp
        });
    }
    
    syncState() {
        // 変更された状態のみを送信(差分同期)
        const changedPlayers = [];
        const changedEntities = [];
        
        // プレイヤーの変更を検出
        this.gameState.players.forEach((player, id) => {
            if (player.timestamp > Date.now() - 100) { // 100ms以内の変更
                changedPlayers.push({ id, ...player });
            }
        });
        
        // エンティティの変更を検出
        this.gameState.entities.forEach((entity, id) => {
            if (entity.timestamp > Date.now() - 100) {
                changedEntities.push({ id, ...entity });
            }
        });
        
        if (changedPlayers.length > 0 || changedEntities.length > 0) {
            this.ws.send('state_sync', {
                players: changedPlayers,
                entities: changedEntities,
                timestamp: Date.now()
            });
        }
    }
}

// REST API での MessagePack サポート
class MessagePackAPI {
    constructor(baseUrl) {
        this.baseUrl = baseUrl;
    }
    
    async request(endpoint, options = {}) {
        const url = `${this.baseUrl}${endpoint}`;
        const headers = {
            'Content-Type': 'application/msgpack',
            'Accept': 'application/msgpack',
            ...options.headers
        };
        
        let body = options.body;
        if (body && typeof body === 'object') {
            body = encode(body);
        }
        
        const response = await fetch(url, {
            ...options,
            headers,
            body
        });
        
        if (response.headers.get('content-type')?.includes('application/msgpack')) {
            const arrayBuffer = await response.arrayBuffer();
            return decode(new Uint8Array(arrayBuffer));
        } else {
            return response.json();
        }
    }
    
    async get(endpoint) {
        return this.request(endpoint, { method: 'GET' });
    }
    
    async post(endpoint, data) {
        return this.request(endpoint, {
            method: 'POST',
            body: data
        });
    }
}

// 使用例
const wsClient = new MessagePackWebSocket('wss://game.example.com/ws');
const gameSync = new GameStateSync(wsClient);
const api = new MessagePackAPI('https://api.example.com');

// API使用例
async function loadUserData() {
    try {
        const userData = await api.get('/users/123');
        console.log('User data:', userData);
        
        const updateResult = await api.post('/users/123', {
            name: '田中太郎',
            email: '[email protected]',
            preferences: {
                theme: 'dark',
                notifications: true
            }
        });
        
        console.log('Update result:', updateResult);
    } catch (error) {
        console.error('API error:', error);
    }
}

loadUserData();