Carpe Diem

備忘録

Go

ログで機密情報をマスキングする方法

背景 DBのconfigのように一部機密情報が含まれるものを環境変数(k8s Secret等)で注入することは多いです。 そしてその環境変数がちゃんと設定されているか起動時にログを吐きたいということもよくあります。 一方で type Config struct { Addr string Port i…

http.ResponseWriterに書き込んだstatus codeを取得したい

Go

背景 5xx系エラーをbugsnagのようなエラー検知サービスに送信したい middleware層で網羅的に対応したい といった際に、 http.ResponseWriterに書き込まれたstatus codeは直接アクセスできない という問題があります。 今回はこの問題を解決する方法を紹介し…

go-redisのtimeoutで気をつけること

概要 Go言語でRedisを使う際に選択肢に挙がるのがgo-redisです。 今回はgo-redisでTimeoutを設定する際に注意すべきことをまとめました。 手前味噌ですがBlast Radius of Failureを最小にするためにTimeoutを短くすることを1つのテクニックとしても紹介して…

Go + Alpine でtzdataはapk addしなくていい

背景 Alpine Linuxはデフォルトではtzdataが含まれていないため、以下のような package main import ( "log" "time" ) func main() { loc, err := time.LoadLocation("Asia/Tokyo") if err != nil { log.Fatal("%w", err) } log.Printf("%v", loc) } time.Lo…

GoでYAMLファイルを読み込んだ時にデフォルト値を設定したい

背景 christina04.hatenablog.com のように設定ファイルをYAMLで管理するパターンは良くありますが、設定項目が増えるほど運用つらくなるので、あまり弄らない項目に対してはデフォルトを用意し、値がなければそれを設定したいケースがあります。 イメージと…

基数木を使った効率的なパスルーティング

背景 次のようなwildcardを含んだpathをフレームワークに頼らず、自前で実装する場合にどうパターンマッチさせるか考えてみます。 /users/:id /articles/:id/comments 単純に考えると正規表現で次のようなパターンを使ってforループで回す、といったものがあ…

gRPCで一時的なネットワーク断でのtransportエラーを回避する

背景 gRPCを利用していると、デプロイを含む一時的なネットワーク断で以下のようなエラーが発生することがあります。 rpc error: code = Unavailable desc = connection error: desc = "transport: Error while dialing: dial tcp xxx: connect: connection …

Goでバンディットアルゴリズムを実装する

概要 複数の案を試す際にA/Bテストがありますが、検証期間中はずっと同じ割合で試行しなければいけないため、もし悪い案であった場合に全体としてその期間損失を生むことになります。 そのような損失を少なくしつつ、良いと思われる案を優先的に試行するアル…

OpenTelemetryでのSampling

概要 トレーシングで重要なのがSamplingです。 単純に全データを計測するとコストが増えたりパフォーマンスに影響が出たりします。 OpenTelemetryではSamplingに対する仕様が決まっているので、それに基づいて具体的な実装を紹介します。 OpenTelemetry Spec…

OpenTelemetryで分散トレーシング

概要 マイクロサービス構成など分散システムにおいて分散トレーシングを行いたい場合は、サービス間でのContextの伝播が必要になります。 OpenTelemetryでサービス間での伝播をしたい場合の実装方法を説明します。 環境 Go v1.20.2 go.opentelemetry.io/otel…

OpenTelemetryのTraceをGoで試してみる

概要 OpenTelemetryはObservabilityの三本柱であるLogs、Metrics、Tracesをベンダーに依存せず実装するための仕様&実装です。 今回はそのうちのTracesをGoで試してみます。 環境 Go v1.20.2 go.opentelemetry.io/otel/trace v1.14.0 前提知識 登場人物 Open…

重み付き乱択を行う

概要 重み付き乱択アルゴリズムは結果に偏りのある抽選で ソシャゲのガチャのようにレアリティによって出現確率を変えたい リクエストを重み付けして分散したり、A/Bテストで99:1のように振り分けたい といった用途で使えます。 今回はその説明と実装方法の…

GoでネストしたMongoDBドキュメントの部分更新をする

概要 MongoDBが使っているbsonはomitemptyというstructタグが利用可能で、これを使うことでそのフィールドがzero値の際は insert時にフィールドを追加しない(容量の削減) update時にフィールドを更新しない(部分アップデートの簡易化) といったメリット…

gRPCでFieldMaskを使う(更新編)

概要 christina04.hatenablog.com 前回はFieldMaskを使ってオーバーフェッチを避ける方法を説明しました。 今回はMutation(更新)におけるFieldMaskの活用方法を説明します。 環境 Go v1.18.3 protoc-gen-go v1.25.0 protoc v3.19.4 grpc-go v1.47.0 MongoDB…

gRPCでFieldMaskを使う(取得編)

概要 クライアントデバイスが多様化する中、UIに必要なデータもそれぞれ異なるためAPIのオーバーフェッチ(不要なデータの取りすぎ)が課題になってきます。 またマイクロサービス間の通信でも、例えばマスタ系データのうち必要なデータだけ取得して利用した…

YAMLのような設定ファイルで環境変数を扱う

背景 christina04.hatenablog.com ではconfig.yamlを読み込ませてサーバを起動するのですが、その中にSlackのAPIトークンを入れる箇所がありました。 config.yaml自体はConfigMapで渡しているのですが、中に記述されているAPIトークンは環境変数でSecretで管…

Bazelを使ってみる その6(テスト)

概要 Bazel解説第6弾です。 Bazelを使ってみる その1(Goのビルド) - Carpe Diem Bazelを使ってみる その2(protobufのビルド) - Carpe Diem Bazelを使ってみる その3(docker imageのビルド) - Carpe Diem Bazelを使ってみる その4(gRPCのビルド)…

fatal: could not read Username for 'https://github.com': terminal prompts disabledが出たら

概要 CIツールでプライベートリポジトリをgo getしようとするとたまに遭遇する fatal: could not read Username for 'https://github.com': terminal prompts disabled の対応方法をまとめます。 原因 go getはデフォルトだとHTTPSで通信しようとしますが、…

Bazelを使ってみる その1(Goのビルド)

背景 Bazelは優れたビルドツールである一方で、導入したチームには1人はBazel職人が必要と言われるほどキャッチアップコストが高くハマったときに開発が止まると言われます。 そのためKubernetesからも削除されるほどです。 しかしながら導入の善し悪しを判…

Bigtableの条件付き書き込みパターン

概要 BigtableにはCheckAndMutateRow APIがあり、書き込む際に条件をつけることで更新のロストなどを防ぐ仕組みが用意されています。 ※更新のロストについては以下を参考にしてください トランザクションの分離レベルで出てくる用語 - Carpe Diem 今回はよく…

go-redisのRingの挙動を検証してみる

概要 Redis Clusterが生まれるまではRedisの水平スケール手段としては前回紹介した Consistent Hashing (コンシステントハッシュ法) - Carpe Diem を用いた手法が使われていました。 これはRedis Ringと呼ばれる形でいくつかのライブラリでサポートされて…

ProtobufとStreamで扱うJSONはどちらが省メモリなのか

背景 JSONよりもProtobufの方が ファイルサイズが小さい シリアライズ・デシリアライズが速い ということは色んな検証記事から明らかになっています。 一方でGoのProtobufはデータをstreamで扱うのではなく、全部メモリに載せてシリアライズ・デシリアライズ…

GoのロギングライブラリzapのTips

概要 zapを使っていて 書き込み先をio.Writerで自由に設定したい テストで時刻を固定値にしたい ログレベルによって標準出力、標準エラー出力を分けたい GCP Cloud Loggingのフォーマットでログ出力したい 全ログに付けたいフィールドがある 独自logパッケー…

bugsnagのWeb UIでスタックトレースを表示する方法

背景 サーバサイドでエラー検知・トラッキングのSaaSを利用する場合、bugsnagが候補に挙がると思います。 ただそのままerrorをNotify()してみてもスタックトレースがきちんと表示されない問題にぶつかります。 今回はGoでbugsnagを利用する場合に、bugsnagの…

fmt.Errorfとxerrors.Errorfと独自エラーと

Go

背景 エラーハンドリングでは エラーが発生した箇所を追うことができる スタックトレースが出力できればなお良し エラーの原因によって処理を分岐することができる といったことが重要です。 Go 1.13から入ったラップする仕組みにより、エラーメッセージにア…

Go modulesで依存モジュールのメジャーバージョンがv2以上の時の対応

Go

背景 依存するモジュールのメジャーバージョンがv2以上の場合に、以下のようにバージョン指定すると $ go get github.com/xxx/yyy@v2.0.1 次のように怒られます。 require github.com/xxx/yyy: version "v2.0.1" invalid: should be v0 or v1, not v2 今回は…

ユースケースに応じたユニークなIDの生成

Go

概要 ユニークなID生成をしたい場面は多々ありますが、ユニークIDにはユースケースによって以下のような要件が出てきます。 ユニーク 短い(=データ量が少ない) 推測困難 分散性(ランダム性) 順序性(Lexicographical) 生成速度 それぞれの観点について…

JWTの署名検証で使う公開鍵をX.509証明書で管理する

概要 JWTをアクセストークンとして利用する場合、署名(秘密鍵)は認証サーバで、署名検証(公開鍵)はリソースサーバで行うのが良いです。 そのため認証サーバは公開鍵をリソースサーバに公開する必要があります。 Googleなどの大規模サービスを見ると、生…

http clientでHTTP/2を使う方法

背景 外部APIを叩く時に利用するhttp clientですが、サーバ側がHTTP/2対応しているのであればコネクションの有効活用ができるようHTTP/2を使いたいものです。 その際にhttp client側で設定する点、気をつける点を説明していきます。 環境 Go 1.15.6 curl 7.6…

WARNING: Missing 'go_package' option が出る時の対処法

背景 いつの間にかprotocでprotoc-gen-goプラグインを使うと以下のようなエラーが出るようになりました。 2021/01/05 06:19:01 WARNING: Missing 'go_package' option in "xxx/xxx.proto", please specify it with the full Go package path as a future rel…