Posts for: #Mongodb

お盆の最終日

1時に寝て何度か起きて8時半に起きた。数ヶ月に1回ぐらいの頻度でしかないことだけど寝坊した。起きたら8時半であれ?と思った。起きてから家でそのぐらいの時間までだらだらするのはちょくちょくあることだけど、気付かず寝てたのは久しぶりだった。

台風が過ぎた後の焼き鳥屋さん

今朝に寝坊した理由はこれだと思うけれど、昨日の22時から晩ご飯を求めて仲のよい焼き鳥屋さんへ行ってきた。台風で9割以上のお店が閉めている中、唯一と言っていいぐらいの珍しさで開いてた。そのお店 (グループ) のオーナーは雨が降ろうが槍が降ろうが営業日は開けるという方針らしい。マスターも夕方から台風は過ぎて雨も弱まっていたので普通に営業していたらしい。しかし、お客さんは数グループと少なかったと仰っていた。私が22時に行って誰もいなくて24時までいたけれど、誰も来なかった。通常なら22時だと他に2-3グループはいて、その後も最低でも1グループは飲みにやってくるぐらいの人気のある焼き鳥屋さんだ。そもそも駅から人が出てこないし、道にも人が歩いていない。物流も止まっていたのでいくつか仕入れが出来なくて提供できないメニューもあった。

いつもなら2杯飲んで帰るところを、こんな日だから売上に貢献しようと思って3杯飲んで寝坊した気がする。

mongodb 7.0.0 リリース

ちょうど qa テスト前で mongodb のメジャーバージョンがリリースされそうなので毎週のようにチェックしていた。rc10 までいって ga されたみたい。

まだ docker hub には正式なリリースバージョンのコンテナイメージは公開されていない。しかし、rc10 が ga になったはずなのでひとまずはそれを使って開発環境とテスト環境を 7.0.0 に移行した。うちの用途だと ttl インデックスを作り直す以外には移行作業は必要なかった。自動テストはそのまま成功したし、テスト環境のデータもそのまま移行してパッとみた感じでは正常に動いている。来週から qa テストも始まるのでぎりぎり間に合ったというところ。MongoDB Software Lifecycle Schedules によると、だいたい mongodb は3年サポートされる。メジャーバージョンが年に1回リリースされているようにみえるので、いまメジャーバージョンを上げておくと1年余裕をもって運用できる。

テックブログの執筆開始

お昼からテックブログの執筆に着手した。あまり大きな意味はないのだけど、お手伝い先のテックブログの記事を早く3つ書きたかった。別に三部作というわけでもない。けれど、テックブログ書いてますよと他者へ伝えるときに最低3つぐらい記事を書いていないと、全然書いてないやんと私なら思ってしまう。3つぐらいあれば、この人はこういう技術に関心があるんだ、こんな業務をやっているんだ、内容もしっかり書けているねとか、そういう判断を下すことができる最低限のコンテンツ量と言えるのではないだろうか。私にとってはそれが3つの記事と言える。ちょっと前に公開した podcast でテックブログの記事を読んでくださいと話したのでリスナーが聞く前に増やしておきたい。ちょうどプロダクトのプレスリリースも出たのでその宣伝も兼ねられるし、勉強会のネタにもなるし、私の義務感を軽減してストレス解消にもなるし、ここは踏ん張って今日・明日で下書きを書き終えたい。

年一ゲストの podcast 収録

夕方に寝て晩ご飯食べてきて23時に寝て7時に起きた。起きてから podcast のネタ帳を書いていた。本当は前日の夜に書くと宣言したものの、夜は眠くて普通に寝てた。

mongodb のインデックス追加

テスト環境の履歴テーブルにドキュメントが135万件ほど入っている。管理画面の履歴一覧を表示すると、一覧がレンダリングされるのに約1秒かかるようになった。遅い。インデックスなしでフルスキャンしているのでデフォルトのソートキーのインデックスが必要なことはすぐに想定できた。実際に compass でクエリを explain で実行してみるとフルスキャンしていることと、どのぐらいの時間がかかっているのかを計測できた。

デフォルトのソートキーのインデックスを追加すると explain の画面で数十 msec かかっていた時間が解消された。ソートキーに対して自動的にインデックスが使われることもわかった。

管理画面からも数十 msec で一覧が表示されるようになった。私の感覚ではひと昔のデータベースは10万件を超えたら1秒ぐらいかかった気はするが、いまはマシンスペックもミドルウェアの性能も上がっているのでそれが100万件超になったんだなという印象。実運用だとすぐに発生する問題が普通の開発をしていると気付きにくくなる懸念があることを学んだ。

podcast 収録

お仕事を終えてから、年一ゲストとして出演している terapyon channel の podcast の収録へ行ってきた。本当は6月頃に出演依頼が来ていたのを、私がお手伝いしている開発のプレスリリースや事例紹介が終わってからの方が話せる内容が多くてよいということで延期してもらっていた。事例紹介はまだ公開できていないけれど、別に事例紹介なくても先方にうちの会社が手伝っていることを公開してよい許可はもらっているので podcast の中で話してもまったく問題ない。

昨日てらださんとたまたまやり取りしていたときに、せっかく東京に来ているのでオフラインで収録しては?と言ったらその方向になって、翌日即収録という行き当たりばったりやっつけ計画で話しが進んだ。非商用の podcast で話すのでそこまで品質に責任をもつ必要もない。19時半にてらださんの会社のオフィスへ伺い、20時頃から収録を始めて、なんやらかんやら盛り上がって言いたい放題言って、2時間経っていた。オンラインで収録するよりも、オフラインの方がずっと多くのことを短い時間で話せるように感じた。相槌うったり会話の掛け合いがしやすいので間が短い気がする。内容的にはちょっと話し過ぎで聞く人は疲れて最後まで聞いてもらえないかもしれない。その後、近くの居酒屋さんへ飲みに行って1時間ほど飲んで24時前にはホテルへ戻ってきた。楽しかったー。

てらださんの近況の中に llm を使ってサービスを開発するときに LangChain というツールがあって、これを使うと簡単にいろんな言語処理ができて楽しいといったことを共有してもらった。いまは触る余裕がないけれど、覚えておきたい。

リフレクションにはまった半日

23時に寝て5時に起きて6時半に起きた。ストレッチで伸ばしたせいか、いつもよりよく眠れた。先週は主に旅行へ行っていて非日常でリフレッシュした。今朝は朝ご飯に野菜サラダを作って食べて7時半には家を出れた。

非同期の ldap 検索の api

先日送った go-ldap の pr を完了した。送ったときはチャンネル用いた検索 api だったのだけど、それから設計を議論して非同期検索を主とした api として生まれ変わった。レビューに1ヶ月を要したものの2人のメンバーから approve をもらって無事にマージされた。

この一歩は大きくてこの機能を突破口にうちらの要件に足りない機能を実装していく。プロトコル部分の修正が過去の draft 実装から参考にできるのであれば今週中にはまた pr を送りたい。

mongodb の unmarshal 実装

mongodb-driver での bson の marshal/unmarshal を実装する。mongo-driver/bson に unmarshal について2つの interface が紹介されている。

type Unmarshaler interface {
	UnmarshalBSON([]byte) error
}

type ValueUnmarshaler interface {
	UnmarshalBSONValue(bsontype.Type, []byte) error
}

bson の byte 列を unmarshal するにあたり、構造体そのものには UnmarshalBSON() を、構造体のメンバーには UnmarshalBSONValue() を使う。これでうまくいきそうに思えたのだけど、interface を介したデコード処理で意図した振る舞いにならないことに気付いた。mongodb-driver は decode/unmarshal 処理を reflect を使って実装している。要件の詳細は省く (interface を使いたい背景がある) が、再現コードが次になる。

type MyInterface interface {
	MyFunc() error
}

type MyType struct {
}

func (t *MyType) MyFunc() error {
	return nil
}

var tMyInterface = reflect.TypeOf((*MyInterface)(nil)).Elem()

func some(v reflect.Value) {
	f := v.Convert(tMyInterface).MethodByName("MyFunc")
	fmt.Println("got", f)
	fmt.Println("=========")
}

func main() {
	t1 := &MyType{}
	some(reflect.ValueOf(t1))
	// the zero value of an interface is nil
	var t2 MyInterface
	some(reflect.ValueOf(&t2).Elem())
}

このコードを実行すると次のエラーになる。

panic: reflect: Method on nil interface value

ドキュメントにも interface の nil の値を呼び出すと panic するよと書いてある。

Method returns a function value corresponding to v’s i’th method. The arguments to a Call on the returned function should not include a receiver; the returned function will always use v as the receiver. Method panics if i is out of range or if v is a nil interface value.

https://pkg.go.dev/reflect#Value.Method

gitlab issues と mongodb による分析

0時に寝て何度か起きて7時に起きた。今日も一日資料作りをしていた。

gitlab issues の解析

ふりかえりの資料を作っていて gitlab issues の解析を始めた。gitlab にも分析系機能は提供されているが、大半が有償機能で free では使えない。実質 free で役に立ちそうなレポートを私はみつけられなかった。

gitlab は glab cli というツールを提供している。試しに glab を使って issues の解析ができないかとやってみたが、グループ単位ではなくプロジェクト単位でしか操作できないようにみえた。そこで rest api を呼び出すための便利ツールとして使うことにした。要は rest api で任意のデータを取得してそれを使ってローカルで解析することにした。例えば、次のようにして特定ラベルを除外した特定グループのマイルストーンごとの issues をすべて取得できる。

$ mygrpid="xxx"
$ milestones="2022-11 2022-12 2023-01 2023-02 2023-03 2023-04"
$ for i in $milestones; do echo $i; glab api --paginate "groups/${mygrpid}/issues?milestone=${i}&not[labels]=Duplicate,Invalid,Wontfix" | jq -c '.[]' > "${i}-issues.json"; done

あとはこの json データをそのまま分析のためのデータベースに取り込む。今回は mongodb にインポートしてみた。mongodb だとスキーマを定義しなくても json データをそのままインポートできてアドホックな分析に便利そうに思えた。オブジェクトの入れ子構造をもつ json データのようなものを rdbms にインポートするのはひと工夫必要なことから json データをそのままインポートできるドキュメントデータベースの有効性を理解できた。インポートしたら MongoDB Shell を使うとてっとり早い。例えば、マイルストーンごとの issues の件数などは次のようにして集計できる。

gitlab> db.issues.aggregate([{ $group: { "_id": "$milestone.title", count: { "$sum": 1 } } }, { $sort: { _id: 1 }}])
[
  { _id: '2022-11', count: 348 },
  { _id: '2022-12', count: 346 },
  { _id: '2023-01', count: 338 },
  { _id: '2023-02', count: 357 },
  { _id: '2023-03', count: 347 },
  { _id: '2023-04', count: 336 }
]

担当者別に Enhance ラベルが付いた issues の件数を数えるときには次のようになる。sql を使えないというデメリットを json データをそのままインポートできるメリットの方が上回るときは mongodb のクエリを学ぶ機会になる。私も mongodb の aggregation の実行方法をドキュメントみながらやってた。全然わからないので慣れが必要になる。

gitlab> db.issues.aggregate([{ $group: { "_id": { assignee: "$assignee.username", enhance: {$in: ["Enhance", "$labels"]} }, count: { "$sum": 1 } } }, {$match: {"_id.enhance": true}}, { $sort: { _id: 1 }}])
[
  { _id: { assignee: 'bob', enhance: true }, count: 84 },
  { _id: { assignee: 'john', enhance: true }, count: 143 },
  { _id: { assignee: 'mary', enhance: true }, count: 53 },
  { _id: { assignee: 'parks', enhance: true }, count: 78 }
]

一日中リファクタリング

0時に寝て7時に起きた。昨日は夜にホテルで作業しようと思いながらテレビをみているうちに寝落ちしてた。朝から夜までずっとリファクタリングのためにコードを書いたり、コンテナ環境の設定を変更したりしていた。

mongodb のコネクションプール

MongoDB Drivers の Connection Example に次のようなことが書いてある。

Reuse Your Client

We recommend that you reuse your client across sessions and operations. You can use the same Client instance to perform multiple tasks, instead of creating a new one each time. The Client type is safe for concurrent use by multiple goroutines. To learn more about how connection pools work in the driver, see the FAQ page.

mongodb drivers の client は goroutine safe なので再利用することを推奨している。内部的にはコネクションプールをもっていて mongodb とのコネクションを再利用できる。具体的にはライブラリ内部に次のようなコードがみつかる。context にセッション情報があればそれを使い、なければクライアントの sessionPool (コネクションプール) を使ってセッションを取得して mongodb にアクセスする関数の終わりで終了処理を行う。

sess := sessionFromContext(ctx)
if sess == nil && coll.client.sessionPool != nil {
	sess = session.NewImplicitClientSession(coll.client.sessionPool, coll.client.id)
	defer sess.EndSession()
}

既存のコードはコネクションプールのことを考慮していないコードになっていたので大きくリファクタリングして効率化した。

docker hub の pull 制限

午前中はリファクタリング、午後は docker compose 環境の変更と再構築、午後はバグ修正と一日中 docker image を取得する作業をしていた。gitlab ci/cd が動くとテストと docker image 生成の処理が動くのでその過程で関連する docker image を pull する。夕方になって gitlab ci/cd で初めて次のエラーが発生することに気付いた。前にお手伝いしていた職場でもそういう現象が起こると聞いて、docker login するコードを github actions のスクリプトに追加していたので、rate limit がかかることは知っていた。

You have reached your pull rate limit. You may increase the limit by authenticating and upgrading: https://www.docker.com/increase-rate-limits.

Understanding Your Docker Hub Rate Limit によると、6時間あたり匿名アクセスは100、 free ユーザーは200を上限としているらしい。匿名アクセスは ip アドレスでカウントしているのだろうから場合によっては会社内からのアクセスをすべてカウントされたりするかもしれない。課金するとこの上限が24時間あたり5000になる。docker hub のプライベートリポジトリを利用する意図で team プランの課金を検討していたが、docker hub のアクセス制限を緩和するために課金する必要があるかもしれない。

mongodb のトランザクションの考え方

1時に寝て8時に起きた。3時頃に気分悪くて起きて少し吐いてそれからまた寝た。丸1日機能拡張とリファクタリングのために go のコードを書いていた。

mongodb のトランザクション管理

2つの web api から同じコレクションの異なるフィールドを更新したい。mongodb で厳密なトランザクションを管理するようなアプリケーションではないけど、なるべく整合性を維持できるように努めることはやっておきたい。mongodb でトランザクションに近いことを実現する方法として次の記事が参考になった。

この記事では findOneAndUpdate() という api を使って、更新時に必ず変更されるフィールドを含めることで find したときのそのフィールドの値が変わっていればエラーになってくれることでトランザクション相当の機能が提供されると書いてある。必ず変更されるフィールドとして ObjectId を使えば他の更新処理を検出するのに役立つだろうとある。いま私が開発しているアプリケーションでは同じフィールドを複数の web api から更新するわけではないのでここまで厳密なトランザクション管理は必要にない。

既存の処理が Replace を使って実装されていたのを Update を使うように変更する。Replace と Update の違いはドキュメント全体を更新するのか、一部のフィールドのみを更新するのかの違いになる。具体的には go のドライバーにおいて次のメソッドの使い分けになる。

これらのメソッドを使うことで find と replace/update の操作を1回の処理でできるから効率もよいらしい。

mongodb の view 調査

4時半に寝て11時に起きた。午後から go のコードをリファクタリングしてた。平日はメンバーの issue を監視して困ってたらコメントしたり、マージリクエストをレビューしたり、管理画面の振る舞いを検証したりと、コードを書いていても途中で作業がちょくちょく中断する。休日にコードを書くとその中断がない分、いつもよりかなり捗った。リファクタリングに思いの外、時間を取られている気がしたのはそのせいもあるか。

mongodb の view 作成

実はこれまで mongodb を扱ったことがなくて今回初めて触っている。とくに難しくもないのだけど、ドキュメントを探してやりたいことを調べたり、デバッグや開発のためのツールとしてどういうものがあるかといった知見がない。ないものは仕方ないので1からドキュメントを読みながら開発というか、リファクタリングをしている。mongodb そのものの知見はなくても、様々なデータベースを操作する開発をしてきたのでやりたいことに対して実装方法はいくつか検討がつくし、その実装を支援するための機能もあるはずだと予測がつくので探すのも早い。

ある collection から複数のデータ構造のレスポンスを返すような処理がある。こういったものは view を使うとうまく整理できると知っているので調べると mongodb view が提供されていることがわかる。3.4 から追加されたらしい。いまクエリの中で aggregation pipeline を書いている処理のいくつかは、あらかじめ view を定義してクエリすることでインフラ層を堅牢にした上で実装もシンプルにできる。さらに 4.2 から on-demand materialized view が追加されていて、標準の view と比較して aggregation pipeline の計算結果をディスクに保持するのでパフォーマンス上のメリットがある。元データの更新が頻繁でなければ on-demand を使った方がよいのだろうと推測する。

またこれまで mongodb の管理画面に mongo-express を使っている。view の振る舞いを確認しようとしたところ、どうも view には対応していないようにみえる。web ベースの管理画面を他にも探してみたが、どうも他に適当なものがない。mongodb が公式に compass というデスクトップアプリケーションの gui クライアントを提供している。macos なら brew からインストールできた。

> brew install mongodb-compass

このツールは collection も view も両方扱えるし、クエリやパイプラインも実行できて機能も充実している。web ベースじゃないとインフラとして共有はできないところだけが残念なところ。それでも開発する上ではとても強力なツールにみえる。適当にデータをインポートしたり、テストで aggregation pipeline を作成してみて、それをエクスポートして view を生成するときのスキーマ定義も作ることができた。ui も洗練していて、こんな優れたデスクトップアプリケーションは久しぶりにみたと思うぐらい感心した。