프로토콜 버퍼(Protocol Buffers, Protobuf)란?

  • Google에서 개발한 데이터 직렬화 도구입니다.
  • Json이나 XML 같은 다른 직렬화 형식에 비해 작고, 빠르며, 효율적입니다.
  • Protobuf는 주로 원격 프로시저 호출(RPC)에서 사용되며, 특히 gRPC에서 중요한 역할을 합니다.

출처: [https://narup.tistory.com/234](https://narup.tistory.com/234)

위 이미지는 첫번째 json 형태를 직렬화 Protobuf를 이용하여 직렬화 되는 과정을 나타내는 이미지 입니다.

프로토콜 버퍼(Protocol Buffers, Protobuf) 특징

1. 언어 중립적

  • 프로토콜 버퍼 정의는 특정 언어와 독립적입니다. 생성된 소스 코드는 다양한 프로그래밍 언어로 제공됩니다.(java, c++, python 등..)

2. 이진 포맷(데이터를 0, 1의 연속으로 표현하는 방식)

  • 데이터는 이진 포맷으로 직렬화되므로 전송 및 저장이 효율적입니다.

3. 확장 가능(필드 번호 이용)

  • 데이터 구조를 변경하더라도 이전 버전의 구조와 호환성을 유지할 수 있습니다.
  • 확장성과 호환성은 특히 분산 시스템, 마이크로서비스 아키텍처, 또는 단순히 시간이 지나면서 시스템의 요구 사항이 변경되는 경우에 중요한 특성입니다.
  • 프로토콜 버퍼(Protobuf)와 같은 이진 직렬화 포맷들은 확장성을 명확하게 지원하도록 설계되었습니다.
  • 이를 통해 개발자들은 시스템이 성장하고 변화해도 서로 다른 버전의 애플리케이션 간에 데이터를 안전하게 교환할 수 있습니다.
필드 번호란?
  • 프로토콜 버퍼에서는 각 필드에 고유한 번호가 할당됩니다.
  • 필드의 식별자로 사용되며, 각 번호는 직렬화와 역직렬화 과정에서 중요한 역할을 합니다.
  • 일단 번호가 할당되면 번호를 변경해서는 안됩니다.
필드 번호 역할 및 사용 방식

1) 식별자로서의 역할

필드 번호는 해당 필드의 고유 식별자로 동작합니다.
직렬화 시, 필드 이름이 아닌 필드 번호가 사용되어 데이터 스트림에 기록됩니다.
이렇게 함으로써, 데이터 전송 시의 크기가 줄어들게 됩니다.

 

2) 직렬화 및 역직렬화

데이터를 이진 형태로 직렬화 할 때, 필드 번호가 이진 스트림에 함께 기록됩니다.
반대로 데이터를 역직렬화 할 때, 이진 스트림에 기록된 필드 번호를 사용하여 해당 필드의 데이터를 올바르게 복원합니다.

 

3) 확장성 및 호환성

데이터 구조가 시간이 지나면서 변경되더라도 필드 번호는 일정하게 유지됩니다.
즉, 새로운 필드를 추가하거나 기존의 필드를 제거해도 해당 필드 번호는 바뀌지 않습니다.
이렇게 하면, 서로 다른 버전의 프로토콜 버퍼 구조 간에도 데이터의 호환성을 유지할 수 있습니다.

예를 들어, 초기 버전의 메시지 구조가 다음과 같다고 가정합니다.

 

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

시간이 지나면서 'age' 필드가 더 이상 필요하지 않을 때, 그 필드를 제거할 수 있지만 필드 번호 2는 재사용해서는 안됩니다.

message Person{
    string name = 1;
    reserved 2;
}

또는 새로운 필드를 추가할 수도 있습니다.

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

기존 'age'에 할당된 필드 번호 2는 변경되지 않았으므로, 오래된 버전과 새로운 버전모두 호환됩니다.

 

4) 필드 순서의 중요성

필드 번호는 필드의 순서나 위치와는 무관하게 동작합니다.
따라서 메시지 정의에서 필드의 순서가 바뀌더라도 필드 번호는 일정하게 유지되어야 합니다.

4. 정의된 스키마

'.proto' 파일에서 데이터 구조를 정의하게 됩니다.
이 파일은 데이터의 구조와 타입 정보를 포함하며, 이를 바탕으로 코드를 생성할 수 있습니다.

.proto 파일
  • 프로토콜 버퍼(Protobuf) 에서 데이터 구조를 정의하는 곳입니다.
  • 이 파일을 이용해서 특정 언어의 코드를 생성할 수 있습니다.
  • 데이터 교환의 규약 또는 계약처럼 동작하며, 다양한 시스템이나 언어 간의 통신에 있어서 일관된 데이터 구조를 보장합니다.

1) 메시지 정의

 

메시지는 여러 필드를 포함하는 구조체와 같습니다.
각 필드에는 데이터 타입, 필드 이름, 고유한 번호가 지정됩니다.

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

 

2) 필드 타입

 

a. 정수 타입

  • int32, int64 : 순서대로 32비트, 64비트의 일반적인 정수 타입입니다.

b. 실수 타입

  • float, dobule : 순서대로 32비트, 64 비트의 부동소수점 타입입니다.

c. 문자열 및 바이트

  • string : UTF-8로 인코딩된 문자열 타입입니다.
  • bytes : 임의의 바이트 데이터를 포함하는 타입입니다.

d. bool(참, 거짓)

  • bool : 참 또는 거짓 값을 나타내는 타입입니다.

e. 열거형

  • enum : 명명된 값의 집합을 나타내는 타입입니다.

f. 필드 타입

  • 단일 필드
    기본적인 데이터 타입이나 복합 메시지 타입을 직접 사용하여 필드를 정의할 수 있습니다.
message Sample{
    int32 id = 1;
    string name = 2;
}
  • 반복 필드 (Repeated Fields)
    특정 필드 타입의 여러 값을 배열 또는 리스트 형태로 포함시킬 수 있습니다.
message Sample{
    repeated string tags = 1;
}
  • 옵셔널 필드
    Protobuf v2 에서는 optional 키워드를 사용하여 필드가 선택적으로 포함될 수 있음을 나타내야 했지만 Protobuf v3에서는 모든 필드는 기본적으로 선택적(옵셔널) 입니다.
  • 임베디드 메시지

다른 메시지 타입을 필드 타입으로 사용하여 중첩된 복합 구조를 만들 수 있습니다.

message Address{
    string city = 1;
    string state = 2;
}

message Person{
    string name = 1;
    Address address = 2;
}
  • Oneof

여러 필드 중 하나만 값을 가질 수 있을 때 사용하는 구조입니다.

message ContactInfo{
    oneof method{
        string phone_number = 1;
        string email = 2;
        string address = 3;
    }
}

위의 예에서 ContactInfo 메시지는 method라는 oneof 필드를 가지며, 이 필드는 phone_number, email, address 중 하나만 값을 가질 수 있습니다.

 

oneof를 사용하면 어떤 필드 중에서 하나만 값이 있어야 함을 명확하게 나타낼 수 있습니다.
이는 데이터의 무결성을 보장합니다.

 

oneof 내의 필드들은 공유 메모리를 사용하기 때문에 메모리 사용량이 절약됩니다.
즉, 한번에 하나의 필드만 값이 할당되므로, 여러 필드에 동시에 메모리를 할당할 필요가 없습니다.

 

만약 프로그램이 실수로 두 개 이상의 필드에 값을 할당하려고 하면, 마지막으로 할당된 필드의 값만 유지되며, 그 전의 필드 값들은 모두 삭제됩니다.

이를 통해 오류를 방지할 수 있습니다.

 

3) 서비스 정의

.proto 파일에서는 RPC(Remote Procedure Call) 서비스를 정의할 수도 있습니다.
이를 통해 클라이언트와 서버 간의 통신 규약을 명확하게 지정할 수 있습니다.

service UserService{
    rpc CreateUser (UserRequest) returns (UserResponse);
}

message UserRequest{
    string username = 1;
}

message UserResponse{
    int32 user_id = 1;
}

 

4) 코드 생성

.proto 파일을 정의한 후, 프로토콜 버퍼 컴파일러(protoc)를 사용하여 특정 언어로 코드를 생성할 수 있습니다.
예를 들어, Java 용 코드를 생성하려면

protoc --java_out=./ yourfile.proto

이렇게 생성된 코드는 프로토콜 버퍼 메시지의 직렬화, 역직렬화, 데이터 접근 등의 기능을 제공합니다.

 

5) 확장 및 임포트

.proto 파일 내에서 다른 .proto 파일을 임포트하여 정의된 메시지나 타입을 사용하거나 확장할 수 있습니다.

import "other_definitions.proto";

message ExtendedPerson {
    Person base = 1;
    string address = 2;
}
반응형

'개발 > MSA' 카테고리의 다른 글

ConnectionPool 이란? - 설정 방법  (0) 2023.09.18

ConnectionPool 이란?

연결을 재사용 가능한 형태로 미리 생성해두고, 필요할 때 애플리케이션에서 제공하는 매커니즘입니다.
ConnectionPool을 사용하는 주요 이유는 연결을 맺고 해제하는 과정이 상대적으로 많은 자원과 시간을 소모하기 때문입니다.

Connection Pool 동작 과정

  1. 애플리케이션이 시작될 때, Connection Pool은 지정된 수의 연결을 미리 생성합니다.
  2. 애플리케이션은 연결 작업이 필요할 때, Connection Pool로부터 연결을 요청합니다.
  3. Connection Pool은 사용 가능한 연결을 애플리케이션에 제공합니다.
  4. 애플리케이션은 작업이 끝나면 연결을 닫지 않고, 다시 Connection Pool에 반환합니다. 이 연결은 후속 요청에서 재사용됩니다.

Connection Pool의 장단점

장점

  1. 응답 시간 감소

사용자가 웹 애플리케이션에 로그인 요청을 보낼 때마다 새로운 연결을 생성하는 대신, 미리 생선된 연결을 사용하여 즉시 응답을 처리할 수 있습니다.
연결 설정 시간이 제거되므로 전체 응답 시간이 빨라집니다.

  1. 리소스 최적화

연결 풀을 사용하면 동시에 열려있는 연결 수를 제한할 수 있습니다.
이렇게 하면 서버에 과도한 부하가 걸리는 것을 방지하면서 리소스를 효율적으로 활용할 수 있습니다.

  1. 시스템 안정성 향상

특정 서비스에 대한 급증한 요청으로 서비스가 다운되는 경우, 연결 풀의 크기 제한을 사용하여 특정 임계값을 넘지 않게 할 수 있습니다.
이는 서비스 거부 공격(Denial-of-Service attack)과 같은 상황에서도 서비스의 안정성을 유지하는데 도움이 됩니다.

단점

  1. 초기 구성 복잡성

연결 풀의 최소/최대 크기, 유휴 시간, 연결 유효성 검사 등 다양한 설정 값을 조절하고 최적화해야 합니다.
설정이 적절하지 않으면 성능 문제나 안정성 문제가 발생할 수 있습니다.

  1. 잠재적 리소스 낭비

연결 풀의 크기가 너무 크게 설정되면, 실제로 사용되지 않는 많은 연결이 유휴 상태로 남아 있게 되어 리소스가 낭비될 수 있습니다.

  1. 커넥션 누수 위험

애플리케이션 코드에서 연결을 올바르게 반환하지 않으면, 해당 연결이 풀에서 계속 점유되게 됩니다.
이러한 상황이 반복되면 결국 풀에 사용 가능한 연결이 없게 되어 새로운 요청을 처리할 수 없게 됩니다.
이를 커넥션 누수(connection leak)라고 합니다.

  1. 불완전한 연결 상태

연결이 오래동안 유휴 상태로 있거나 네트워크 문제 등으로 인해 연결이 끊어진 상태일 수 있습니다.
이 경우, 애플리케이션이 해당 연결을 사용하려고 하면 오류가 발생할 수 있습니다.
이를 방지하기 위해 연결 유효성 검사 등의 추가 작업이 필요합니다.

Connection Pool 이 주로 사용되는 4가지

  1. DB
  • DB 연결은 생성과 해제에 비용이 많이 들기 때문에, 연결 풀링을 통해 성능을 향상시킬 수 있습니다.
  1. HTTP 연결
  • 웹 서버 또는 웹 서비스에 대한 HTTP(S) 연결을 풀링하여, 동일한 목적지에 대한 반복적인 요청의 성능을 향상시킬 수 있습니다.
  1. 네트워크 소켓 연결
  • 원격 서비스나 미들웨어(ex: message broker) 와의 통신을 위한 일반적인 TCP 또는 UDP 소켓 연결을 풀링할 수 있습니다.
  1. LDAP 연결
  • LDAP 서버와의 통신을 위한 연결을 풀링하여, 사용자 인증 및 권한 부여 쿼리의 성능을 향상시킬 수 있습니다.

Connection Pool 의 주요 설정 값

  1. maxPoolSize (최대 풀 크기)
  • 연결 풀에 보유할 수 있는 최대 연결 수 입니다.
  • 너무 큰 값은 시스템의 자원을 낭비할 수 있으므로, 실제 시스템의 부하에 따라 조절이 필요합니다.
  1. minPoolSize (최소 풀 크기)
  • 연결 풀이 유지해야 하는 최소한의 연결 수 입니다.
  • 시스템이 비활성 상태일 때도 일정 수의 연결을 유지하게 됩니다.
  1. initialPoolSize (초기 풀 크기)
  • 연결 풀이 처음 생성될 때 만들어져야 하는 연결의 수 입니다.
  • minPoolSize 이상, maxPoolSize 이하의 값이 되어야 합니다.
  1. connectionTimeout (연결 시간 초과)
  • 연결을 얻기 위해 대기할 최대 시간입니다.
  • 지정된 시간 안에 연결을 얻지 못하면 오류가 발생합니다.
  1. idleTimeout (유휴 연결 시간 초과)
  • 연결이 유휴 상태로 있을 수 있는 최대 시간입니다. 이 시간을 초과하면 연결은 닫힙니다.
  • 유휴 상태(Idle State)는 어떤 자원이 활성화되어 있찌만 현재 사용되고 있지 않은 상태(대기 중, 사용되지 않는 상태) 입니다.
  1. maxLifetime (최대 연결 수명)
  • Pool 에 있을 수 있는 연결의 최대 수명입니다.
  • 이 시간을 초과하면 연결은 닫힙니다.
  1. validationTimeout (검증 시간 초과)
  • 연결 검증에 대한 최대 대기 시간입니다.
  1. connectionTestQuery (연결 테스트 쿼리)
  • 연결의 유효성을 확인하기 위한 쿼리입니다.
  • 일반적으로 간단한 쿼리 (ex: 'SELECT 1')가 사용됩니다.

ConnectionPool 의 적절한 설정값 구하기

사실상 ConnectionPool 의 설정값을 "이 값으로 하면 최적이다"라고 하는 값은 없습니다.
그러므로 다음과 같은 절차를 따라 적절한 설정값을 구할 수 있습니다.

  1. 시스템 분석
  • 시스템의 특성, 예상 사용자 수, 트래픽 패턴, 데이터베이스와의 통신 패턴 등을 분석합니다.
  1. 기본값 시작
  • 많은 Connection Pool 라이브러리는 합리적인 기본값을 제공합니다. 이를 시작점으로 사용하고, 이후에 조절할 수 있습니다.
  1. 모니터링
  • 초기 설정을 한 후에는 Connection Pool 의 사용 상태를 모니터링 합니다.
  • 풀에서 사용 가능한 연결의 수, 대기 중인 연결 요청의 수, 유휴 연결의 수 등을 확인할 수 있습니다.
  1. 부하 테스트
  • 시스템에 예상 트래픽 이상의 부하를 가하면서 Connection Pool의 동작을 관찰합니다.
  • 이를 통해 최대 연결 수, 연결 시간 초과 등의 설정값을 조절할 수 있습니다.
  1. 조절 및 반복
  • 모니터링 및 부하 테스트의 결과를 바탕으로 설정값을 조절하고, 다시 테스트와 모니터링을 반복합니다.
  1. 유휴 연결 관리
  • 연결이 유휴 상태로 오래 유지되지 않도록 설정합니다.
  • 일정 시간 이상 사용되지 않는 연결은 자동으로 닫히도록 'idleTimeout' 설정을 조절할 수 있습니다.
  1. 문서화
  • 설정값과 그 이유, 테스트 결과 등을 문서화하여 나중에 참조하거나, 다른 팀원과 공유할 수 있도록 합니다.
  1. 정기적인 리뷰
  • 시스템의 환경이나 요구 사항이 변경될 수 있으므로, 주기적으로 Connection Pool 설정을 리뷰하고 필요에 따라 조절합니다.
반응형

'개발 > MSA' 카테고리의 다른 글

프로토콜 버퍼(Protocol Buffers, Protobuf)란?  (0) 2023.09.19

+ Recent posts