Prometheus Remote-Write 2.0 사양

Prometheus Remote-Write 2.0 사양

프로메테우스 Remote-Write 프로토콜 2.0의 공식 사양이에요. 1.0과 비교해 프로토콜과 의미에 약간의 변경을 더했어요. 새 Protobuf 메시지를 도입해 더 많은 사용 사례와 광범위한 채택, 그리고 성능·비용 절감을 가능하게 했고, 신뢰성을 위해 필수 응답 헤더도 추가했어요. 1.0의 Protobuf 메시지는 deprecated 처리됐어요.

실험 상태(릴리스 후보)인 문서지만, 네이티브 히스토그램, 시작 타임스탬프, 메타데이터, exemplar 등 모던 프로메테우스 데이터 모델을 반영한 다음 세대 사양이에요. Remote-Write를 구현하거나 차세대 수신기와 연동할 때 참고하는 문서랍니다.

출처: 문서

본문

  • 버전: 2.0-rc.4
  • 상태: 실험(Experimental)
  • 날짜: 2024년 5월

Remote-Write 사양은 일반적으로 프로메테우스와 호환 원격 쓰기 전송자가 프로메테우스 또는 호환 수신자에게 데이터를 보내는 표준을 문서화하기 위한 것이에요.

이 문서는 프로메테우스 Remote-Write API의 두 번째 버전을 정의하기 위한 것이며, 프로토콜과 의미에 약간의 변경이 있어요. 이 두 번째 버전은 더 많은 사용 사례와 성능·비용 절감 위에서의 광범위한 채택을 가능하게 하는 새 기능을 가진 새 Protobuf 메시지를 추가해요. 또한 1.0 Remote-Write 사양의 이전 Protobuf 메시지를 deprecated 처리하고, 신뢰성을 위해 필수 X-Prometheus-Remote-Write-*-Written HTTP 응답 헤더를 추가해요. 마지막으로 이 사양은 기존 기본 콘텐츠 협상 요청 헤더를 사용해 (단일 엔드포인트에서도) 역호환 전송자와 수신자를 구현하는 방법을 제시해요. 더 고급스러운 자동 콘텐츠 협상 메커니즘이 필요하면 미래 마이너 버전에서 올 수 있어요. 2.0 사양의 근거는 공식 제안을 참조하세요.

이 문서의 "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", "OPTIONAL" 키워드는 RFC 2119에서 설명된 대로 해석해야 해요.

참고: 이것은 Remote-Write 2.0 사양의 릴리스 후보예요. 이는 이 사양이 현재 실험 상태라는 뜻이에요 - 큰 변경은 예상되지 않지만, 초기 채택자의 피드백에 기반해 필요하면 호환성을 깨뜨릴 권리를 보유해요. 잠재적 피드백, 질문, 제안은 열린 제안이 있는 PR에 주석으로 추가해야 해요.

서론 (Introduction)

배경 (Background)

Remote-Write 프로토콜은 전송자에서 수신자로 샘플을 손실 없이 실시간으로 안정적으로 전파할 수 있게 설계된 것이에요.

Remote-Write 프로토콜은 무상태(stateless)로 설계됩니다. 메시지 간 통신이 엄격히 없어요. 따라서 이 프로토콜은 "스트리밍"으로 간주되지 않아요. 스트리밍 효과를 얻으려면 HTTP/1.1이나 HTTP/2를 사용해 같은 연결로 여러 메시지를 보내야 해요. gRPC 같은 "화려한" 기술도 고려됐지만, 당시에는 널리 채택되지 않았고 AWS EC2 ELB 같은 로드 밸런서 뒤에서는 gRPC 서비스를 인터넷에 노출하기 어려웠어요.

Remote-Write 프로토콜은 일괄처리(batching) 기회를 포함해요. 예를 들어 단일 요청에 서로 다른 시리즈의 여러 샘플을 보낼 수 있어요. 같은 시리즈의 여러 샘플을 같은 요청으로 보내는 것은 흔하지 않을 것으로 예상되지만, Protobuf Message에서 이를 지원해요.

준수 테스트는 다음에서 찾을 수 있어요:

용어 (Glossary)

이 문서에서 다음 정의를 따릅니다:

  • Remote-Write는 이 프로메테우스 프로토콜의 이름
  • Protocol은 클라이언트와 서버가 메트릭을 전송 가능하게 하는 통신 사양
  • Protobuf Message(또는 Proto Message)는 이 프로토콜의 데이터 구조의 콘텐츠 타입 정의를 가리켜요. 사양은 Google 프로토콜 버퍼("protobuf")를 전용으로 사용하므로, 스키마는 "proto" 파일에 정의되고 단일 Protobuf "message"로 표현돼요
  • Wire Format은 데이터가 와이어(즉 네트워크) 위를 이동할 때의 형식. Remote-Write의 경우 항상 압축된 이진 protobuf 형식
  • Sender는 Remote-Write 데이터를 보내는 것
  • Receiver는 Remote-Write 데이터를 받는(쓰는) 것. Written의 의미는 Receiver에 달려 있으며, 예를 들어 보통 받은 데이터를 데이터베이스에 저장하는 것을 의미하지만 단지 검증, 분리, 강화만 의미할 수도 있어요
  • WrittenReceiver가 수신해 수락하는 데이터를 가리켜요. 이를 영구 저장소에 수집했는지, WAL에 썼는지 등은 Receiver에 달려 있어요. 유일한 구별은 Receiver가 이 데이터를 오류 응답으로 명시적으로 거부하기보다 수락했다는 것이에요
  • Sample은 (시작 타임스탬프, 타임스탬프, 값)의 삼중항
  • Histogram은 (시작 타임스탬프, 타임스탬프, 히스토그램 값)의 삼중항
  • Label은 (key, value)의 쌍
  • Series는 고유한 레이블 세트로 식별되는 샘플(또는 히스토그램) 목록

정의 (Definitions)

프로토콜 (Protocol)

Remote-Write 프로토콜은 요청 본문을 Google Protocol Buffers로 직렬화한 다음 압축한 RPC로 구성되어야(MUST) 해요.

protobuf 직렬화는 다음 Protobuf Message 중 하나를 사용해야(MUST) 해요:

  • Remote-Write 1.0 사양에서 도입된 prometheus.WriteRequest. 2.0부터 이 메시지는 deprecated예요. 호환성 이유로만 사용해야(SHOULD) 해요. 전송자와 수신자는 prometheus.WriteRequest를 지원하지 않을 수(MAY NOT) 있어요
  • 이 사양에서 도입하고 아래에서 정의된 io.prometheus.write.v2.Request. 전송자와 수신자는 가능하면 이 메시지를 사용해야(SHOULD) 해요. 전송자와 수신자는 io.prometheus.write.v2.Request를 지원해야(MUST) 해요

Protobuf Message는 이진 Wire Format을 사용해야(MUST) 해요. 그런 다음 Google의 Snappy로 압축해야(MUST) 해요. Snappy의 블록 형식을 사용해야(MUST) 하고 프레임 형식은 사용해서는 안(MUST NOT) 돼요.

전송자는 HTTP POST 요청 본문에 직렬화·압축된 Protobuf Message를 보내고, 제공된 URL 경로의 HTTP를 통해 수신자에게 보내야(MUST) 해요. 수신자는 메트릭을 받을 어떤 HTTP URL 경로라도 지정할 수(MAY) 있어요.

전송자는 HTTP 요청과 함께 다음 예약 헤더를 보내야(MUST) 해요:

  • Content-Encoding
  • Content-Type
  • X-Prometheus-Remote-Write-Version
  • User-Agent

전송자는 사용자가 사용자 정의 HTTP 헤더를 추가하도록 허용할 수(MAY) 있지만, 예약 헤더를 보내도록 구성하는 것을 허용해서는 안(MUST NOT) 돼요.

Content-Encoding

Content-Encoding: <압축 방식>

콘텐츠 인코딩 요청 헤더는 RFC 9110을 따라야(MUST) 해요. 전송자는 snappy 값을 사용해야(MUST) 해요. 수신자는 snappy 압축을 지원해야(MUST) 해요. 새롭고 선택적인 압축 알고리즘은 2.x 이상에서 올 수 있어요.

Content-Type

Content-Type: application/x-protobuf
Content-Type: application/x-protobuf;proto=<fq-name>

콘텐츠 타입 요청 헤더는 RFC 9110을 따라야(MUST) 해요. 전송자는 유일한 미디어 타입으로 application/x-protobuf를 사용해야(MUST) 해요. 전송자는 헤더 값에 ;proto= 파라미터를 추가해 위 두 가지 중 어떤 Protobuf Message가 사용됐는지 정규화된 이름을 나타낼 수(MAY) 있어요. 결과적으로 전송자는 지원되는 세 가지 헤더 값 중 하나를 보내야(MUST) 해요:

PRW 1.0에서 도입되고 prometheus.WriteRequest로 식별되는 deprecated 메시지의 경우:

  • Content-Type: application/x-protobuf
  • Content-Type: application/x-protobuf;proto=prometheus.WriteRequest

PRW 2.0에서 도입되고 io.prometheus.write.v2.Request로 식별되는 메시지의 경우:

  • Content-Type: application/x-protobuf;proto=io.prometheus.write.v2.Request

1.x 수신자와 대화할 때 전송자는 역호환을 위해 Content-Type: application/x-protobuf를 사용해야(SHOULD) 해요. 그렇지 않으면 전송자는 Content-Type: application/x-protobuf;proto=io.prometheus.write.v2.Request를 사용해야(SHOULD) 해요. 더 많은 Protobuf Message가 2.x 이상에서 올 수 있어요.

수신자는 사용할 Protobuf Message 스키마를 식별하기 위해 콘텐츠 타입 헤더를 사용해야(MUST) 해요. 우발적인 잘못된 스키마 선택은 비결정적 동작(예: 손상)을 초래할 수 있어요.

참고: io.prometheus.write.v2.Request의 예약된 필드 덕분에, 수신자가 prometheus.WriteRequest로 잘못된 스키마를 우발적으로 사용하면 빈 메시지가 됩니다. 이것은 일반적으로 놀라운 오류를 피하기 위한 편의이지만, 이에 의존하지 마세요 -- 미래의 Protobuf Message에는 이 기능이 없을 수 있어요.

X-Prometheus-Remote-Write-Version

X-Prometheus-Remote-Write-Version: <버전>

1.x 수신자와 대화할 때 전송자는 역호환을 위해 X-Prometheus-Remote-Write-Version: 0.1.0을 사용해야(MUST) 해요. 그렇지 않으면 전송자는 자신이 호환되는 가장 새로운 Remote-Write 버전, 예: X-Prometheus-Remote-Write-Version: 2.0.0을 사용해야(SHOULD) 해요.

User-Agent

User-Agent: <값>

전송자는 RFC 9110 User-Agent 헤더 형식을 따라야(SHOULD) 하는 user agent 헤더를 포함해야(MUST) 해요.

응답 (Response)

모든 데이터를 성공적으로 쓴 수신자는 성공 2xx HTTP 상태 코드를 반환해야(MUST) 해요. 그런 성공의 경우 수신자의 응답 본문은 비어 있어야(SHOULD) 하고 상태 코드는 204 HTTP No Content여야(SHOULD) 해요. 전송자는 응답 본문을 무시해야(MUST) 해요. 응답 본문은 미래 사용을 위해 RESERVED(예약)돼요.

수신자가 아는 보낸 데이터 조각(예: 샘플, 히스토그램, exemplar)이 하나라도 성공적으로 쓰이지 않았다면(부분 쓰기나 전체 쓰기 거부 모두) 수신자는 2xx HTTP 상태 코드를 반환해서는 안(MUST NOT) 돼요. 그런 경우 수신자는 응답 본문에 사람이 읽을 수 있는 오류 메시지를 제공해야(MUST) 해요. 수신자의 오류는 거부된 샘플의 양과 그 이유에 대한 정보를 포함해야(SHOULD) 해요. 전송자는 오류 메시지를 해석하려고 해서는 안(MUST NOT) 되고 그대로 로그해야(SHOULD) 해요.

다음 하위 섹션은 헤더와 다양한 쓰기 오류 경우에 대한 전송자·수신자 의미를 지정해요.

필수 Written 응답 헤더 (Required Written Response Headers)

성공적인 콘텐츠 협상 후 수신자는 받은 데이터 배치를 처리(쓰기)해요. 각 중요한 데이터 조각(현재 샘플, 히스토그램, Daemonexemplar)에 대해 완료되면(성공이든 실패든) 수신자는 성공적으로 쓰인 요소의 정확한 수를 담은 전용 HTTP X-Prometheus-Remote-Write-*-Written 응답 헤더를 보내야(MUST) 해요.

각 헤더 값은 단일 64비트 정수여야(MUST) 해요. 헤더 이름은 다음과 같아야(MUST) 해요:

X-Prometheus-Remote-Write-Samples-Written
X-Prometheus-Remote-Write-Histograms-Written
X-Prometheus-Remote-Write-Exemplars-Written

2xx 또는 4xx 상태 코드를 받으면 전송자는 누락된 X-Prometheus-Remote-Write-*-Written 응답 헤더가 이 범주(예: Sample)의 어떤 요소도 수신자가 쓰지 않았다는(수 0) 것을 의미한다고 가정할 수(CAN) 있어요. 전송자는 deprecated prometheus.WriteRequest Protobuf Message를 사용할 때는 이 기능이 없는 1.0 수신자를 만날 위험이 있으므로 같은 것을 가정해서는 안(MUST NOT) 돼요.

전송자는 이 헤더를 사용해 데이터의 어떤 부분이 수신자에 의해 성공적으로 쓰였는지 확인할 수(MAY) 있어요. 일반적인 사용 사례:

  • 부분 쓰기 실패 상황의 더 나은 처리: 전송자는 이 헤더를 사용해 더 정확한 클라이언트 계측과 오류 처리를 할 수(MAY) 있어요
  • 깨진 1.0 수신자 구현 감지: 전송자는 io.prometheus.write.v2.Request 요청으로 데이터를 보내고 2xx HTTP 상태 코드를 받았지만 수신자로부터 X-Prometheus-Remote-Write-*-Written 응답 헤더가 하나도 없으면 415 HTTP Unsupported Media Type 상태 코드를 가정해야(SHOULD) 해요. 이것은 Content-Type 요청 헤더를 확인하지 않는 1.0 수신자에게 흔한 문제이며, prometheus.WriteRequest 스키마로 io.prometheus.write.v2.Request 페이로드를 우발적으로 디코딩하면 빈 결과와 디코딩 오류 없음을 초래해요
  • 다른 깨진 구현이나 문제 감지: 전송자는 이 헤더를 사용해 깨진 전송자·수신자 구현이나 다른 문제를 감지할 수(MAY) 있어요

전송자는 remote write 응답 헤더로부터 수신자가 어떤 Remote Write 사양 버전을 구현하는지 가정해서는 안(MUST NOT) 돼요.

더 많은 (선택적) 헤더가 미래에 올 수 있어요. 예를 들어 더 많은 엔터티나 필드가 추가되어 확인할 가치가 있을 때요.

부분 쓰기 (Partial Write)

전송자는 단일 요청에 여러 시리즈의 샘플을 보내는 데 Remote-Write를 사용해야(SHOULD) 해요. 결과적으로 수신자는 그 외에는 유효하지 않거나 쓰이지 않는 샘플을 포함하는 쓰기 요청에서 유효한 샘플을 쓸 수(MAY) 있어요. 이것이 부분 쓰기 경우를 나타내요. 그런 경우 수신자는 유효하지 않은 샘플부분 쓰기 재시도 섹션을 따라 2xx가 아닌 상태 코드를 반환해야(MUST) 해요.

지원되지 않는 요청 콘텐츠 (Unsupported Request Content)

수신자는 전송자가 제공한 주어진 콘텐츠 타입이나 인코딩을 지원하지 않으면 415 HTTP Unsupported Media Type 상태 코드를 반환해야(MUST) 해요.

전송자는 역호환을 위해 1.x 수신자로부터 위 이유로 400 HTTP Bad Request를 기대해야(SHOULD) 해요.

유효하지 않은 샘플 (Invalid Samples)

수신자는 특정 메트릭 타입이나 샘플을 지원하지 않을 수(MAY NOT) 있어요(예: 어떤 수신자는 메타데이터 타입이나 시작 타임스탬프가 지정되지 않은 샘플을 거부하고, 다른 수신자는 그런 샘플을 받아들일 수 있음). 어떤 샘플이 유효하지 않은지는 수신자에게 달려 있어요. 부분 재시도 가능 쓰기가 발생하지 않는 한, 수신자는 유효하지 않은 샘플을 포함하는 쓰기 요청에 대해 400 HTTP Bad Request 상태 코드를 반환해야(MUST) 해요.

전송자는 (429를 제외한) 4xx HTTP 상태 코드에서 재시도해서는 안(MUST NOT) 돼요. 429는 쓰기 연산이 결코 성공할 수 없고 재시도해서는 안 된다는 것을 나타내기 위해 수신자가 사용해야(MUST) 해요. 전송자는 수신자가 지원하는지 확인하기 위해 다른 콘텐츠 타입이나 인코딩으로 415 HTTP 상태 코드에서 재시도할 수(MAY) 있어요.

재시도 및 백오프 (Retries & Backoff)

수신자는 과부하된 서버 상황을 나타내기 위해 429 HTTP Too Many Requests 상태 코드를 반환할 수(MAY) 있어요. 수신자는 다음 쓰기 시도를 위한 시간을 나타내기 위해 Retry-After 헤더를 반환할 수(MAY) 있어요. 수신자는 내부 서버 오류를 나타내기 위해 5xx HTTP 상태 코드를 반환할 수(MAY) 있어요.

전송자는 429 HTTP 상태 코드에서 재시도할 수(MAY) 있어요. 전송자는 5xx HTTP에서 쓰기 요청을 재시도해야(MUST) 해요. 전송자는 서버를 압도하지 않도록 백오프 알고리즘을 사용해야(MUST) 해요. 전송자는 Retry-After 응답 헤더를 처리해 다음 재시도 시간을 추정할 수(MAY) 있어요.

429 대 5xx 처리의 차이는 수신자가 요청 볼륨을 따라잡지 못해 전송자가 "뒤처질" 수 있는 잠재적 상황, 또는 수신자가 가용성을 보호하기 위해 전송자를 레이트 리밋하기로 선택하는 상황 때문이에요. 결과적으로 전송자에게 429에서 재시도하지 않을 옵션이 있어요. 이는 전송자 측 오류(예: 트래픽 과다)가 있을 때 진행이 되게 하고, 수신자 측 오류(5xx)가 있을 때는 데이터가 손실되지 않게 해요.

부분 쓰기 재시도 (Retries on Partial Writes)

수신자는 전송자가 전체 요청을 재시도하기를 기대할 때 부분 쓰기나 부분 유효하지 않은 샘플 경우에서 5xx HTTP 또는 429 HTTP 상태 코드를 반환할 수(MAY) 있어요. 그 경우 전송자가 같은 요청으로 재시도할 수(MAY) 있으므로 수신자는 멱등성(idempotency)을 지원해야(MUST) 해요.

역방향 및 정방향 호환성 (Backward and Forward Compatibility)

이 프로토콜은 시맨틱 버저닝 2.0을 따릅니다. 어떤 2.x 호환 수신자든 어떤 2.x 호환 전송자를 읽을 수 있어야 하고 그 반대도 마찬가지예요. 파괴적 또는 역호환 불가능 변경은 사양의 3.x 버전을 만든다.

Protobuf Message(Wire Format) 자체는 어떤 측면에서 정방향/역방향 호환돼요:

  • Protobuf Message에서 필드를 제거하면 메이저 버전 증가 필요
  • (선택적) 필드를 추가하면 마이너 버전 증가 가능

즉, 2.x의 미래 마이너 버전은 역호환이라면(예: 수신자와 전송자 모두에게 선택적) io.prometheus.write.v2.Request에 새 선택적 필드, 새 압축, Protobuf Message 및 협상 메커니즘을 추가할 수(MAY) 있어요.

2.x 대 1.x 호환성

2.x 프로토콜은 새롭고 필수적인 io.prometheus.write.v2.Request Protobuf Message를 도입하고 prometheus.WriteRequest를 deprecated 처리함으로써 1.x와의 호환성을 깨뜨려요.

2.x 전송자는 사용자가 어떤 콘텐츠 타입을 사용할지 구성하도록 허용해 1.x 수신자를 지원할 수(MAY) 있어요. 2.x 전송자는 수신자가 415 HTTP 상태 코드를 반환하면 다른 콘텐츠 타입으로 자동 폴백할 수도(MAY) 있어요.

Protobuf Message

io.prometheus.write.v2.Request

io.prometheus.write.v2.Request는 Remote-Write 1.0의 prometheus.WriteRequest 메시지를 대체하고 deprecated 처리하기 위한 새 Protobuf Message를 참조해요.

전체 스키마의 진실의 원천은 프로메테우스 저장소의 prompb/io/prometheus/write/v2/types.proto에 있어요. gogo 의존성과 옵션은 무시할 수(CAN) 있어요 (결국 제거될 예정). 그것들은 직렬화 형식에 영향을 주지 않으므로 사양의 일부가 아니에요.

io.prometheus.write.v2.Request의 단순화된 버전이 아래에 제시돼요.

message Request {
  reserved 1 to 3;

  // symbols contains a de-duplicated array of string elements used for various
  // items in a Request message, like labels and metadata items. For the sender's convenience
  // around empty values for optional fields like unit_ref, symbols array MUST start with
  // empty string.
  //
  // To decode each of the symbolized strings, referenced, by "ref(s)" suffix, you
  // need to lookup the actual string by index from symbols array. The order of
  // strings is up to the sender. The receiver should not assume any particular encoding.
  repeated string symbols = 4;
  // timeseries represents an array of distinct series with 0 or more samples.
  repeated TimeSeries timeseries = 5;
}

// TimeSeries represents a single series.
message TimeSeries {
  reserved 6;

  // labels_refs is a list of label name-value pair references, encoded
  // as indices to the Request.symbols array. This list's length is always
  // a multiple of two, and the underlying labels should be sorted lexicographically.
  //
  // Note that there might be multiple TimeSeries objects in the same
  // Requests with the same labels e.g. for different exemplars, metadata
  // or start timestamp.
  repeated uint32 labels_refs = 1;

  // Timeseries messages can either specify samples or (native) histogram samples
  // (histogram field), but not both. For a typical sender (real-time metric
  // streaming), in healthy cases, there will be only one sample or histogram.
  //
  // Samples and histograms are sorted by timestamp (older first).
  repeated Sample samples = 2;
  repeated Histogram histograms = 3;

  // exemplars represents an optional set of exemplars attached to this series' samples.
  repeated Exemplar exemplars = 4;

  // metadata represents the metadata associated with the given series' samples.
  Metadata metadata = 5;
}

// Exemplar is an additional information attached to some series' samples.
// It is typically used to attach an example trace or request ID associated with
// the metric changes.
message Exemplar {
  // labels_refs is an optional list of label name-value pair references, encoded
  // as indices to the Request.symbols array. This list's len is always
  // a multiple of 2, and the underlying labels should be sorted lexicographically.
  // If the exemplar references a trace it should use the `trace_id` label name, as a best practice.
  repeated uint32 labels_refs = 1;
  // value represents an exact example value. This can be useful when the exemplar
  // is attached to a histogram, which only gives an estimated value through buckets.
  double value = 2;
  // timestamp represents the timestamp of the exemplar in ms.
  //
  // For Go, see github.com/prometheus/prometheus/model/timestamp/timestamp.go
  // for conversion from/to time.Time to Prometheus timestamp.
  int64 timestamp = 3;
}

// Sample represents series sample.
message Sample {
  // value of the sample.
  double value = 1;
  // timestamp represents timestamp of the sample in ms.
  //
  // For Go, see github.com/prometheus/prometheus/model/timestamp/timestamp.go
  // for conversion from/to time.Time to Prometheus timestamp.
  int64 timestamp = 2;
  // start_timestamp represents an optional start timestamp for the sample,
  // in ms format. This information is typically used for counter, histogram (cumulative)
  // or delta type metrics.
  //
  // For cumulative metrics, the start timestamp represents the time when the
  // counter started counting (sometimes referred to as created timestamp), which
  // can increase the accuracy of certain processing and query semantics (e.g. rates).
  //
  // Note:
  // * That some receivers might require start timestamps for certain metric
  // types; rejecting such samples within the Request as a result.
  // * start timestamp is the same as "created timestamp" name Prometheus used in the past.
  //
  // For Go, see github.com/prometheus/prometheus/model/timestamp/timestamp.go
  // for conversion from/to time.Time to Prometheus timestamp.
  //
  // Note that the "optional" keyword is omitted due to efficiency and consistency.
  // Zero value means value not set. If you need to use exactly zero value for
  // the timestamp, use 1 millisecond before or after.
  int64 start_timestamp = 3;
}

// Metadata represents the metadata associated with the given series' samples.
message Metadata {
  enum MetricType {
    METRIC_TYPE_UNSPECIFIED    = 0;
    METRIC_TYPE_COUNTER        = 1;
    METRIC_TYPE_GAUGE          = 2;
    METRIC_TYPE_HISTOGRAM      = 3;
    METRIC_TYPE_GAUGEHISTOGRAM = 4;
    METRIC_TYPE_SUMMARY        = 5;
    METRIC_TYPE_INFO           = 6;
    METRIC_TYPE_STATESET       = 7;
  }
  MetricType type = 1;
  // help_ref is a reference to the Request.symbols array representing help
  // text for the metric. Help is optional, reference should point to an empty string in
  // such a case.
  uint32 help_ref = 3;
  // unit_ref is a reference to the Request.symbols array representing a unit
  // for the metric. Unit is optional, reference should point to an empty string in
  // such a case.
  uint32 unit_ref = 4;
}

// A native histogram message, supporting
// * sparse exponential bucketing, custom bucketing.
// * float or integer histograms.
//
// See the full spec: https://prometheus.io/docs/specs/native_histograms/
message Histogram { ... }

모든 타임스탬프는 Unix epoch 이후 밀리초로 센 int64여야(MUST) 해요. 샘플 값은 float64여야(MUST) 해요.

모든 TimeSeries 메시지에 대해:

  • labels_refs를 제공해야(MUST) 해요
  • samples 또는 histograms에 최소 하나의 요소를 제공해야(MUST) 해요. TimeSeriessampleshistograms를 모두 포함해서는 안(MUST NOT) 돼요. float와 히스토그램 샘플을 (드물게) 섞는 시리즈의 경우 별도의 TimeSeries 메시지를 사용해야(MUST) 해요
  • metadata 하위 필드는 제공해야(SHOULD) 해요. 수신자는 Metadata.type이 지정되지 않은 시리즈를 거부할 수(MAY) 있어요
  • 시리즈에 exemplar가 있으면 제공해야(SHOULD) 해요

다음 하위 섹션은 일부 스키마 요소를 상세히 정의해요.

Symbols

io.prometheus.write.v2.Request Protobuf Message는 표준 압축 위에서 입증된 추가 압축·메모리 효율 이점을 위해 문자열 인터닝을 하도록 설계됐어요.

symbols 테이블을 제공해야(MUST) 하고, 시리즈, exemplar 레이블, 메타데이터 문자열에 사용된 중복 없는 문자열을 포함해야 해요. symbols 테이블의 첫 요소는 Metadata.unit_refMetadata.help_ref가 제공되지 않을 때 같은 비어 있거나 지정되지 않은 값을 나타내는 데 사용되는 빈 문자열이어야(MUST) 해요. 참조는 symbols 문자열 배열의 기존 인덱스를 가리켜야(MUST) 해요.

시리즈 레이블 (Series Labels)

Sample 또는 Histogram 샘플과 함께 완전한 레이블 세트를 보내야(MUST) 해요. 추가로 샘플과 관련된 레이블 세트는:

  • __name__ 레이블을 포함해야(SHOULD) 해요
  • 반복되는 레이블 이름을 포함해서는 안(MUST NOT) 돼요
  • 사전순으로 정렬된 레이블 이름을 가져야(MUST) 해요
  • 빈 레이블 이름이나 값을 포함해서는 안(MUST NOT) 돼요

메트릭 이름, 레이블 이름, 레이블 값은 어떤 UTF-8 문자 시퀀스든 될 수 있어야(MUST) 해요.

메트릭 이름은 정규식 [a-zA-Z_:]([a-zA-Z0-9_:])*를 따라야(SHOULD) 해요.

레이블 이름은 정규식 [a-zA-Z_]([a-zA-Z0-9_])*를 따라야(SHOULD) 해요.

위를 따르지 않는 이름은 PromQL 사용자에게 사용하기 더 어려울 수 있어요 (자세한 내용은 UTF-8 제안 참조).

"__"로 시작하는 레이블 이름은 시스템 사용을 위해 RESERVED(예약)이며 사용해서는 안(SHOULD NOT) 돼요. 프로메테우스 데이터 모델 참조.

수신자는 또한 레이블의 수와 길이에 제한을 부과할 수(MAY) 있지만, 이는 수신자별이며 이 문서의 범위 밖이에요.

샘플 및 히스토그램 샘플

전송자는 어떤 주어진 TimeSeries에 대해 samples(또는 histograms)를 타임스탬프 순서로 보내야(MUST) 해요. 전송자는 서로 다른 시리즈에 대해 여러 요청을 병렬로 보낼 수(MAY) 있어요.

샘플이나 히스토그램의 start_timestamp는 카운터 의미를 따르는 타입(예: 카운터와 카운터 히스토그램)에 대해 제공해야(SHOULD) 해요. 수신자는 start_timestamp가 설정되지 않은 그런 시리즈를 거부할 수(MAY) 있어요. 선택성이 주어졌으므로 0 값은 수신자가 미설정 값으로 취급해야(MUST) 해요. 드문 0 unix 타임스탬프(밀리초)를 나타내려면 "1" 또는 "-1" 값을 사용해야(MUST) 해요.

전송자는 시계열이 더 이상 추가되지 않을 때 stale 마커를 보내야(SHOULD) 해요. 시계열의 중단을 감지할 수 있으면 전송자는 stale 마커를 보내야(MUST) 해요. 예를 들어:

  • 명시적 타임스탬프를 사용하지 않았다면 (스크레이프된) 풀링된 시리즈
  • 기록 규칙 평가로 만들어진 시리즈

일반적으로 중단된 시리즈에 stale 마커를 보내지 않으면 수신자의 비자명한 쿼리 시간 정렬 문제로 이어질 수 있어요.

Stale 마커는 특별한 NaN 값 0x7ff0000000000002로 신호되어야(MUST) 해요. 이 값은 그 외에 사용해서는 안(MUST NOT) 돼요.

일반적으로 전송자는 다음 기술로 시계열이 더 이상 추가되지 않을 때를 감지할 수 있어요:

  • 서비스 디스커버리로 시리즈를 노출하는 타깃이 사라진 것을 감지
  • 연속 스크레이프 사이에 타깃이 더 이상 시계열을 노출하지 않음을 파악
  • 원래 시계열을 노출하던 타깃을 스크레이프하지 못함
  • 기록/경고 규칙에 대한 구성과 평가 추적
  • 비스크레이프 소스의 메트릭 중단 추적 (예: k6에서 벤치마크가 완료되면 시리즈별로 stale 마커를 발행 가능)

메타데이터 (Metadata)

메타데이터는 TypeHelp에 대한 공식 프로메테우스 지침을 따라야(SHOULD) 해요.

메타데이터는 Unit에 대한 공식 OpenMetrics 지침을 따를 수(MAY) 있어요.

Exemplar

각 exemplar는 TimeSeries에 붙으면:

  • 값을 포함해야(MUST) 해요
  • 예를 들어 트레이스나 요청 ID를 참조하는 레이블을 포함할 수(MAY) 있어요. exemplar가 트레이스를 참조하면 모범 사례로 trace_id 레이블 이름을 사용해야(SHOULD) 해요
  • 타임스탬프를 포함해야(MUST) 해요. exemplar 타임스탬프는 프로메테우스/Open Metrics exposition 형식에서 선택적이지만, 타임스탬프가 스크레이프 샘플에 부여되는 것과 같은 방식으로 스크레이프 시 부여된다고 가정해요. 수신자는 들어오는 exemplar를 안정적으로 처리(예: 중복 제거)하려면 exemplar 타임스탬프를 필요로 해요

범위 밖 (Out of Scope)

1.0과 같음.

향후 계획 (Future Plans)

이 섹션은 아직 프로토콜 사양의 일부로 간주되지 않는 추측적 계획을 완전성을 위해 포함해요. 2.0 사양은 1.0의 미래 계획 3개 중 2개를 완료했다는 점을 유의하세요.

  • 트랜잭션성: 2.0 사양에도 여전히 트랜잭션성이 정의되어 있지 않아요. 대부분 확장 가능한 전송자 구현을 어렵게 하기 때문이에요. 프로메테우스 전송자는 "트랜잭션"성, 즉 부분적으로 스크레이프된 타깃을 쿼리에 노출하지 않는 것을 목표로 해요. Remote-Write로도 같은 것을 할 의도에요. 예를 들어 미래에는 Remote-Write를 스크레이프와 "정렬"하여 단일 스크레이프의 모든 샘플, 메타데이터, exemplar가 단일 Remote-Write 요청으로 보내지길 원해요. 하지만 Remote-Write 2.0 사양은 클래식 히스토그램 버킷에 대한 중요한 트랜잭션성 문제를 해결해요. 이는 io.prometheus.write.v2.Request 와이어 형식으로 가능한 사용자 정의 버킷팅을 지원하는 네이티브 히스토그램 덕분이에요. 전송자는 모든 클래식 히스토그램을 네이티브 히스토그램으로 변환할 수 있지만, 이를 강제하는 것은 이 사양의 범위 밖이에요. 그렇지만 이 이유로 수신자는 특정 메트릭 타입(예: 클래식 히스토그램)을 무시할 수(MAY) 있어요
  • 대체 와이어 형식: OpenTelemetry 커뮤니티는 OTLP 프로토콜로 와이어 위 데이터 전송에 Apache Arrow(그리고 잠재적으로 다른 컬럼형 형식)의 타당성을 보여줬어요. 유사한 형식이 프로메테우스 데이터 모델과 호환되는지 확인하는 실험과 리소스 사용 변경의 벤치마크를 포함하고 싶어요. 호환성 이유로 장기적으로 protobuf와 컬럼형 형식 둘 다를 유지하고, 이 목적을 위해 콘텐츠 협상을 사용해 다른 Protobuf Message를 추가할 수 있어요
  • 전역 symbols: 인터닝을 위한 미리 정의된 문자열 사전. 프로토콜은 "namespace", "le", "job", "seconds", "bytes" 등 흔한 것으로 간주되는 문자열을 포함하는 ref->symbol의 정적 사전을 미리 정의할 수 있어요. 전송자는 이를 요청의 symbols 테이블에 포함할 필요 없이 참조할 수 있어요. 이 사전은 이 프로토콜의 마이너 버전 릴리스로 점진적으로 커질 수 있어요

FAQ

왜 gRPC를 쓰지 않나요? 1.0 프로토콜이 gRPC를 사용하지 않으므로, 그것을 깨는 것은 채택의 마찰을 증가시킬 거예요. 1.0 이유 참조.

왜 protobuf 메시지를 스트리밍하지 않나요? 영속 HTTP/1.1 연결을 사용하면 스트리밍에 꽤 가깝습니다. 물론 헤더를 다시 보내야 하지만, 새 TCP 설정보다는 저렴합니다.

왜 샘플을 순서대로 보내나요? 순서 제약은 프로메테우스의 시계열 데이터 인코딩(추가 전용 워크로드에 최적화된 구현)에서 나옵니다. 하지만 이 요구사항은 생태계의 다른 많은 데이터베이스와 벤더도 공유합니다. 실제로 OOO 기능이 활성화된 프로메테우스는 성능 불이익과 함께 순서 없는 쓰기를 허용하므로 드문 사건에 예약됩니다. 요약하면 수신자는 순서 없는 쓰기를 지원할 수 있지만 사양에서는 허용되지 않습니다. 미래 예: 2.x 사양 버전에서는 필요하면 콘텐츠 타입을 확장해 순서 없는 쓰기를 협상할 수 있습니다.

순서 제약으로 요청을 어떻게 병렬화하나요? 주어진 시리즈에 대해 샘플은 순서대로여야 합니다. 하지만 수신자가 순서 없는 쓰기를 지원하지 않더라도, Remote-Write 요청은 서로 다른 시리즈에 대한 것이라면 병렬로 보낼 수 있습니다. 프로메테우스는 레이블로 샘플을 여러 큐로 샤딩하고 각 큐에서 쓰기가 순차적으로 발생합니다. 이는 같은 시리즈의 샘플이 순서대로 전달되고 서로 다른 시리즈의 샘플이 병렬로(그리고 서로 다른 시리즈 간에는 잠재적으로 "순서 없이") 보내짐을 보장합니다.

Remote-Write 2.0과 OpenTelemetry의 OTLP 프로토콜의 차이는 무엇인가요? OpenTelemetry OTLP는 텔레메트리 소스, 중간 노드, 텔레메트리 백엔드 사이에서 텔레메트리 데이터(메트릭, 로그, 트레이스, 프로파일 같은)를 운반하기 위한 프로토콜이에요. 권장 전송은 protobuf와 함께 gRPC를 포함하지만 protobuf 또는 JSON과 함께 HTTP도 설명돼요. 다양한 관측성 신호, 데이터 타입, 추가 정보를 지원하려는 의도로 처음부터 설계됐어요. 메트릭의 경우 추가 비식별 레이블, 플래그, 시간적 집계 타입, 리소스 또는 범위 메트릭, 스키마 URL 등을 의미해요. OTLP는 또한 시맨틱 컨벤션을 요구해요.

Remote-Write는 단순성, 효율성, 유기적 성장을 위해 설계됐어요. 첫 버전은 수년간 이미 CNCF 생태계의 수십 개 전투 테스트를 거친 채택자가 이 프로토콜을 사용해 온 2023년에 공식 출시됐어요. Remote-Write 2.0은 몇 가지 새 요소(메타데이터, exemplar, 시작 타임스탬프, 네이티브 히스토그램)와 문자열 인터닝을 추가해 이전 프로토콜을 반복해요. Remote-Write 2.0은 항상 무상태이고 메트릭에만 초점을 맞추며 독단적개이어서, 프로메테우스 커뮤니티가 견고한 메트릭 솔루션을 위해 충분하다고 여기는 요소로 범위를 축소해요. 의도는 Remote-Write가 관측성 생태계의 대안보다 더 저렴하고 단순하게 채택하고 사용할 수 있는 안정적인 프로토콜임을 보장하는 것이에요.

더 알아보기 (Learn more)