🤖

OpenTelemetryのBufferingとRetryについて

2024/06/19に公開

OpenTelemetryでTraceとればロックインを回避できてコストカットができると考えて会社で進めている。その中でよく受ける質問として、CollectorのSideCarが落ちたらデータがロストするのではないか、みたいな事をいわれる。

これは調べると半分あっていて、半分間違っているように見える。
データロストを防ぐ方法としては、RetryとBufferingがよく使われるが、OpenTelemetryではどのように実装されているかを確認する。

SDKでやっていること

OpenTelemetryのSDKはTraceを作成して、logsにもtrace idやspan idを付与している。
このtrace idやspan idを利用する事でTraceやLogを一貫してみることができるようになっている。

これは仕組みとしてはTracerProviderというTracerを生成するAPIがあり、TracerがSpanを作成して、Spanが処理をトレースするという形で実現されている。
Javaや.NETなどは自動計装があり、自動計装の川でこれらの一連の処理を行なっている。

https://opentelemetry.io/docs/specs/otel/trace/api/

logについても同様でLoggerProvider APIがあり、LoggerProviderを通じてLoggerインスタンスを生成し、LoggerインスタンスがLogRecordとしてLogを出力する。

https://opentelemetry.io/docs/specs/otel/logs/sdk/

Traceと紐付けはcontextという情報伝達の仕組みを経由してTraceで生成したtrace idを自動的にlogに付与するようになっている。

https://opentelemetry.io/docs/specs/otel/context/

ここからが本題となる。

Exporterについて

collectorで設定するexporterと同じくそれぞれの言語毎で利用可能なExporterの事となる。
例えば、Javaであればこの一覧のExporterが利用可能となっている。

https://opentelemetry.io/ecosystem/registry/?component=exporter&language=java

あまり推奨される構成ではないが、Exporterを利用してJavaやGoなどから直接バックエンドにデータを送ることが可能とはなっている。

ただあまりバックエンドの情報を直接コードに入れ込むことになるのであまりお勧めはできない構成ではある。

https://opentelemetry.io/docs/collector/deployment/no-collector/

今回はこのExporterがCollectorにデータを送るときのbufferingとretryについて確認したい。

Bufferingについて

fluentd/fluentbitなどもそうだがデータをすぐに送るのではなくある程度のChunkにして外部に書き出すことが多い。

OpenTelemetryの場合はProcesserにその役割を担わせている。

https://github.com/open-telemetry/opentelemetry-go/blob/main/sdk/trace/batch_span_processor.go

type BatchSpanProcessorOptions struct {
	// MaxQueueSize is the maximum queue size to buffer spans for delayed processing. If the
	// queue gets full it drops the spans. Use BlockOnQueueFull to change this behavior.
	// The default value of MaxQueueSize is 2048.
	MaxQueueSize int

	// BatchTimeout is the maximum duration for constructing a batch. Processor
	// forcefully sends available spans when timeout is reached.
	// The default value of BatchTimeout is 5000 msec.
	BatchTimeout time.Duration

	// ExportTimeout specifies the maximum duration for exporting spans. If the timeout
	// is reached, the export will be cancelled.
	// The default value of ExportTimeout is 30000 msec.
	ExportTimeout time.Duration

	// MaxExportBatchSize is the maximum number of spans to process in a single batch.
	// If there are more than one batch worth of spans then it processes multiple batches
	// of spans one batch after the other without any delay.
	// The default value of MaxExportBatchSize is 512.
	MaxExportBatchSize int

	// BlockOnQueueFull blocks onEnd() and onStart() method if the queue is full
	// AND if BlockOnQueueFull is set to true.
	// Blocking option should be used carefully as it can severely affect the performance of an
	// application.
	BlockOnQueueFull bool
}

この期間やサイズまでは言語側でBufferをしている。
リソースと相談とはなるが言語側でもある程度のBufferを用意することでデータのロストをなるべく減らすことができそうだ。

これらは環境変数経由で設定が可能となっている。

Retryについて

retryについても仕様と定義がある。

https://opentelemetry.io/docs/specs/otlp/#failures
https://opentelemetry.io/docs/specs/otel/protocol/exporter/#retry

サーバー側が定義されているRetry可能なエラーが返ってきたらRetryやThrottleをするようになっている。

Retryのところに堂々と買いてあるがRetryの上限に達した場合はデータは失われる。

結論

BufferのQueueの上限あるいはRetryの上限に達すればデータはロストするが、その間にCollectorにデータを送れればデータのロストを防ぐことができる。

ECSでCollectorを動かす場合にはSideCarとして多いと思われる。その際に気になるのはデータロストどのようにして防ぐか、というところかと思う。
この記事で確認してきたように一定サイズのBufferでデータを保存しつつも、retryの上限を超える期間collectorと通信ができないとデータがロストされる。(もちろん、BufferのQueueも上限に達すればデータはロストされる)
そのため大事なのはCollectorをいかに早く復旧させるかにかかっている。

SideCarではなく、全体の共有のサービスとしてCollectorを切り出すのは一つありなように見えてくる。
ただしそもそもOpenTelemetryはSamplingがあり、すべてのデータを送信するようにはなってはいない事が多い。これはコストとの兼ね合いでそうなっている。それでもより多くのデータを取得したい場合は、bufferやretryについてもより気にかけると良いと思われる。

最後に皆さんのObservability Lifeが楽しくなることを祈る。

Discussion