Posts for: #Go

3つめのテックブログ

2時に寝て何度か起きて7時に起きた。朝から弁護士さんにメールの返信をしたりしていた。午前/午後と普通に運用ツールの開発をして夕方に勉強会をして、夜に軽く呑みに行ってきた。

go-ldap テックブログとその勉強会

水曜日から テックブログの執筆 に着手して、木曜日のお昼には下書きを書き上げて社内レビューをお願いしていた。ldap プロトコルの振る舞いについて調べたことを書いた。あまり自信はなかったけど、社内のシニアエンジニアにレビューしてもらって、よく書けているとコメントをもらってシンプルに嬉しかった。いま開発の佳境のしんどい時期に、非開発以外のことに時間をとって記事を書いて報われた気持ちになった。

今日のチーム勉強会は私が担当だったのでこのテックブログの記事を解説した。実際に go-ldap のソースコードを一緒に読みながら進める。書いたり話したりすると、ちゃんと自分が理解できているかどうかを確認ができる。そして、コードを読みながら説明しているときに、記事に書いてある内容が一部間違っていることにも気付けた。こういう体験の繰り返しで私は自分を信頼しないことを学ぶ。私の浅い理解や未熟さを実感する機会があって、また次にがんばろうというモチベーションにもつながる。

自身の理解度の確認やチームへの共有、モチベーションコントロールなど、複数の意味で自分が書いたテックブログの記事を解説する勉強会はいいように思えた。

思い入れのあった内容を書いて公開してしまうと、燃え尽きのような、少しやり切った感を感じていた。しかし、開発はまだ佳境の途中なのでここで立ち止まることはできない。それで呑みに行ってみた。とくに目的なくみつけたお店に初めて入ってみた。普通の居酒屋よりちょっとだけ値が張る感じの、落ち着いたお店で、私よりも少し年配 (にみえる) マスターが1人でやりくりしていた。軽く話しながら晩ご飯を食べれてやや救われた。また行ってみようと思う。

台風の暴風雨にびびった

台風が来るということだったので昨日は18時には家に戻ってきてゆっくりしていた。とくに何をしていたわけでもないけれど、なぜか眠れなくて3時ぐらいまでは起きていた気がする。あまりちゃんと眠れない中、7時に起きた。朝から外の暴風雨がすごくて人が飛ばされそうな勢いだった。さすがにオフィス行けないなと諦めて家でリモートワークしていた。お昼過ぎぐらいまで暴風雨が続いていたと思う。夕方になってから外に出たら普通の雨になっていてそれからオフィスに来た。

課題管理とプロジェクトマネージメントの話を熱く語る

理由があって先日 チェックした音声データ とは違う音声データを使って昨日の夜に公開された。週末働いてバテていたせいか、昨日は余裕なくて聞けなかったものの、深夜に聞き始めた。よいこと言っているなーと自画自賛しつついくつか間違ったことも話してしまっているけれど、私の話しにそこまで注意して聞く人はいないでしょう。

課題管理の話題になると、ついつい熱中して話してしまう。「熱く語る」と書かれてしまうのはこの分野に熱意をもっている人が稀だからかな。私はこの1-2年この分野をずっと調べているから、ここで話した10倍ぐらいのコンテンツをもっている。勉強会の資料も数個はあるし、スライドは200枚ぐらいある。そして、調べれば調べるほど私が分かっていないことも分かってきて、もっともっと調べたいことがある。しかし、いまいまはもう体力と気力がない。

エージェントアプリケーション開発

昨日の続き 。昨日レビューをしっかりしてもらってマージした。windows ad サーバーとの dirsync の通信のところを、一切動かさず、既存のコードをインターフェースにあうように作り直したものの、実際に動かしてみると非同期の制御が意図したデータフローでなかったり、windows ad サーバーの知らない仕様があったり、細かいバグもあったりで半日ほどかけてデバッグしながらバグ修正してた。単体レベルのテストでこのバグ数だと、qa レベルだとさらにバグありそうだなという感触だけ確かめた。その後 dirsync の検索も非同期になった方が嬉しいなと思ってちょっとリファクタリングして検証がてら提案してみた。特別なことをしなくても go-ldap の非同期検索を使ってそのまま動くことも確認できたのでこれはこれで役に立つと思う。

週明けから疲労困憊

4時に寝て7時に起きた。遅くまでコードを書いていたわけではないけど、眠れなかったのとお腹空いて夜にもぐもぐ夜食を食べていたせいかもしれない。たいてい月曜日は元気いっぱいなのに今日はバテバテだった。

wifi 復旧

金曜日からコワーキングスペースの wifi が不通 になっていた。午前中に業者がフロアの機器室の扉 (施錠されている) を開けて作業していた。週末に復旧できなかったのはシンプルに業者との保守契約に休日対応オプションが入っていなかったのかもしれない。運悪く連休に発生したために約3日間停止していた wifi が11時過ぎにようやく復旧した。フロアにまったく人影がなくて、みんなお盆休みなのかもしれない。

エージェントアプリケーション開発

昨日の続き 。朝からマージリクエストを作ってメンバーにレビューしてもらう。1000行程度の diff になったのでレビューするのも大変。基本的なロジックは問題なかったけれど、メンバーがしっかりレビューしてくれたおかげで細かいところをみていくと、いくつか修正点があった。感謝。レビューを終えて、マージして、テスト環境にデプロイして、一通り動いていることは確認した。ここからは運用レベルの検証に入っていく。週末も昨日もあまり寝てないのもあって、台風がきているし、今日はここで休むことにした。疲れたー。

神戸の高速道路事情

4時に寝て7時に起きた。蛍光灯をつけっぱなしで寝たのであまり深く寝ていない感じがする。

テーブル受け取り

先日の ジモティー取引 のテーブルを受け取りに行ってきた。テーブルのサイズは次になる。

  • 縦: 120cm
  • 幅: 75cm
  • 高さ: 70cm

ちゃんとメジャーで測って車の荷室に入ることを確認して行った。やや工夫しながら積み込みしないと当たるところもあった。後部座席を倒してこのぐらいのサイズで荷室が埋まる。もうひとまわりぐらいの余裕はあるかな。

あとは椅子を探すだけ。次は9月に実家へ帰ろうと考えているのでそれまでに準備したい。

阪神高速3号神戸線

以前 実家からの帰りで渋滞に遭遇 してから、たいてい実家から帰ってくるときは白川まで出て7号北神戸線で帰るようになった。少しだけ遠回りだけど、まず渋滞しないので時間的には早く着く可能性が高い。今日は京橋から西明石まで高速道路で走ってきた。30-40分といったところ。往路は白川経由で1080円 (渋滞しない) 、復路は須磨経由 (3号神戸線と呼ばれるルート) が1040円で若宮-湊川-柳原あたりで安定の渋滞につかまった。明らかに白河経由なら20分は早く帰れたと思う。下りはまだしも、上りは渋滞に遭遇する確率がかなり高い。次の記事によると、阪神高速3号神戸線は全国ワースト1位の渋滞区間になるらしい。もう休日の上りで3号神戸線を走ることはないかもしれない。

東京圏の首都高速など、全国6都市圏にある都市高速道路のうち、最も渋滞する区間が、阪神高速3号神戸線のおもに神戸市内の区間です。

阪神高速3号神戸線はなぜ混む?

3号神戸線が渋滞する理由を調べるといろいろな意見がある。それっぽいのをまとめておく。

  • 神戸-大阪という都市間の交通量は多いのに土地が狭い
    • 山と海に挟まれていて土地がないから下道の幹線道路 (国道) も貧弱
  • サグ (下り坂から上り坂に変わる箇所) がいくつかある
  • 出口のすぐ先に信号があるから出口付近で混雑する
  • 鋭角カーブがあるので減速する

次の記事も図解があってわかりやすかった。

エージェントアプリケーション開発

昨日の続き 。お昼に2時間ほどやって、夕方に晩ご飯の買いものへ行ったら強い通り雨に降られて一旦家で休んでいるうちに少し寝て、また涼しくなった20時頃から4時間ほどやってた。windows ad サーバーとの dirsync の通信もコードを読みながらインターフェースをあわせるように書き換えていった。実際に動かしてないから動かないかもしれない。このテストは明日に行う。channel を使った非同期/並行処理のところは難しいのでなるべく共通化してアプリケーション側はそれを再利用する形にしたい。マージリクエストを送る一歩手前までは整理できた。あとはテストだけ。疲れたけど、ようやく先週のスケジュールに復帰した。

室温と集中力との相関関係

2時に寝て2回ぐらい起きて7時に起きた。朝からやや疲れ気味。

ストレッチ

疲労と暑さと出張でバテバテ。今日の開脚幅は開始前157cmで、ストレッチ後160cmだった。数値はよい感じ。トレーナーさんが言うにはお尻と肩が硬かったらしい。私の感覚ではそれらに加えて、ふくらはぎの後ろの筋がかなり痛かった、とくに左足。さらに体全体がだるくて疲れが溜まっているなーという印象も受けた。実は8月21日の週にまた出張する予定になったので体力がもつか、不安も感じるようになってきた。

空調工事の結果

先日の 暑さ対策委員会 の続き。

出張から帰ってきて、エアコンの冷媒切り替えを終えたはずのオフィスの室温はどう変わったか?その結果が楽しみにで昨日、出張帰りにオフィスに寄ってみたというのもあった。結論から言うと

何の成果も!!得られませんでした!!

温湿度計を買っておいてよかった。ちゃんと数値でどう変わったかを測れるもんね。だって午前中は34℃、お昼から32℃、夜は28℃、なんも変わってない。この部屋が暑い理由は冷媒が原因ではなかったという切り分けはできた。がっかりして、また運営会社に電話して、成果がなかったことと、前に断熱のブラインドに変えてくれると言っていた件はどうなったの?とツッコミまで入れてしまったよ。もう建物の構造的にこの区画は涼しくならないんやろか?

オフィスの wifi 不通

昨日の午前中、運営会社のスタッフとやり取りしていて wifi が不通になっていることに気付いた。デスクトップマシンは有線ネットワークを使っていてそちらは疎通しているものの、wifi のアクセスポイントまでは接続できるが、なぜかその先のインターネット接続が不通になるという現象が発生していた。たまたま部屋の外に出たときにコワーキングスペースの利用者が運営会社のサポートに電話して、やや強い口調でクレームしていた。コワーキングスペースの利用者向けには wifi ネットワークしかないため、コワーキングスペースにわざわざ来てインターネット繋がりませんで怒る気持ちは分からないでもない。

ネットワーク障害が発生することそのものは仕方ないものの、発生してから翌日の20時時点でその wifi ネットワークの障害が解消していない。同じフロア内にある有線ネットワークが疎通していることから、このネットワーク障害は小規模な原因であることが推測される。それこそ通信機器を再起動すれば直るかもしれない。1日以上放置している運営体制を懸念に思ってしまったのだけど、これは職業病?週末に知らずにコワーキングスペースへ来られる方がいるのではないだろうか?

エージェントアプリケーション開発

今週中に完了させておきたい機能開発 が全然進捗しなかったので週末に取り組む。昼間は暑くて (34℃) 集中力が出なかったので15時から19時まで家に帰ってエアコンの効いた部屋で寝てた。ただただ寝てた。私が4時間起きずに眠れることは稀なので自分でも驚いた。その後、オフィスに戻って19時半ぐらいから開発に臨んだ。夜のオフィスの室温は28℃前後なので十分に涼しい (と適応している自分がいる) 。

ldap サーバーでのユーザーとグループのエントリー、それぞれの変更を検知して id 連携しないといけない。既存の実装は1つのクエリにユーザーとグループの検索条件を OR 条件にして両方のエントリーを取得するようにしている。このフィルター自体は問題ないが、その後のユーザーとグループの判別に DN の接尾辞の部分マッチで判定していた。これは微妙な判定方法だ。このやり方だとユーザーとグループの DN の接尾辞が同じときに運用できない。ユーザーとグループは分けて管理した方が要件がシンプルで運用も実装もわかりやすいだろうと考え、それぞれのクエリを非同期/並行に動かすようにした。こういうのは go 言語の得意とするところ。一通り動くようになったら3時半ぐらいになってた。涼しい方が集中できる。

出張帰りにオフィスに寄って開発する

18時19分の新幹線に乗って21時前に新神戸駅に着く。この時間帯で帰ってくるのが楽な気がする。新神戸駅についたらいつもそこからタクシーで帰りたいと思う。いまうちの会社は全然儲かってないのでそんなことできないけれど、いつか余裕ができたら出張帰りの電車乗り継ぎをやめてタクシーで直帰してよいルールにしたい。

go 1.21 への移行

昨日 Go 1.21 is released! されたことを知った。自分の作業を中断して早めに移行して問題があれば検出できるしておきたかったので 1.20 から 1.21 への移行をしていた。もっとも大きな移行としてログ出力をすべて log/slog へ移行した。もともと cybozu-go/log という標準の log パッケージに近いものを使っていたので移行そのものは大きな課題にはならなかった。一方で変更することによって運用にどういった影響が出るかは実際に動かさないと気付かないこともあるだろうという視点で早く移行したかった。昨日の夕方に 1.21 で一通りは動くようにして、今朝から細かいところの修正は他のライブラリとして slices/maps といった新規に追加された標準ライブラリを使うように変更していった。リポジトリが数個あるので単純に労力だけの問題。ついでに go-ldap の ci 環境の設定も変えておいた。

github-api-tools 再び

帰りの新幹線の中でてらださんとやり取りしていた。issue 検索に llm の技術を使ってサンプルアプリケーションを作るというアイディアが進んでいて、公けの github issues のデータを使えばいいという話しをした。実際に学習データにするには、一定の前処理したテキストとメタデータが必要になる。github issues からあるプロジェクトの情報を一括で取得する方法はないか?という相談を受けて、github の rest api などを使って取得するのがよいのではないか?と提案した。

私も過去に github での作業時間の検証のために github-api-tools というツールを作っていた。21時半頃にオフィスに戻ってきて、せっかくなので試しにやってみるかと翌1時過ぎぐらいまでコードを書いていた。過去にも issues の機能も作った方がよいよねという課題は残してあった。

python のコードを滅多に触る機会がなくなってしまったので勘所を思い出したりするのにやや手間取った。それでも自分が過去に作ったものなのですぐにできた。

go-ldap の syncrepl 機能のレビュー対応

1時に寝て何度か起きて7時に起きた。今週もよく働いたからバテバテ。

ストレッチ

これまでも慢性的に右足全般は悪かったのだけれども、今日は左足の張りがある (痛い) ところと右足の張りがある (痛い) ところが全然違うことに気付いた。トレーナーさんによるとさらに今日はいつもより上半身の腕も硬かったという話しだった。これから1ヶ月か、1ヶ月半ほどは開発の佳境で忙しくなる (座っている時間が長くなる) ので体調がよくなることはないと思う。今日の開脚幅は開始前155cmで、ストレッチ後158cmだった。普段通りなのでこの調子なら問題ない。

syncrepl のレビュー対応

先日のレビュー対応 の修正。通信プロトコルの処理を実装するには想定したパケット (byte 列) をデコードしないといけない。そのために誤っているとすぐに panic する。開発しているときは既存の処理の振る舞いと競合してあちこちで panic してデバッグが容易ではなかった。そこで既存処理とは分割して先ずは実装した。その後、プロトコルの仕様と対応するパケットを理解してしまえば、どこを直せばよいか把握できているので既存の処理と共存させることはとくに難しくなかった。私の先入観であちこち変更しないといけないのでは?と思い込んでしまっていたのをレビューアの指摘で気付くことができた。感謝。

これでレビュー対応を終えた。pr を送ってから2週間放置されていた。そこでレビューしてくれないかとお願いしたら2人のメンテナーがすぐにレビューしてくれた。これは 非同期検索 でのやり取りを経て私の信頼があがっていたためと思われる。この機能はお仕事の開発にも使う。ちょうどいま開発の佳境に際して花を添えるよいタイミングと言える。

進捗報告の資料作り

気付いたら来週は出張の週になる。月例報告のための資料を作っていないことに今日気付いて慌てて資料を作った。いまは開発の佳境の時期なので、開発方法論に新しいことを試しているわけでもないし、この1ヶ月のやったことの進捗を報告するだけ。内容は固まっていて資料を作るのはそんなに大変ではない。理想的なスケジュールだと9月上旬で開発完了を目指していたが、それは無理そうだと判断した。その次のイテレーションで完了できるように目指す。さらに追加でバッファのイテレーションももう1つある (と私が勝手に思っている) 。2つイテレーションを伸ばすと開発は1ヶ月の遅延となる。このぐらいのブレは私の中では許容範囲だけれども、一般の会社だと認められるのかどうか、開発マネジメントの機微によって分かれるのかもしれない。

オフィスは夜の方が涼しい

22時からオフィスで作業していたのもあり、そのまま夜通しで作業して、6時に帰ってきて寝て9時に起きた。こんなことやっているから体調が悪くなりそうな気がする。宅急便を受け取る必要があったので午前中はのんびりしてた。なぜか宅配ボックスは毎日埋まっていてまったく使えない状態になっている。今後はコンビニ受け取りかオフィスに送るようにしよう。

ジモティー検索

以前 ジモティーで椅子を購入 してよかったので実家で使う家具でよさそうなもの、具体的にはダイニングテーブルを探している。

ジモティーとヤフオクの違いとして、ジモティーはオークションじゃないから持ち主が処分したい品物はゼロ円で譲りますと出品されていたりする。その代わり、送付はせず引き取りが必須となる。ダイニングテーブルのような大きいものだと粗大ゴミに出すにもお金がいるから持っていってくれるならそれでいいといった感覚だろうか。さらに地域に特化した引き取りを必須条件にするので誰もが応募できるわけでもない。常にみていなくてもタイミングがあえば格安で入手できる可能性がある。そこで社用車の荷室スペースのサイズを確認していた。後部座席を倒すことで荷室スペースを拡げられる。このサイズならたいていのダイニングテーブルは積載できそうに思える。

  • 長さ: 680-1,480mm
  • 幅: 1,000mm
  • 高さ: 850mm

実家の離れオフィスのリモートワーク、さらには今後のコワーキングスペースとして使うときの家具をジモティーでのリサイクルも考慮しながら揃えていきたい。

scim 向けの urn パーサーの拡張

先日 チームのメンバーが実装した scim 連携のレビュー をした。そのときに scim の urn のパース処理を自前で実装していた。それ自体は悪くないが、urn のような標準化されたものなら専用ライブラリを使った方がよいのではないか?と思って調べてみた。予想通り作っている人はいたものの、それほど煩雑なものでもないのでライブラリを使うほどでもないのかもしれない。

例えば、次のような scim 向けの urn がある。

urn:ietf:params:scim:schemas:core:2.0:User

go-urn は汎用の urn パーサーなので scim に特化した属性などをパースしてくれない。それについて issue で質問してみたら、scim 向けのサブパーサー作ったらいいんじゃない?というコメントが返ってきた。そこで rfc-7643 の urn の仕様を眺めながら試しに実装してみた。この機能がマージされてもこの用途のためだけに依存ライブラリを増やすかどうかはまだ懐疑的なところ。とはいえ、せっかく調査して実装したから誰かの役に立つかもしれないと思って pr を送った。

go-ldap の syncrepl 対応

2時に寝て2回ぐらい起きて6時半に起きた。お休みとったからいろいろ余裕がないけれど、体力だけはある。

syncrepl 機能の実装

go-ldap の非同期検索 の続き。persistent search という用語 も理解して、満を持して openldap の syncrepl 対応に臨んでいた。syncrepl を用いた persistent search というのは、簡単に言えばメッセージキューで言うところの pubsub のコンシューマーに相当する。その通信のテストやデバッグをしているときに非同期検索のバグもみつけた。余計なことをして返って複雑なバグを混入したなと反省しながら修正の pr を送った。

go-ldap の ldap 通信の処理や設計を理解するのに2日、rfc-4533 を読みながら Control に関するプロトコル実装に1日、テストやデバッグ、その他の調査や設計に2日ぐらい、次の pr を作るのに1週間 (平日5日) ほどは工数を割いた。

ローカル環境で単体レベルの動作は問題ないと思う。あとは私が知らない ldap プロトコルの振る舞いや単体レベルで検証できていない通信、実際の運用レベルの syncrepl の状態やエラーなどに耐えられるかどうかといったところだと思う。おそらく rfc を読みながらプロトコルの一部を私が実装したのは初めてかもしれない。もうサーバーサイドやバックエンドの領域で (スキルの多寡はあれども) 私が作れないものはそうないだろうという自信をもっている。実際に rfc で提案されたプロトコルを実装して、テストして、ちゃんと動いて、そういう日々がすごく楽しくて嬉しかった。根拠のなかった自信を確認できた。またレビューに時間かかるかな?マージのためのやり取りや修正に2週間から1ヶ月ぐらいを見込む。

メモリリークに遭遇

23時に寝て何度か起きて5時に起きてからだらだらネットしながら記事を読んだりしていて7時に起き上がった。

agent アプリケーションのメモリリーク調査

qa テストの一環として先月からテスト環境で毎分 agent アプリケーションにリクエストを投げる長時間稼働テストを実行している。なんとなく気になるところがあったからやったわけではあるけれど、長時間稼働テストによってメモリリークを検出できてしまった。自分を過信せずちゃんと検証しないといけないなと思えた。top コマンドの実メモリー (RES) を1ヶ月前と比較して増えているからメモリリークだと気付いたところ。これからメモリプロファイリングをしながら原因を追求していく。私が書いた (レビューした) go のコードでメモリリークはないだろうと高をくくっていただけにちょっとショックではあった。

go は標準ライブラリに pprof というプロファイラがあるので簡単にデバッグできる。プロファイラで昨日から調査していたところ、go-zeromq/zmq4 の処理でメモリリークしていることはわかった。それがライブラリの使い方が誤っているのか、潜在的な不具合なのかはまだこれから調査するところ。

ライブラリ側の問題を調査するので厄介ではあるけど、私が書いた (レビューした) go のコードでメモリリークしているわけじゃないことがわかって少しほっとした。

go の generics 勉強会

先日準備した資料 を使って勉強会を開催した。

この勉強会はある意味、うちのチームのメンバーが理解しておくべき内容なので go のプログラミングをやっていないメンバーが聞いてもあまり関心をもてない内容となっている。そういうお断りもした上で最悪2-3人ぐらいの参加者になるかと思ったもののプログラミングに関心がある人たちは参加してくれて5-6人ぐらいの規模にはなった。一方で内容も難しいし、私の説明がどれだけわかりやすかったか、私自身にはわからないのでなんとも言えない。質問も一切なかったので喋りきって疲れたという疲労感と、伝わったのか伝わらなかったのか分からない消化不良感と、金曜日だから今日はもういいや感でどっと疲れたというのが率直な感想になる。

とはいえ、私もずっと generics の仕様をちゃんと追いかけたいと思いながら先送りしていたものではあるので私の中では自分が go の generics の理解度をあげて実際の開発の中で使い分けるだけの判断基準をもてたことが収穫だったと言える。

縁の下のマネージャー

20時にホテルに戻ってきてのんびりしながら気付いたら22時ぐらいになって、少しテレビをみて0時に寝て4時ぐらいから起きてその後はあまり眠れなかった。それでも7時過ぎまでだらだらしていた。

7月後半に実装予定の新機能の設計

9月までに実装する新機能のうち、唯一、私の頭の中で設計の見通しをもっていなかった機能の設計を行うことにした。

ざっくりした機能概要から私がふわっと想定していたものはずっと複雑なものだったのだけど、プロダクトオーナーに要件をヒアリングしているうちにそんな高度なものは求められていないことに気付いた。逆にその高度な機能の仕組みを提供しても、実際に運用の現場で使うにあたって手間暇だけかかってそんなものを求めていないと言われそうな気がした。そこで私が作りたいなと思っていた設計のアイディアは封印することにした。既存の先行プロダクトがもっている機能とほぼ同様のものを、うちらの開発しているプロダクトで実現するだけでよさそうにみえた。そのシンプルな機能の設計を軽くやっておいた。詳細を詰めるのは次のマイルストーンで私ではないメンバーに実装してもらうことになるけれど、なんとなく当初の想定よりも早くできそうに思えた。

ログ出力のリファクタリング

id 連携の処理で複雑なリソースを map 型で扱うときデバッグ用途でリソースを丸ごと dump したい。しかし、パスワードのような機密情報が含まれる場合はそれらはログに出力したくない。この処理をいまは連携種別ごとに実装していて、本質じゃないところで個別実装の手間があるのと機密情報の出力というセキュリティに関するところを毎回プログラマーが手で実装するのもどうかな?という気がして汎用のログユーティリティとしてロガーのライブラリ側で提供することにした。インフラやプラットフォーム的な機能に私は積極的に開発に介入している。

やり方の1つとしてオリジナルのリソースをコピーして機密情報だけ削除した一時的なリソースコピーを dump してログ出力する。go 1.21 で標準ライブラリに追加される maps パッケージを使うと map の操作が簡単にできる。コピー関数もある。しかし、この機能は shallow copy なので map の値にネストした map が含まれる場合はオリジナルの値を書き換えてしまう。ネストした map を調べてそれらもクローンしていく処理を実装した。excludeKeys に除外したい任意のキーを渡し、map の値を再帰的にチェックして取り除く。最終的には次のようなコピーユーティリティになった。

func copyWithoutExcludeKeys(
	fields map[string]any, excludeKeys []string,
) map[string]any {
	cloned := maps.Clone(fields)
	for k, v := range cloned {
		switch t := v.(type) {
		case map[string]string:
			strMapCloned := maps.Clone(t)
			for _, sk := range maps.Keys(strMapCloned) {
				if slices.Contains(excludeKeys, sk) {
					delete(strMapCloned, sk)
				}
			}
			cloned[k] = strMapCloned
		case map[string]any:
			cloned[k] = copyWithoutExcludeKeys(t, excludeKeys)
		case []map[string]any:
			for i, v := range t {
				t[i] = copyWithoutExcludeKeys(v, excludeKeys)
			}
		default:
			if slices.Contains(excludeKeys, k) {
				delete(cloned, k)
			}
		}
	}
	return cloned
}