Datadog SLO 101 を読んだ
Datadog のブログに、SLOに関するシリーズがある。今日は一つ目のブログを読んだ。
以下、大雑把な意訳と感想。
GoogleのSRE本で紹介されたSLO(service level objectives)は、それを利用することでプロダクト開発と運用タスクのバランスをとって、最終的にはプロダクトのユーザ体験を高めることができる、というもの。
このブログでは、SLI, SLO, SLA,エラーバジェットの説明、SLOは誰にとって重要なのか、SLO設定に当たってとりあげるべきSLIについて説明があった。
用語の説明
Service Level Indicators (SLIs)
ユーザに提供しているサービスのレベルを測るメトリクス。例えば可用性、レイテンシー、スループットなど。
Service Level Objectives (SLOs)
目標サービスレベル。SLIによって計測される。
Service Level Agreements (SLAs)
提供されるサービスのレベルに関して、サービスの提供者とサービスの利用者の間で交わされる契約、合意。 これを下回った場合は料金の減額などが行われる。
Error bugets
直訳するとエラー予算。SLOを守るにあたって許容されるエラーの累積値の上限。 エラーバジェットが残っている間は新規開発などが許されるが、それを下回った場合は不具合の修正や設計の見直しが求められる。
SLOは誰にとって重要なのか?
エンドユーザ
サービスの品質に対するエンドユーザの期待値は高い。ただ100%の信頼性は無理。
SLOを設定することで、製品開発(ユーザに新しい価値を提供するが、何かを壊す可能性もある)と、信頼性(ユーザの幸福度をキープする)のバランスをとることができる。
製品開発エンジニア(dev)、運用エンジニア(ops)
歴史的に製品開発エンジニアと運用エンジニアが分かれていて、それぞれの目標が利益相反していた。 大雑把にいうと、製品開発エンジニアは新しい機能を追加することに責任をもつが、運用エンジニアは出来上がったものの信頼性に責任を持つ。
SLOとエラーバジェットを設定することで信用性に関して同じ責任感を持つことができるようになる。
エラーバジェットが残っているうちは、製品開発者エンジニアは新しい機能をリリースできるし、運用エンジニアは長期的な改善プロジェクトを進めることができる。 エラーバジェットが残り少なくなったら、製品開発者エンジニアは手を止めて信頼性の向上のため、運用エンジニアと共に直近の改善活動をする必要がある。
簡単にいうとエラーバジェットは、製品開発エンジニアと運用エンジニアの仕事を調整するための数値化手法ですよ、という話。
SLIからSLOへ
良いSLIを選ぼう
すべてのSLIが良いSLIではない。 ユーザ体験に直接影響する指標に絞りましょう。 例えば買い物サイトにおいて、サーバのCPU使用率が高くても、ユーザの買い物がスムースにできていれば問題ない。この例ではCPU使用率は良くないSLIと言える。
ユーザに近い位置で計測することを勧める。
SLIの選択には、GoogleのSRE本が参考になる。 https://sre.google/workbook/implementing-slos/#slis-for-different-types-of-services
SLIをSLOにする
SLOは例えば、30日間で99%のリクエストのレイテンシーが250ms以下である、といったものになる。
SLOを設定するにあたっては以下の注意事項がある。
- 現実的になろう。100%のSLOは無理。
- 最初から正解のSLOは出せないので運用しながらよくしていきましょう。
- 複雑にしすぎないように。ユーザ体験に直結するものに絞りましょう。
SLO設定にあたってのチェックリストをDatadogが用意している。 https://www.datadoghq.com/pdf/SLOChecklist_200619.pdf
感想
SLOやエラーバジェットを設定するのはすぐにできそうだが、それをうまく運用するにはチームメンバーとの協力が必要。 設定値に納得感がないといけないし、下回った場合にどうするか事前に決めておく必要がある。
Transactional Outbox パターンをSpring Bootで実装する
Transactional Outbox
という、Databaseへの保存とメッセージの送信をアトミックにすることができる実装パターンがあります。
参照
- Pattern: Transactional outbox | Microservice Architecture
- Transactional Outbox: A Simplified Approach
例えば決済処理において、決済完了したことをDBに保存する処理と、決済完了メッセージを送信する処理がアトミックではない場合、下のように困ったことが起きてしまいます。
保存はできたがメッセージの送信に失敗した場合、後続の処理が進まない
保存に失敗したのに、決済完了メッセージだけ送信してしまった場合、後続の処理が誤って進んでしまう
特に、サーバサイドのアプリケーションがマイクロサービス化されていて、サービス間のメッセージ送受信の確実性がシステムの信頼性に直結する場合、 Transactional Outboxパターンはよく使われるパターンかと思います。
実装
このパターンをSpring Boot で実装する方法を紹介します。
言語は Kotlin を使っています。
受注完了時に送信するメッセージを OrderEvent
とします。
class OrderEvent( val orderId: String, .... )
OrderEvent
のpublish
受注完了時にOrderEvent
を org.springframework.context.ApplicationEventPublisher
を使って publishします。
@Service class OrderService( private val publisher: ApplicationEventPublisher ) { .... @Transactional fun order() { .... val event: OrderEvent = ... publisher.publish(event) } }
OrderEvent
の永続化
publishしたOrderEvent
を org.springframework.context.event.EventListener
アノテーションをつけたメソッドで受けてDBへ永続化します。このときのDBトランザクションは、上記の OrderService.order
メソッドで開始しているDBトランザクションと同じものになります。
@Component class OrderEventPersistentSubscriber { @EventListener fun processSubscriptionEvent(event: OrderEvent) { ... // 永続化処理 } }
OrderEvent
の送信
受注処理が完了してDBトランザクションがCOMMITされた後、OrderEvent
を外部のメッセージブローカーに渡します。このときorg.springframework.scheduling.annotation.Async
をつけて非同期に送信するとOrderService.order
メソッドの処理をブロックしないので効率的です。
@Component class OrderEventRelay { @Async @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) fun relayOrderEventEvent(event: OrderEvent) { .... // OrderEventを外部のメッセージブローカーへ送信する処理 } }
OrderEvent
の再送
メッセージブローカーがダウンしているなどの理由で上記の送信処理が失敗することがあります。
それに備えて、送信されていない OrderEvent
を定期的に見つけて再送する必要があります。
例えば、org.springframework.scheduling.annotation.Scheduled
を使って、永続化した OrderEvent
のうち送信完了していないものを定期的に選択して再送します。
@Component class OrderEventRecoveryTask { @Scheduled(...) fun recovery() { .... // 再送処理 } }
参考
上記で利用したSpringのクラスの設定方法や利用方法は、下記を参考にして下さい。
ApplicationEventPublisher
, @EventListener
Spring bootを使っていればデフォルトで利用可能だったと思います。使い方は下記を参照してください。
Spring Framework - ApplicationEventPublisher Examples
@Async
@EnableAsync
で有効にしつつ、Executorの設定が必要です。下記を参照してください。
@Scheduled
@EnableScheduling
で有効にする必要があります。下記を参照してください。
Gradleのscanオプションについて
仕事ではgradleを使ったJavaアプリケーションの開発をしている。
その割には gradle についてちゃんと勉強したことがなかったので、Gradle公式のユーザガイド Building Kotlin Applications Sample を読んでいた。
この中で scan オプションが紹介されていた。
% ./gradlew build --scan BUILD SUCCESSFUL in 8s 8 actionable tasks: 3 executed, 5 up-to-date Publishing a build scan to scans.gradle.com requires accepting the Gradle Terms of Service defined at https://gradle.com/terms-of-service. Do you accept these terms? [yes, no] yes Gradle Terms of Service accepted. Publishing build scan... https://gradle.com/s/wrk56****
途中で Do you accept these terms
と聞かれるので同意すると、scanの結果がgradleのwebサーバへアップロードされる。
最後に表示されたURL https://gradle.com/s/wrk56****
を開くと、メールアドレス認証があり、それを通過すると、アップロードしたscan結果を綺麗なレイアウトで見るとこができた。
Enterprise版にすればもっと便利な機能もあるらしい。
セキュリティ的な懸念は少しあるけどビルド速度の改善には使えるかもしれない。
調べてみると2017年あたりにはあった機能なのね。