← λͺ©λ‘μœΌλ‘œ
WebSocket with Protocol Buffers

졜근 μ‹€μ‹œκ°„ ν˜‘μ—… μ„œλΉ„μŠ€λ₯Ό κ°œλ°œν–ˆλ˜ κ²½ν—˜μ„ ν† λŒ€λ‘œ κΈ°μ‘΄ REST API 기반 μ•„ν‚€ν…μ²˜λ‘œλŠ” λͺ‡ κ°€μ§€ μ‹¬κ°ν•œ ν•œκ³„μ— λŒ€ν•΄ μΈμ§€ν•˜κ²Œ λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

μ‹€μ‹œκ°„ 데이터 동기화가 ν•„μˆ˜μ μΈ ν˜‘μ—… λ„κ΅¬μ—μ„œ μ΄λŸ¬ν•œ ν•œκ³„λŠ” μ‚¬μš©μž κ²½ν—˜μ— 직접적인 영ν–₯을 μ£Όμ—ˆκ³ , 이λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•œ λŒ€μ•ˆμ„ μ°Ύμ•„μ•Ό ν–ˆμŠ΅λ‹ˆλ‹€.

REST API의 근본적인 ν•œκ³„

REST APIλŠ” μ›Ή μ„œλΉ„μŠ€ κ°œλ°œμ— 널리 μ‚¬μš©λ˜λŠ” μ•„ν‚€ν…μ²˜μ΄μ§€λ§Œ, μ‹€μ‹œκ°„ ν˜‘μ—… μ„œλΉ„μŠ€μ—μ„œλŠ” λ‹€μŒκ³Ό 같은 ν•œκ³„μ μ„ λ“œλŸ¬λƒˆμŠ΅λ‹ˆλ‹€.

  • 높은 λ„€νŠΈμ›Œν¬ λΉ„μš©: λ§€ μš”μ²­λ§ˆλ‹€ HTTP 헀더와 같은 λΆ€κ°€ 정보가 ν¬ν•¨λ˜μ–΄ μ‹€μ œ 데이터 외에도 λ§Žμ€ λ„€νŠΈμ›Œν¬ νŠΈλž˜ν”½μ΄ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€. 특히 μž‘μ€ 크기의 μ‹€μ‹œκ°„ μ—…λ°μ΄νŠΈκ°€ 자주 λ°œμƒν•˜λŠ” ν™˜κ²½μ—μ„œ 이 μ˜€λ²„ν—€λ“œλŠ” λ¬΄μ‹œν•  수 μ—†μ—ˆμŠ΅λ‹ˆλ‹€.
  • μ—°κ²° μ˜€λ²„ν—€λ“œ: REST APIλŠ” λ§€ μš”μ²­λ§ˆλ‹€ μƒˆλ‘œμš΄ 연결을 λ§Ίκ³  λŠλŠ” 과정이 ν•„μš”ν•©λ‹ˆλ‹€. μ΄λŸ¬ν•œ μ—°κ²° μ„€μ • λΉ„μš©μ€ μ‹€μ‹œκ°„μ„±μ΄ μ€‘μš”ν•œ ν˜‘μ—… μ„œλΉ„μŠ€μ—μ„œ μ‹¬κ°ν•œ 지연을 μ΄ˆλž˜ν–ˆμŠ΅λ‹ˆλ‹€.

μ΄λŸ¬ν•œ λ¬Έμ œλ“€μ€ λ‹¨μˆœν•œ μ΅œμ ν™”λ‘œλŠ” ν•΄κ²°ν•˜κΈ° μ–΄λ €μ› κ³ , 보닀 근본적인 μ•„ν‚€ν…μ²˜ 변경이 ν•„μš”ν–ˆμŠ΅λ‹ˆλ‹€.

μŠ¬λž™μ—κ²Œ 배우기

해결책을 μ°ΎκΈ° μœ„ν•΄ ν˜‘μ—… 도ꡬ인 μŠ¬λž™(Slack)의 톡신 방식을 λΆ„μ„ν•΄λ³΄μ•˜μŠ΅λ‹ˆλ‹€. μŠ¬λž™μ€ μ›Ήμ†ŒμΌ“μ„ 기반으둜 λ°”μ΄λ„ˆλ¦¬ 데이터λ₯Ό μ£Όκ³ λ°›λŠ” 방식을 μ‚¬μš©ν•˜κ³  μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

μŠ¬λž™μ˜ 톡신 방식은 λ‹€μŒκ³Ό 같은 νŠΉμ§•μ„ λ³΄μ˜€μŠ΅λ‹ˆλ‹€

  • μ›Ήμ†ŒμΌ“μ„ ν†΅ν•œ 지속적인 μ—°κ²° μœ μ§€
  • λ°”μ΄λ„ˆλ¦¬ ν˜•μ‹μ˜ 데이터 μ „μ†‘μœΌλ‘œ νš¨μœ¨μ„± ν–₯상
  • μ‹€μ‹œκ°„ μ–‘λ°©ν–₯ 톡신 지원

λ°”μ΄λ„ˆλ¦¬ 데이터 전솑 방식이 JSON 같은 ν…μŠ€νŠΈ 기반 ν˜•μ‹λ³΄λ‹€ νš¨μœ¨μ μ΄λΌλŠ” 점이 ν₯λ―Έλ‘œμ› μŠ΅λ‹ˆλ‹€.

μ‹œλ„ν•΄λ³΄κΈ°

μŠ¬λž™μ˜ μ ‘κ·Ό λ°©μ‹μ—μ„œ μ˜κ°μ„ λ°›μ•„, μ›Ήμ†ŒμΌ“μ„ 톡신 μ±„λ„λ‘œ μ‚¬μš©ν•˜κ³  Protocol Buffersλ₯Ό 데이터 직렬화 ν˜•μ‹μœΌλ‘œ ν™œμš©ν•˜λŠ” 것을 고민해보고 ν…ŒμŠ€νŠΈ ν•΄λ³΄κΈ°λ‘œ ν–ˆμŠ΅λ‹ˆλ‹€.

ν…ŒμŠ€νŠΈ ν”„λ‘œμ νŠΈμ˜ ꡬ성 μš”μ†ŒλŠ” λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€

  • ν΄λΌμ΄μ–ΈνŠΈ: React 기반 μ›Ή μ• ν”Œλ¦¬μΌ€μ΄μ…˜
  • μ›Ήμ†ŒμΌ“ μ„œλ²„: ν΄λΌμ΄μ–ΈνŠΈμ™€ 지속적인 연결을 μœ μ§€ν•˜λŠ” Node.js μ„œλ²„
  • gRPC μ„œλ²„: λ‚΄λΆ€ μ„œλΉ„μŠ€ 및 데이터 처리λ₯Ό λ‹΄λ‹Ήν•˜λŠ” μ„œλ²„

μ—¬κΈ°μ„œ μ€‘μš”ν•œ 점은 톡신 채널과 데이터 ν˜•μ‹μ˜ λΆ„λ¦¬μž…λ‹ˆλ‹€:

  1. 톡신 채널: ν΄λΌμ΄μ–ΈνŠΈμ™€ μ„œλ²„ κ°„μ—λŠ” μ›Ήμ†ŒμΌ“ 연결을 μ‚¬μš©ν•©λ‹ˆλ‹€. 이λ₯Ό 톡해 지속적인 연결을 μœ μ§€ν•˜κ³  μ–‘λ°©ν–₯ 톡신이 κ°€λŠ₯ν•΄μ§‘λ‹ˆλ‹€.
  1. 데이터 ν˜•μ‹: μ›Ήμ†ŒμΌ“μ„ 톡해 μ „μ†‘λ˜λŠ” λ°μ΄ν„°λŠ” Protocol Buffers둜 μ§λ ¬ν™”λœ λ°”μ΄λ„ˆλ¦¬ ν˜•μ‹μž…λ‹ˆλ‹€. μ΄λŠ” JSON보닀 효율적인 데이터 전솑을 κ°€λŠ₯ν•˜κ²Œ ν•©λ‹ˆλ‹€.
  1. λ‚΄λΆ€ μ„œλΉ„μŠ€ 톡신: μ›Ήμ†ŒμΌ“ μ„œλ²„μ™€ λ‹€λ₯Έ λ°±μ—”λ“œ μ„œλΉ„μŠ€ κ°„μ—λŠ” gRPCλ₯Ό μ‚¬μš©ν•˜μ—¬ 효율적으둜 ν†΅μ‹ ν•©λ‹ˆλ‹€.

이 κ΅¬μ‘°λŠ” λΈŒλΌμš°μ €μ—μ„œ 직접 gRPCλ₯Ό μ‚¬μš©ν•  수 μ—†λŠ” μ œμ•½μ„ μ›Ήμ†ŒμΌ“μœΌλ‘œ μš°νšŒν•˜λ©΄μ„œλ„, Protocol Buffers의 νš¨μœ¨μ„±μ„ ν™œμš©ν•  수 있게 ν•΄μ€λ‹ˆλ‹€.

Protocol Buffers

Protocol BuffersλŠ” κ΅¬κΈ€μ—μ„œ κ°œλ°œν•œ μ–Έμ–΄ 쀑립적이고 ν”Œλž«νΌ 쀑립적인 직렬화 λ©”μ»€λ‹ˆμ¦˜μœΌλ‘œ, λ‹€μŒκ³Ό 같은 νŠΉμ§•μ„ κ°€μ§‘λ‹ˆλ‹€

  1. μŠ€ν‚€λ§ˆ 기반 μ ‘κ·Ό 방식: .proto νŒŒμΌμ— λͺ…ν™•ν•œ 데이터 ꡬ쑰λ₯Ό μ •μ˜ν•©λ‹ˆλ‹€.
  2. 효율적인 λ°”μ΄λ„ˆλ¦¬ 인코딩: ν…μŠ€νŠΈ 기반 ν˜•μ‹λ³΄λ‹€ 크기가 μž‘κ³  처리 속도가 λΉ λ¦…λ‹ˆλ‹€.
  3. μ–Έμ–΄ 쀑립성: λ‹€μ–‘ν•œ ν”„λ‘œκ·Έλž˜λ° μ–Έμ–΄μ—μ„œ μ‚¬μš©ν•  수 μžˆλŠ” μ½”λ“œλ₯Ό μžλ™ μƒμ„±ν•©λ‹ˆλ‹€.

μ•„λž˜λŠ” ν…ŒμŠ€νŠΈ ν”„λ‘œμ νŠΈμ— μ‚¬μš©λ˜λŠ” λ©”μ‹œμ§€ νƒ€μž…μ„ μ •μ˜ν•œ .proto νŒŒμΌμž…λ‹ˆλ‹€.

syntax = "proto3";

package user;

service UserService {
  rpc GetUser (UserRequest) returns (User) {}
  rpc ListUsers (Empty) returns (UserList) {}
}

message Empty {}

message UserRequest {
  string id = 1;
}

message User {
  string id = 1;
  string name = 2;
  string email = 3;
  int32 age = 4;
  string role = 5;
}

message UserList {
  repeated User users = 1;
}

이 .proto νŒŒμΌμ€ ν΄λΌμ΄μ–ΈνŠΈμ™€ μ„œλ²„ μ–‘μͺ½μ—μ„œ κ³΅μœ λ˜μ–΄, μΌκ΄€λœ λ©”μ‹œμ§€ ꡬ쑰λ₯Ό 보μž₯ν•©λ‹ˆλ‹€.

κ΅¬ν˜„ 세뢀사항

ν΄λΌμ΄μ–ΈνŠΈ μΈ‘ κ΅¬ν˜„ (React)

ν΄λΌμ΄μ–ΈνŠΈμ—μ„œλŠ” μ›Ήμ†ŒμΌ“ 연결을 μ„€μ •ν•˜κ³ , Protocol Buffersλ₯Ό μ‚¬μš©ν•˜μ—¬ 데이터λ₯Ό 직렬화/μ—­μ§λ ¬ν™”ν•˜λŠ” μ½”λ“œλ₯Ό κ΅¬ν˜„ν–ˆμŠ΅λ‹ˆλ‹€

import * as protobuf from "protobufjs";

// μ‚¬μš©μž νƒ€μž… μ •μ˜
export interface User {
  id: string;
  name: string;
  email: string;
  age: number;
  role: string;
}

// λ°”μ΄λ„ˆλ¦¬ 데이터 역직렬화 ν•¨μˆ˜
private deserializeMessage(buffer: Uint8Array, messageType: string): any {
  try {
    // Protobuf λ””μ½”λ”©
    const decodedMessage = MessageType.decode(buffer);
    // μžλ°”μŠ€ν¬λ¦½νŠΈ 객체둜 λ³€ν™˜
    return MessageType.toObject(decodedMessage, {
      longs: String,
      enums: String,
      bytes: String,
    });
  } catch (error) {
    console.error(`λ©”μ‹œμ§€ 역직렬화 쀑 였λ₯˜(${messageType}):`, error);
  }
}
export class WebSocketClient {
  // getUserById μš”μ²­ μ˜ˆμ‹œ
  public getUserById(userId: string): Promise<User> {
    return new Promise((resolve, reject) => {
      if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
        reject(new Error("WebSocket이 μ—°κ²°λ˜μ–΄ μžˆμ§€ μ•ŠμŠ΅λ‹ˆλ‹€"));
        return;
      }

      const requestType = new Uint8Array([1]); // μš”μ²­ νƒ€μž… 1 (μœ μ € 정보 μš”μ²­)
      const userIdBytes = new TextEncoder().encode(userId);
      const requestData = new Uint8Array(
        requestType.length + userIdBytes.length
      );
      requestData.set(requestType);
      requestData.set(userIdBytes, requestType.length);

      this.messageCallbacks.set(1, (data) => {
        resolve(data as User);
      });

      this.socket.send(requestData);
    });
  }
}

μ„œλ²„ κ΅¬ν˜„

μ›Ήμ†ŒμΌ“ μ„œλ²„λŠ” ν΄λΌμ΄μ–ΈνŠΈμ™€μ˜ 연결을 κ΄€λ¦¬ν•˜κ³ , Protocol Buffers둜 μ§λ ¬ν™”λœ λ©”μ‹œμ§€λ₯Ό μ²˜λ¦¬ν•©λ‹ˆλ‹€.
그리고 ν•„μš”μ— 따라 gRPCλ₯Ό 톡해 λ°±μ—”λ“œ μ„œλΉ„μŠ€μ™€ ν†΅μ‹ ν•©λ‹ˆλ‹€.

import * as grpc from "@grpc/grpc-js";
import * as protoLoader from "@grpc/proto-loader";
import * as path from "path";
import * as protobuf from "protobufjs";
import * as WebSocket from "ws";

interface User {
  id: string;
  name: string;
  email: string;
  age: number;
  role: string;
}

const PROTO_PATH = path.resolve(__dirname, "../../proto/user.proto");
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
  keepCase: true,
  longs: String,
  enums: String,
  defaults: true,
  oneofs: true,
});

const proto = grpc.loadPackageDefinition(packageDefinition);
let protoRoot: protobuf.Root | null = null;

try {
  protoRoot = protobuf.loadSync(PROTO_PATH);
  console.log("Protobuf μ •μ˜κ°€ μ„±κ³΅μ μœΌλ‘œ λ‘œλ“œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.");
} catch (error) {
  console.error("Protobuf μ •μ˜ λ‘œλ“œ 쀑 였λ₯˜:", error);
}

function serializeMessage(message: any, messageType: string): Buffer {
  try {
    if (!protoRoot) {
      console.warn("Protobuf μ •μ˜κ°€ λ‘œλ“œλ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€. JSON으둜 ν΄λ°±ν•©λ‹ˆλ‹€.");
      return Buffer.from(JSON.stringify(message), "utf8");
    }

    const MessageType = protoRoot.lookupType("user." + messageType);

    const verificationError = MessageType.verify(message);
    if (verificationError) {
      console.warn(
        `λ©”μ‹œμ§€ 검증 였λ₯˜ (${messageType}): ${verificationError}. JSON으둜 ν΄λ°±ν•©λ‹ˆλ‹€.`
      );
      return Buffer.from(JSON.stringify(message), "utf8");
    }

    const protoMessage = MessageType.create(message);
    const encodedMessage = MessageType.encode(protoMessage).finish();

    return Buffer.from(encodedMessage);
  } catch (error) {
    console.error(`λ©”μ‹œμ§€ 직렬화 쀑 였λ₯˜ (${messageType}):`, error);
    return Buffer.from(JSON.stringify(message), "utf8");
  }
}

function startWebSocketServer() {
  const wss = new WebSocket.Server({ port: 8080 });

  wss.on("connection", (ws) => {
    console.log("μƒˆλ‘œμš΄ WebSocket μ—°κ²° 섀정됨");

    ws.on("message", async (message) => {
      try {
        const requestBuffer = Buffer.from(message as Buffer);
        const requestType = requestBuffer.readUInt8(0);
        const requestData = requestBuffer.subarray(1); // λ‚˜λ¨Έμ§€λŠ” μš”μ²­ 데이터

        const client = new (proto as any).user.UserService(
          "localhost:50051",
          grpc.credentials.createInsecure()
        );

        let response: Buffer;

        switch (requestType) {
          case 1: // μ‚¬μš©μž 정보 μš”μ²­
            const userId = requestData.toString("utf8");
            console.log(`μ‚¬μš©μž ID(${userId}) 정보 μš”μ²­ μˆ˜μ‹ `);

            const user = await getUserById(client, userId);
            const userBinary = serializeMessage(user, "User");
            response = Buffer.concat([Buffer.from([1]), userBinary]);

            break;

          case 2: // μ‚¬μš©μž λͺ©λ‘ μš”μ²­
            console.log("μ‚¬μš©μž λͺ©λ‘ μš”μ²­ μˆ˜μ‹ ");

            const userList = await listAllUsers(client);
            const userListBinary = serializeMessage(userList, "UserList");
            response = Buffer.concat([Buffer.from([2]), userListBinary]);

            break;

          default:
            console.warn(`μ•Œ 수 μ—†λŠ” μš”μ²­ νƒ€μž…: ${requestType}`);
            response = Buffer.from([0, 0]);
            break;
        }

        ws.send(response);
      } catch (error) {
        console.error("λ©”μ‹œμ§€ 처리 쀑 였λ₯˜ λ°œμƒ:", error);
        const errorResponse = Buffer.concat([
          Buffer.from([255]), // 255λŠ” μ—λŸ¬ 응닡 νƒ€μž…
          Buffer.from(JSON.stringify({ error: "μš”μ²­ 처리 μ‹€νŒ¨" })),
        ]);
        ws.send(errorResponse);
      }
    });
  });

  console.log("WebSocket μ„œλ²„κ°€ 포트 8080μ—μ„œ μ‹€ν–‰ μ€‘μž…λ‹ˆλ‹€");
  return wss;
}

데이터 흐름 μ΄ν•΄ν•˜κΈ°

이 μ•„ν‚€ν…μ²˜μ—μ„œ 데이터가 μ–΄λ–»κ²Œ 흐λ₯΄λŠ”μ§€ μ‚΄νŽ΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

  1. ν΄λΌμ΄μ–ΈνŠΈκ°€ λ©”μ‹œμ§€λ₯Ό μ „μ†‘ν•˜λ €λ©΄

- Protocol Buffers둜 λ©”μ‹œμ§€λ₯Ό 인코딩
- λ©”μ‹œμ§€ νƒ€μž… μ‹λ³„μžλ₯Ό μΆ”κ°€
- μ›Ήμ†ŒμΌ“μ„ 톡해 μ„œλ²„λ‘œ 전솑

  1. μ›Ήμ†ŒμΌ“ μ„œλ²„λŠ”

- λ©”μ‹œμ§€ νƒ€μž…μ„ 식별
- Protocol Buffers둜 λ©”μ‹œμ§€λ₯Ό λ””μ½”λ”©
- gRPC ν΄λΌμ΄μ–ΈνŠΈλ₯Ό 톡해 λ°±μ—”λ“œ μ„œλΉ„μŠ€μ— μš”μ²­
- μ›Ήμ†ŒμΌ“μ„ 톡해 ν΄λΌμ΄μ–ΈνŠΈμ— 전솑

이 νλ¦„μ—μ„œ μ€‘μš”ν•œ 점은

  • 톡신 채널은 μ›Ήμ†ŒμΌ“μž…λ‹ˆλ‹€. μ΄λŠ” 지속적인 연결을 μœ μ§€ν•˜λ©° μ–‘λ°©ν–₯ 톡신을 κ°€λŠ₯ν•˜κ²Œ ν•©λ‹ˆλ‹€.
  • 데이터 ν˜•μ‹μ€ Protocol Buffersμž…λ‹ˆλ‹€. μ΄λŠ” 효율적인 λ°”μ΄λ„ˆλ¦¬ 직렬화λ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€.
  • λ‚΄λΆ€ μ„œλΉ„μŠ€ 톡신은 gRPCλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€. μ΄λŠ” μ„œλΉ„μŠ€ κ°„ 톡신에 μ΅œμ ν™”λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.

νŒ¨ν‚· μˆ˜μ€€ μ΅œμ ν™”

이 μ•„ν‚€ν…μ²˜κ°€ κΈ°μ‘΄ REST API λŒ€λΉ„ μ–΄λ–€ 이점을 κ°€μ§€λŠ”μ§€ μ‚΄νŽ΄λ³΄μ•˜μŠ΅λ‹ˆλ‹€.

REST API vs WebSocket

REST APIλŠ” 각 μš”μ²­λ§ˆλ‹€ μƒˆλ‘œμš΄ TCP 연결을 μ„€μ •ν•˜κ±°λ‚˜, keep-aliveλ₯Ό μ‚¬μš©ν•˜λ”λΌλ„ μš”μ²­/응닡 νŒ¨ν„΄μœΌλ‘œ μΈν•œ μ˜€λ²„ν—€λ“œκ°€ μžˆμŠ΅λ‹ˆλ‹€.
λ¬Όλ‘  κΈ°μ‘΄ HTTP/1.1 ν”„λ‘œν† μ½œμ—μ„œλŠ” μ΄λŸ¬ν•œ μ˜€λ²„ν—€λ“œλ₯Ό HTTP/2.0 ν”„λ‘œν† μ½œλ‘œ κ°œμ„ ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

REST API (HTTP/1.1)의 μ—°κ²° νŒ¨ν„΄:

  1. TCP μ—°κ²° μ„€μ • (3-way handshake)
  2. HTTP μš”μ²­ 전솑
  3. HTTP 응닡 μˆ˜μ‹ 
  4. μ—°κ²° μ’…λ£Œ λ˜λŠ” μœ μ§€

μ΄λŸ¬ν•œ νŒ¨ν„΄μ€ 각 μš”μ²­λ§ˆλ‹€ μ΅œμ†Œ 1번의 왕볡 μ‹œκ°„(RTT)이 ν•„μš”ν•˜λ©°, 헀더 쀑볡과 같은 μ˜€λ²„ν—€λ“œκ°€ λ°œμƒν•©λ‹ˆλ‹€.

WebSocket의 μ—°κ²° νŒ¨ν„΄:

  1. 초기 TCP μ—°κ²° 및 WebSocket ν•Έλ“œμ…°μ΄ν¬ (ν•œ 번만 μˆ˜ν–‰)
  2. μ–‘λ°©ν–₯ λ©”μ‹œμ§€ κ΅ν™˜ (λ³„λ„μ˜ μ—°κ²° μ„€μ • μ—†μŒ)
  3. μ„Έμ…˜ μ’…λ£Œ μ‹œ μ—°κ²° μ’…λ£Œ

JSON vs Protocol Buffers

λ™μΌν•œ 정보λ₯Ό ν‘œν˜„ν•  λ•Œ, Protocol BuffersλŠ” JSON보닀 νš¨μœ¨μ μž…λ‹ˆλ‹€.

Protocol Buffersκ°€ 크기λ₯Ό μ€„μ΄λŠ” 핡심 μ›λ¦¬λŠ” λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€

  1. ν•„λ“œ νƒœκ·Έ μ‚¬μš©: JSON의 λ¬Έμžμ—΄ ν‚€ λŒ€μ‹  숫자 νƒœκ·Έλ₯Ό μ‚¬μš©ν•˜μ—¬ ν•„λ“œλ₯Ό μ‹λ³„ν•©λ‹ˆλ‹€.
  2. κ°€λ³€ 길이 μ •μˆ˜ 인코딩(Varint): μž‘μ€ μˆ«μžλŠ” 적은 λ°”μ΄νŠΈλ₯Ό, 큰 μˆ«μžλŠ” 더 λ§Žμ€ λ°”μ΄νŠΈλ₯Ό μ‚¬μš©ν•˜μ—¬ 곡간을 μ ˆμ•½ν•©λ‹ˆλ‹€.
  3. λ¬Έμžμ—΄ νš¨μœ¨μ„±: 길이 접두사λ₯Ό μ‚¬μš©ν•˜μ—¬ λ¬Έμžμ—΄ 경계λ₯Ό λͺ…ν™•νžˆ ν•©λ‹ˆλ‹€.
  4. λΆˆν•„μš”ν•œ ꡬ문 μƒλž΅: JSON의 λ”°μ˜΄ν‘œ, 콜둠, μ€‘κ΄„ν˜Έ, μ‰Όν‘œ λ“±μ˜ ꡬ문 μ˜€λ²„ν—€λ“œκ°€ μ—†μŠ΅λ‹ˆλ‹€.

Protocol Buffers의 μž₯점

Protocol BuffersλŠ” λ‹¨μˆœν•œ λ°”μ΄λ„ˆλ¦¬ 직렬화(예: MessagePack, BSON)와 비ꡐ해도 μ—¬λŸ¬ κ°€μ§€ μ€‘μš”ν•œ μž₯점을 μ œκ³΅ν•©λ‹ˆλ‹€.

1. μŠ€ν‚€λ§ˆ 기반 섀계

Protocol BuffersλŠ” λͺ…ν™•ν•˜κ²Œ μ •μ˜λœ μŠ€ν‚€λ§ˆλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€.

message Person {
  string name = 1;
  int32 age = 2;
  repeated string hobbies = 3;
}

이 μŠ€ν‚€λ§ˆλŠ”

  • 데이터 ꡬ쑰λ₯Ό λͺ…ν™•ν•˜κ²Œ λ¬Έμ„œν™”
  • νƒ€μž… μ•ˆμ „μ„± 보μž₯
  • μ½”λ“œ 생성을 ν†΅ν•œ 개발 νŽΈμ˜μ„± 제곡
  • 버전 관리 μš©μ΄μ„±

일반적인 λ°”μ΄λ„ˆλ¦¬ μ§λ ¬ν™”λŠ” μŠ€ν‚€λ§ˆκ°€ μ—†κ±°λ‚˜ 덜 λͺ…μ‹œμ μ΄μ–΄μ„œ νƒ€μž… 였λ₯˜μ™€ 버전 ν˜Έν™˜μ„± λ¬Έμ œκ°€ λ°œμƒν•˜κΈ° μ‰½μŠ΅λ‹ˆλ‹€.

2. 효율적인 인코딩 μ „λž΅

Protocol BuffersλŠ” μ΅œμ ν™”λœ 인코딩 μ „λž΅μ„ μ‚¬μš©ν•©λ‹ˆλ‹€

  • ν•„λ“œ 번호 체계: ν•„λ“œλͺ… λŒ€μ‹  숫자λ₯Ό μ‚¬μš©ν•˜μ—¬ 곡간을 μ ˆμ•½ν•©λ‹ˆλ‹€.
  • κ°€λ³€ 길이 μ •μˆ˜(Varint): μž‘μ€ μˆ«μžλŠ” 적은 λ°”μ΄νŠΈλ₯Ό μ‚¬μš©ν•˜μ—¬ 곡간을 μ ˆμ•½ν•©λ‹ˆλ‹€.

3. ν•˜μœ„ ν˜Έν™˜μ„± 보μž₯

Protocol BuffersλŠ” μ²˜μŒλΆ€ν„° ν•˜μœ„ ν˜Έν™˜μ„±μ„ κ³ λ €ν•œ 섀계λ₯Ό κ°€μ§€κ³  μžˆμŠ΅λ‹ˆλ‹€:

  • μƒˆλ‘œμš΄ ν•„λ“œλ₯Ό 좔가해도 κΈ°μ‘΄ ν΄λΌμ΄μ–ΈνŠΈλŠ” ν•΄λ‹Ή ν•„λ“œλ₯Ό λ¬΄μ‹œν•˜κ³  계속 μž‘λ™
  • ν•„λ“œ λ²ˆν˜ΈλŠ” ν•œ 번 ν• λ‹Ήλ˜λ©΄ λ³€κ²½ν•˜μ§€ μ•ŠλŠ” κ·œμΉ™
  • 선택적 ν•„λ“œμ™€ ν•„μˆ˜ ν•„λ“œμ˜ κ°œλ…

μ΄λŸ¬ν•œ νŠΉμ„±μ€ API 버전 관리가 λ³΅μž‘ν•œ λŒ€κ·œλͺ¨ μ‹œμŠ€ν…œμ—μ„œ 특히 μ€‘μš”ν•©λ‹ˆλ‹€.

마치며

REST API의 ν•œκ³„λ₯Ό κ·Ήλ³΅ν•˜κΈ° μœ„ν•΄ μ›Ήμ†ŒμΌ“μ„ 톡신 μ±„λ„λ‘œ, Protocol Buffersλ₯Ό 데이터 ν˜•μ‹μœΌλ‘œ μ‚¬μš©ν•˜λŠ” μƒˆλ‘œμš΄ μ•„ν‚€ν…μ²˜λ₯Ό ν…ŒμŠ€νŠΈν•΄λ³΄μ•˜μŠ΅λ‹ˆλ‹€.

μŠ¬λž™κ³Ό 같이 데이터가 자주 λ³€κ²½λ˜λŠ” ν™˜κ²½μ—μ„œλŠ” 이 μ•„ν‚€ν…μ²˜κ°€ 더 λ‚˜μ€ μ„±λŠ₯을 보여쀄 수 μžˆλŠ” 것이 κ²€μ¦λ˜μ—ˆμœΌλ―€λ‘œ 상황에 따라 쒋은 μ „λž΅μ΄ 되리라 μƒκ°ν•©λ‹ˆλ‹€.

ν…ŒμŠ€νŠΈ ν”„λ‘œμ νŠΈλŠ” κΉƒν—ˆλΈŒμ—μ„œ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.