Posts for: #Mongodb

コンテナイメージの移行

1時に寝て3時に起きて6時半に起きた。スマホで呪術廻戦のゲームを開いたまま寝てた。

サードパーティの mongodb コンテナへの移行

昨日の mongodb のサードパーティのコンテナイメージ調査 の続き。

レプリカセットの削除

基本的に一度作ったレプリカセットを削除することはないせいか、レプリカセットを削除するユーティリティは提供されていない。なんらかの理由でレプリカセットを再作成したいときは、レプリカセットの設定が保存されている local database を削除する。

またレプリカセットの稼働中に local database を削除することはできないため、mongod サーバーを --replSet を指定していない状態で起動させ、そのときに次のようにして local database を削除できる。

test> use admin
admin> db.grantRolesToUser("root", ["__system"]);
{ ok: 1 }
admin> use local
switched to db local
local> db.dropDatabase()
{ ok: 1, dropped: 'local' }
local> use admin
switched to db admin
admin> db.revokeRolesFromUser("root", ["__system"]);
{ ok: 1 }

コンテナを使ったレプリカセットの初期設定

bitnami/mongodb を使うと、ローカルのシングルノードでレプリカセットを使うには次のような設定になる。

  mongo:
    image: docker.io/bitnami/mongodb:7.0.1
    user: root  # デフォルトは非 root ユーザーで起動するのでローカルの開発環境なら root で実行した方が手間がない
    volumes:
      - ./volumes/mongodb:/bitnami/mongodb
    environment:
      MONGODB_ROOT_USER: "${MONGO_USER}"  # 認証ユーザー
      MONGODB_ROOT_PASSWORD: "${MONGO_PASSWORD}"  # 認証ユーザーのパスワード
      MONGODB_ADVERTISED_HOSTNAME: "mongo-primary"  # レプリカセットのノードを ip アドレスではなくホスト名で指定する
      MONGODB_REPLICA_SET_NAME: "myrs"  # レプリカセットの名前
      MONGODB_REPLICA_SET_MODE: "primary"  # プライマリノードとして設定
      MONGODB_REPLICA_SET_KEY: "my/replication/common/key123"  # キーファイルのコンテンツ (base64 でデコードできる値)
      MONGODB_SYSTEM_LOG_VERBOSITY: 0  # ログレベル
    hostname: mongo-primary  # コンテナの内外から解決できるホスト名を指定
    container_name: mongo  # コンテナ名 (docker container ls で表示される名前)
    ports:
      - 27017:27017  # レプリカセットを運用する場合はポート番号のマッピングを一致させる必要がある
    restart: "always"

この設定でレプリカセットを初期した場合、レプリカセットの initialize 処理は、次のような config/member をもつ。

members: [{ _id: 0, host : "mongo-primary:27017", priority: 5 }]

コンテナの内部からは mongo-primary というホスト名に対して、コンテナネットワーク内のローカル ip アドレスが解決される。

c67a5ca94a77:/app# dig +short mongo-primary
192.168.240.3

ここで host os 上のアプリケーションから mongo コンテナに対してレプリカセット接続をする場合 replicaSet=${レプリカセットの名前} のパラメーターを追加する。

mongodb://root:password@localhost:27017/?authMechanism=DEFAULT&replicaSet=myrs

これは localhost:27017 にレプリカセットの接続を試行し、接続できるとレプリカセットのメンバーが返される。

レプリカセットのメンバーには mongo-primary:27017 という設定が行われているため、mongo-primary というホスト名に対して host os 上で名前解決できる必要がある。そのために /etc/hosts に次の設定を行う。

$ sudo vi /etc/hosts
...
127.0.0.1 	mongo-primary

compass で接続した場合、レプリカセット接続であれば、レプリカセットの名前が接続情報として表示される。

ダイニングテーブル引き取り

実は火曜日にも長机を引き取りに行ってきて、今日はダイニングテーブルを引き取りに行ってきた。この3日間で2つもテーブルが手に入った。いつも目ぼしいと思ったものは、すぐに他の人と取り引きが成立してしまうのに、たまたま続けて私と取り引きが成立した。車で20分ぐらいの距離のマンションまで引き取りに行った。20時の予定を、19時10分には着いてしまって、先方も快く対応してくれた。私よりも見た目すこし年配の方で人当たりのよい感じの方だった。ジモティのやり取りはその人の性格が出るもので、受け渡しだけささっとやって余計な話しはしないパターンもあれば、愛想よく話しながら受け渡しをするパターンもある。先方によると、大事に使っていたテーブルのようにみえるので私も離れのオフィススペースで大事に使おうと思う。

mongodb のサードパーティのコンテナイメージ

23時に寝て3時に起きて寝たかどうか覚えていないうちに6時半になっていて7時半に起きた。

json を介した go の bool 値のバリエーション

go-playground/validator のバリデータには required というバリデーションオプションがある。しかし、このオプションは go のゼロ値でないことをチェックするという仕様になっている。bool のゼロ値は false となるため、リクエストした JSON データに false を設定していたのか、未設定だったのかの違いを検出できない。これはバリデータの問題ではなく、go の json ライブラリの制約のようなもので使い勝手のよい仕様とは言えない。私もこの振る舞いに起因する不具合に遭遇したこともあるし、こういうときにどうしたらよいかも過去に3回ぐらいは調べている気がする。

現時点での私の最適化は次のコードになる。データ構造として *bool 型にすれば、ポインタ型のゼロ値は nil となるため、true, false, nil の3値でバリデーションできる。しかし、私はこのデータ構造を好ましく思わない。というのは、内部的には true/false の2値でしか管理しないメンバーを、json のバリデーションのためだけに nil も許容する3値にすることがよい設計だと私は思えない。そこでバリデータによるバリデーションは諦めて、json の Unmarshal 処理をフックしてバリデーション相当の処理を自分で実装する。このやり方のデメリットはメンバーが追加されたときに自分で UnmarshalJSON() メソッドを保守する必要がある点になる。しかし、メリットとして内部のデータ構造の型は bool 型で扱える。一概にどちらがよいとは言いにくいかもしれないし、設計上の好みかもしれない。

type reqMyData struct {
	Name       string `json:"name"`
	View       *bool  `json:"view"`
}

type MyData struct {
	Name       string `json:"name"`
	View       bool   `json:"view"`
}

func (d *MyData) UnmarshalJSON(data []byte) error {
	var tmp reqMyData
	if err := json.Unmarshal(data, &tmp); err != nil {
		return fmt.Errorf("failed to unmarshal as reqMyData")
	}
	if tmp.View == nil {
		return fmt.Errorf("required view field")
	}
	d.Name = tmp.Name
	d.View = *tmp.View
	return nil
}

サードパーティの mongodb コンテナイメージ

先日の mongodb のレプリカセット調査 の続き。コードレビューをしていて bitnami/mongodb というサードパーティのコンテナイメージを使った方がよいのではないか?というコメントがあったのでその調査をしてみた。VMware 社が提供しているサードパーティのコンテナイメージらしい。

MongoDB(R) is run and maintained by MongoDB, which is a completely separate project from Bitnami.

まず MongoDB プロジェクトとはまったく別管理であることが書いてある。

Bitnami イメージを使用する理由

  • Bitnamiはアップストリームソースの変更を綿密に追跡し、自動化されたシステムを使用してこのイメージの新しいバージョンを迅速に公開します。
  • Bitnami イメージでは、最新のバグ修正と機能をできるだけ早く利用できます。
  • Bitnamiのコンテナ、仮想マシン、クラウドイメージは、同じコンポーネントと構成アプローチを使用しているため、プロジェクトのニーズに応じて形式を簡単に切り替えることができます。
  • Bitnamiのイメージはすべて、minideb(最小限のDebianベースのコンテナイメージ)またはscratch(明示的に空のイメージ)をベースにしています。
  • Docker Hubで利用可能なすべてのBitnamiイメージは、Docker Content Trust(DCT)で署名されています。DOCKER_CONTENT_TRUST=1 を使用して、イメージの完全性を確認できます。
  • Bitnamiコンテナイメージは定期的にリリースされ、最新のディストリビューションパッケージが利用可能です。

MongoDB®を本番環境で使用したいですか?Bitnami Application Catalogのエンタープライズ版であるVMware Tanzu Application Catalogをお試しください。

mongo の公式イメージは ubuntu をベースイメージにしている。ubuntu よりは minideb の方が軽いのかな?そしてちゃんと upstream にも追随しているみたい。このベースイメージの違いによるものかは定かではないが、結合テストのイメージも移行してみたところ、10-20秒ほど結合テストの実行時間が速くなった。割合にすると10%程度かな。

KubernetesにMongoDB®をデプロイするには?

Bitnami アプリケーションを Helm Chart としてデプロイすることは、Kubernetes 上で当社のアプリケーションを使い始める最も簡単な方法です。インストールの詳細については、Bitnami MongoDB® Chart GitHub リポジトリを参照してください。

Bitnami コンテナは、クラスタへの Helm Charts のデプロイと管理に Kubeapps と一緒に使用できます。

helm chart も提供しているようで、いずれクラウド版を作るときに MongoDB も k8s 上にデプロイする上でこのことは都合がよいように思える。

レプリケーションを前提とした初期設定があり、entrypoint スクリプトもいくつか読んでみた感じだと、きれいに管理されていて保守もちゃんとやってくれそうにみえる。

昨日、導入したばかりの公式イメージ + 自作スクリプトによるレプリケーション設定を廃止して、Bitnami のコンテナイメージを使うことに決めた。

owner/permission の違うファイルとリポジトリ管理

23時に寝て2時に起きて6時に起きて7時過ぎに起きた。なんか微妙な寝方をした。

先日の mongodb のレプリカセットの調査 の整理をしてマージリクエストを作成した。共通鍵の keyFile をどう扱えばいいのか、わからなくて、一旦コンテナ内の tmp 領域にコピーして、それを entrypoint スクリプトでコピーしてから owner/permission を変更するというやり方で、リポジトリ管理で共有しやすいようにしてみた。entrypoint スクリプトは root 権限で実行されることも理解した。

volumes:
  - ./mongo/keyfile:/var/tmp/keyfile.orig
command:
  - mongod
  - --keyFile
  - /data/keyfile
  - --replSet
  - "myrs"
entrypoint:
  - bash
  - -c
  - |
    if [[ ! -f /data/keyfile ]]; then
      cp /var/tmp/keyfile.orig /data/keyfile
      chmod 400 /data/keyfile
      chown mongodb:mongodb /data/keyfile

    fi
    exec docker-entrypoint.sh $$@    

テックブログを読む会

昨日、西原さんに教えてもらった テックブログを読むイベント を探したら毎週月曜日に行われているようだった。早速 テックブログ一気読み選手権20231211杯 に参加した。HackMD で読んだメモを管理している。記事を選択して、読んで、所感をまとめて、他の人たちと共有する。ただそれだけのイベント。ちょうど30分で終わって、自分の勉強にもなったし、他の人の話しも聞いて参考になった。たった30分でも、なにもやらないよりずっとよい。1ヶ月ほど参加してやり方を学んだらチームにも展開してみようかと考えている。

mongodb のレプリカセットのデプロイ調査

4時前に寝て6時半に起きた。1時過ぎまで作業して、帰って少しゲームして、うまく眠れなくてだらだらしていた。

mongodb のレプリカセットの調査

以前 mongodb でトランザクションを使うときにレプリカセットが必要 なことがわかった。他機能の開発途中だったので一旦後回しにしていたものを回収している。状況によってはメンバーに委譲してもよかったんだけど、私が遊撃で出張ってみることにした。実際に調べてみてコンテナの運用も考慮するとけっこう難しいことがわかってきた。

mongosh からは Replication Methods を使ってレプリカセットの操作ができる。これはユーティリティのようなもので mongodb としての低レベルのコマンド操作は Replication Commands になる。mongo-go-driver はレプリカセット向けのユーティリティを提供していないため、Replication Commands を RunCommand() の低レベル API を使って自分で実装しないといけない。

例えば、レプリカセットの初期化をするときは次のように replSetInitiate というコマンドを適切なパラメーターで呼び出す。あまりドキュメントで丁寧に説明されていないので試行錯誤でエラーメッセージをみながら実装することになる。とくにはまるのが mongod のサーバーは --replSet myrs のようにレプリカセットを指定して起動させるものの、初期化コマンドを実行するときはまだレプリカセットを設定していないため、レプリカセットを指定せず、且つ direct パラメーターをセットしないと mongod サーバーに接続できない。この微妙な設定を把握するのにはまった。これが正しい手順かどうかもわからないが、ググったりしているとフォーラムでそういったコメントが散見されたりする。おそらく mongosh の Replication Methods を使うと、クライアントからサーバー接続は裏方でよしなにやってくれるのでそっちの方が簡単ではある。

func (r *ReplicaSet) Initiate(ctx context.Context, config bson.M) error {
	client, err := r.connectDirect(ctx)
	if err != nil {
		return fmt.Errorf("failed to connect with direct: %w", err)
	}
	defer client.Disconnect(ctx)

	var result bson.M
	cmd := bson.D{{Key: "replSetInitiate", Value: config}}
	if err := client.Database(r.db).RunCommand(ctx, cmd).Decode(&result); err != nil {
		return fmt.Errorf("failed to run replSetInitiate(): %w", err)
	}
	log.PrettyPrint("completed to initiate", result)
	return nil
}

func (r *ReplicaSet) connectDirect(ctx context.Context) (*mongo.Client, error) {
	opts := options.Client().
		SetAuth(options.Credential{
			Username: r.config.User,
			Password: r.config.Passwd.String(),
		}).
		SetHosts(r.config.Hosts).
		SetDirect(true) // must be true
	return mongo.Connect(ctx, opts)
}

func InitSingleReplicaSet(
	ctx context.Context, cfg *config.MongoDB,
) error {
	rs := NewReplicaSet(cfg)
	initConfig := bson.M{
		"_id": cfg.ReplicaSet,
		"members": []bson.M{
			{"_id": 0, "host": "localhost:27017"},
		},
	}
	return rs.Initiate(ctx, initConfig)
}

さらに mongod サーバーを起動するときに --replSet--keyFile (認証が必要な場合のみ?) という2つのパラメーターを指定する必要がある。--replSet はレプリカセットの識別子を指定する。そして --keyFile は共通鍵を指定する。この共通鍵を生成するには次のようにする。

$ openssl rand -base64 756 > my-mongo-keyfile
$ chown mongodb:mongodb my-mongo-keyfile
$ chmod 400 my-mongo-keyfile

普通のサーバーインスタンスならすぐできることだが、コンテナの運用において面倒なのが owner とパーミッションを設定しないといけないところ。mongo のコンテナは mongodb ユーザーで起動するため、root でマウントされたファイルシステムには書き込みできなかったりして keyFile の配置をどう扱えばよいのかが難しい。docker hub の mongo の issues でもどうやって設定したらいいの?って議論が発散している。mongo 本体が公式のスクリプトや仕組みを提供していれば済む話しだけど、どうもそうではないみたい。だから泥臭い方法で自分でなんとかしないといけないようにみえる。

dockertest でもレプリカセットの設定について次の issue として登録されている。mongo のコンテナを使ったテストの場合、dockertest のレイヤーが挟まるのでさらにわかりにくくなっている。テストを動かすためにどういった設定が必要かは把握できたのでなにかよい方法を考えてコントリビュートしたい。

穴場のビアバー

穴場のビアバー

0時に寝て何度か起きて6時半に起きた。移動日の疲れもあってか、いつもよりよく眠れた気がする。

朝食バイキング

先々月から大崎探索 をしていて ニューオータニイン東京 に泊まってみた。川沿いの道を歩きながら朝食バイキングもおいしい。食品の品数があると満足度が高い。

プロジェクトの進捗報告

出張したときの月例報告の11回目。前回の進捗報告はこちら

まだ開発の序盤なので 事前に準備した資料 の内容を一通り終えて雑談の時間も多かったように思う。3回目の開発フェーズになるのもあり、メンバーの練度も上がって、開発の手際はかなりよくなっているように私からはみえている。それらも含めて、メンバーが自律的に issue を作ったり、タスクを自分に割り当てたりしながら開発が進むようになっていると報告した。プロジェクトオーナーからも、最近のマイルストーン定例をみていて、メンバーが気付きが増えている、自律的によくないところを改善しようという意思がみえるといった話題も出ていた。課題管理のプラクティスを実践してきて、着実によい開発者の習慣を身につけて、よい開発チームに育ってきているように、私からもみえている。あとは課題管理についてのコンテンツを私が書かないといけないのを、バーンナウトの影響もあってか、先月はさぼってしまっていたので、今月こそなんか書きますと、自分を追い込む意図でもその場で話題にした。

mongo とトランザクションとレプリカセット

mongo には トランザクション の機能が提供されている。しかし、トランザクションを使うためには レプリケーション を有効にしないといけない。レプリケーションは通常は複数台のマシンでクラスタリングを構築するものになる。うちらはシングルノードの mongo を扱っているのでレプリケーションを必要としていない。それでも、ドキュメントを読んでいると、スタンドアローンでも本番環境ではレプリカセットを使う方がよいといった説明もみつかる。

ちょうど開発の要件としてトランザクションを必要とする状況も出てきた。mongo のレプリケーションやトランザクションの仕組みを理解するよい機会かもしれない。11月中には調査したいというところ。

エビスバー

大崎探検の2日目。YEBISU BAR 大崎店 へ行ってみた。大崎駅横の駅ビルの2階にある。アクセスもよい。水曜日なのに20-21時半ぐらいまでいて、お客さんは3組ぐらしかいなかった。よい意味で空いてて静かにゆっくり飲めてよかった。穴場のバーをみつけた。ビール2杯とつまみ2品でちょうど3000円程度。ちょっと割高かもしれないけど、場所とお店の雰囲気とゆっくりできたことを考慮するとちょうどよい価格帯にも思える。またゆっくり軽く飲みたいときに行こうと思う。

相続税の申告の一歩手前

22時頃から寝始めて何度か起きて6時に起きた。早寝早起き。

会計士事務所への訪問

相続税の申告手続きを未だにやっている。

父が失くなったのが 昨年の12月26日 になる。相続税は死亡を知った日から10ヶ月以内と期限が決められている。それを過ぎると算税や延滞税などのペナルティが科せられる。うちの期限は10月26日になる。1-3月ぐらい葬儀やらお仕事やらで忙しかったものの、4月ぐらいから相続の手続きに着手した。5月29日に親族の相続関連の書類を取りまとめて弁護士さんへ送付した。それから銀行口座の解約やら司法書士さんやら税理士さんの作業やらなんやらあって、いまもまだ会計士さんに申告の書類を作ってもらっているところ。その過程でいくつか質疑応答があってそれを調査したりしている。

その会計士さんの事務所が近所にあるのでお昼に訪問して挨拶してきた。申告に必要な書類の提出をしつつ軽く打ち合わせをした。税務署は20年遡って口座のお金を動きを調べるらしい。話しているときに死亡保険金とかないですか?と聞かれて、母が受けとったと話していたなと思い出して、それも父の遺産として扱う必要がありますと言われて、確かにそうだと思って申告漏れしていることに気付いた。これで死亡保険金の書類が必要になってまた取り寄せに時間がかかる。こんな感じに五月雨式に遅れていくので10ヶ月ぎりぎりになりそうな雰囲気。来週中に申告が終わる嬉しいなといったところ。

mongodb の初期化ツール

ちょっと前から少しずつ mongodb の初期化ツールを作っている。コレクションの作成ならびにインデックスの追加を、さらに初期データの投入も制御したい。mongodb のバージョンが 6.0.x のときは作成済みのコレクションに対して同じ設定で作成しようとすると、既に作成済みというエラーが発生していた。それが 7.0.x になってエラーにならないようになっていることに気付いた。調べてみると、次の issue で同じ設定なら作成の結果に関係なく冪等であるのでエラーとして扱わなくてよいという考え方になる。

これは初期化ツールを作っている私にとっては朗報で、同じコレクションに対して複数の操作をしても変更した設定だけが有効になるといった振る舞いをする。こういう細かい所もバージョンアップをしながら改善していくことが伺えて学びになった。

資料作りの一日

23時に寝て何度か起きて6時に起きた。疲労も溜まっているのでなるべく早く寝るように努めている。旅行中ずっと早起きしていたから早起きするのは苦にならない。

今開発の大きなふりかえりの資料作り

ふりかえりのために課題管理システムの issue 情報から統計的な数値を取得する。

gitlab の cli ツールを使って issue 情報を取得して mongodb にインポートする。

$ glab api --paginate "groups/product%2Funicorncidm/issues?milestone=2023-09-05&not[labels]=Duplicate,Invalid,Wontfix" | jq -c '.[]' > 2023-09-05-issues.json
$ mongoimport --authenticationDatabase=admin --uri "mongodb://root:secret@localhost:27017" --db gitlab --collection issues 2023-09-05-issues.json

mongodb で aggregation (sql で言うところの group by 句に相当する) するときはパイプライン処理を実装する。例えば、最初にデータをフィルターして、次にグルーピングして、最後にソートするのは次のようなパラメーターになる。

$ mongosh --username root --password secret
test> use gitlab
gitlab> db.issues.aggregate([
  { $match: { $or: [ { "milestone.title": "2023-09-05" },{ "milestone.title": "2023-09-19" } ] } },
  { $group: { "_id": { milestone: "$milestone.title" , author: "$author.username" }, councount: { "$sum": 1 } } },
  { $sort: { _id: 1 } }
])

これで前回の開発のときに取得した数値と今回の開発の数値を比較してみると、いろいろわかることもあって、メンバーが成長していることも伺えて、課題管理をしながら開発を進めることのメリットを実感できる開発になったのではないかと思う。gitlab のアクティビティ図 (草生え図) をみても前回の開発よりも草がたくさん生えているので課題管理に習熟している様子が伺える。これをあと2-3年ぐらいすれば、課題管理を使いこなせる一般の開発者になるのではないかと思う。課題管理 + イテレーション開発でチームビルディングしていくところの、地盤のようなものはできたようにみえる。

この草生え図を匿名化して利用許可をもらって、うちの会社でいうところの課題管理ができるようになると、メンバーの働き方はこうなるというサンプルとして紹介させてもらうつもり。また来週メンバーにもその許諾を取ろうと思う。

次開発の要件打ち合わせの資料作り

今開発を始めるときに洗い出した要件の未対応のものと、今開発でやり残した非機能要件の開発課題を資料にまとめてたたき台とした。それだけでも3-4ヶ月分の開発課題になりそうだし、アーキテクチャとして大きな意思決定も1つある。私自身、7割型は決まっているが、考え方や要件によってはもう1つの案でいくのもよいかもしれない。機能要件は実際にお客さん先へ導入するときにもいくつか出てくるだろうけれど、非機能要件の運用に影響を与える懸念のところはなるべく早く改善しておきたい。次の開発期間にそこだけ対応すれば、一定の安心をもってお客さんへ提供できるようになると思う。

お盆の最終日

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 }
]