私が歌川です

@utgwkk が書いている

MySQLの生成カラムをMODIFY (CHANGE COLUMN) できるかどうかはあんまり自明じゃない

この記事のタイトルにあることの理由は MySQL :: MySQL 8.0 リファレンスマニュアル :: 13.1.9.2 ALTER TABLE および生成されるカラム を読んだら分かります。


生成カラムの式を変えてsqldefでマイグレーションを反映したら、DROP COLUMNしてADD COLUMNするDDLが実行されたことで、表題の件に気づいた。

ものすごく単純化すると、たとえば以下のようなDDLが実行されることがある。

ALTER TABLE `users` DROP COLUMN `id_mod`;
ALTER TABLE `users` ADD COLUMN `id_mod` int GENERATED ALWAYS AS (id % 5) VIRTUAL AFTER `id`;

参照しているカラムをDROPされると、稼動中のアプリケーションでエラーが発生してしまう。これは都合がよくない。以下のように生成カラムに対してもCHANGE COLUMNで安全にカラム定義を変更できるはず。

ALTER TABLE `users` CHANGE COLUMN `id_mod` `id_mod` int GENERATED ALWAYS AS (id % 5) VIRTUAL;

ということで、以下のissueを起票した。

github.com

ところで (個別のDBMSの実装にもよると思うけど) sqldefが何故このようなDDLを出力するのか。この記事を書いた時点ではちゃんと実装は追っていないけど、MySQLのドキュメントを読みにいったらだいたい分かった。

dev.mysql.com

生成カラムの定義は常にin-placeに変更 (MODIFYやCHANGE COLUMN) できるわけではなく、条件によっては一度DROP COLUMNを経由する必要がある。今回適用したいマイグレーションはこの条件に当てはまらない。DROP COLUMNしてADD COLUMNするのは (一部のケースを除いて) 常に可能である。

「CHANGE COLUMNできるスキーマ変更かどうか」がうまく (sqldefの枠組みの中で) 判定できる方法が分かったらPRを出したいですね。

3年ぶり! 川見てるアドベントカレンダー2024やります

川見てるアドベントカレンダー2024をやります。

adventar.org

前回開催は2021年、実に3年ぶりの川見てるアドベントカレンダーということになります。

blog.utgw.net

川をはじめとした、海、湖、滝、流れなどについて語ったり、写真を貼ったりしませんか。前に取り上げたことがある川などであっても構いません。どうぞよろしくお願いします。

Kaigi on Rails 2024に参加した #kaigionrails

参加しました。

kaigionrails.org

普段の仕事では主にGo/TypeScript/Reactあたりを書いています。Rails周辺のぼんやりとした世界観は知ってるかも、ぐらいの立場です。

よかったトーク

speakerdeck.com

推しVTuberのために匿名質問サービスを高速に作り、仲間を集める、という流れが非常に美しく、懐しさすら感じさせる発表でした。こういう勢いをたぶん失っているんだろうな、とも……。

speakerdeck.com

どこのプロダクトでも長時間の非同期ジョブに悩まされているのだ、と知れて勇気づけられました。非同期ジョブの実装側でlong-runningなジョブを中断・再開できるようにする仕組みは面白かったです。一方で、ジョブをじゅうぶん小さな単位に分割して並列実行したのち結果をマージする、といった手法も考えられないか? と思い、そのあたりを懇親会で議論できたのでよかったです。

speakerdeck.com

スループットを上げるために1行1在庫で管理する、逆に決済システムのレートリミットを考慮してスループットをあえて下げるなど、正しくパフォーマンスと向き合っている、と感じました。1行1在庫だと行数が爆発しないか? と思ったけど、一時的なイベントなら不要になった行は消せるし、200万在庫をさばいた実績がある、とのこと。

speakerdeck.com

ActiveJobやSidekiq、そしてSolid Queueができるまでの歴史の話が面白く、TheSchwartzFireworqを使っているプロダクトが身近にあるのでなかなか遠くないところの話のようにも感じました。最近だとAWS SQSを使った仕組みを手作りするなどの事例があるのですが、「非同期処理」と一口に言ってもいろいろあるし、Sidekiqのwikiを一度読んでおくといいんだろうな、と思いました。

speakerdeck.com

データマイグレーションの決定解みたいなの、Rails界ならあるでしょって勝手に思っていたけど意外とまだないんですねえ。手元からスクリプトを打ってデータを修正した経験は自分もあるけどいまいちだし、かといって最強の仕組みを作るほど手をかけづらい場合もあって悩ましい。サーバーレスかつ安全にonetimeスクリプトを実行する最強の環境セットがあるといいのか??

speakerdeck.com

最終日の基調講演です。修復とは元に戻すことではなく変化に適応しつづけること、という言葉が目から鱗でした。これはフレームワークもそうだし、長寿プロダクトとかにも言えることになりそう。

Railsエンジニアじゃない立場から見た「Rails way」

冒頭で述べたように、普段はサーバーサイドの実装言語としてGoを使っています。昔はPerlも書いていました。

Railsに対して、やっぱり道具がひと通り揃っているのは強いだろう、ということをよく思います。重厚よりは簡素なフレームワークを選び、足りない道具があれば自分で実装する、というのを仕事でよくやっているのですが、一方でRailsの方を見て、こういうのは数行ぐらいで完成するんだろうな*1……と思うことは少なくありません。密結合で高速に走れる、という言葉を耳にしたことがあります。

一方で、Rails wayにうまく乗れないと気持ちのいい開発体験が損われがちで、そのあたりのノウハウが模索されている部分もあるんだろうな、とも感じました。モデル設計やHotwireなど、うまくハマるといいけどちゃんと考えないと苦しくなる面はありそうで、Railsだから思考停止でいい、ということはなさそう。「隣の芝は青い」みたいな感じで、どこにいてもちゃんと考えてプロダクトを作っていかないといけない、ということですね。

今の仕事にRails wayをそのまま取り込めるかは分からないけど、ある種のヒントは得られたかもしれません。とくに、最後の基調講演での話はRailsと関係なく普段から意識できるとよいのだろうな、と思いながら聞いていました。

おわりに

Railsは普段書いてない、という立場で参加しましたが、けっこう学びになるトークが多かったと思います。見れていないトークも興味深そうなものがいくつかあるので、資料を見返しておきます。一方、もっと深掘りできるとよさそうだけど15分トークだとギリギリすぎるか、ということも思ったので悩ましいですね。

来年も何もなければ参加します。会場が東京駅直結になるっぽくてすごい。

写真コーナー

*1:実際には数行では済まないかもしれないけど

gomock (mockgen) v0.5.0がリリースされた & package modeが導入された

はじめに

2024/10/18*1にgomock (mockgen) v0.5.0がリリースされました。

github.com

リリースノートを見ていこうと思うのですが、その前にpackage modeという新しいコード生成のモードについて触れましょう。

reflect modeの限界とpackage modeについて

mockgen v0.4.0までは、コード生成のモードとしてsource modeとreflect modeの2つがありました。

source modeは、指定されたGoのソースファイルを構文解析して得られた情報もとにinterfaceのモック生成を行うモードです。モック生成の対象がファイル単位でしか指定できない・別のpackageのinterfaceモックが生成できない など制約は強いけど、静的解析だけで済むので高速にコード生成できるモードです。このモードはmockgen v0.5.0にも残っています。

reflect modeは、リフレクションを使ってinterfaceのモック生成を行うモードです。リフレクションが使えるのでsource modeより柔軟性が高いように見えて、じつはいくつか限界がありました。

代表的なものに、ジェネリックな型を返すメソッドを含むモックのコード生成がエラーになる、というものがあります。reflect modeで以下のようなinterfaceのモックを生成するとエラーになります。

github.com

type Interface interface {
  GetSomeInt() *Some[int] // これが生成できない
}

type Some[T any] struct {
  // 省略
}

ほかにもいくつか限界がありました*2が、これらを解決するためにreflect modeとは仕組みの異なるコード生成モードを導入した、というのがpackage modeのモチベーションになります。

package modeは、golang.org/x/tools/go/packagesを使ってコードの解析を行ってinterfaceのモックを生成するモードになります。mockgen v0.5.0で導入されました。

github.com

package modeはreflect modeとの互換性を保ちつつ、reflect modeでは解決できてなかったコード生成の問題も解決しています。リフレクションではなく、golang.org/x/tools/go/packages というパッケージで提供されるGoのコード解析の仕組みを使っています。golang.org/x/tools/go/packages については以下の記事が詳しいです。

int128.hatenablog.com

リリースノート

package modeについて見たので、リリースノートのほうも見てみましょう。

Added

#153: Add --write_command_comment flag to specify whether to include Generated by this command comment.

github.com

mockgenでコード生成をすると、生成されたコードに以下のようなコメントが出力されます。

// Generated by this command:
//
// mockgen -mock_names=Service=UserServiceMock -package mocks -typed -destination mocks/user_service.go -self_package go.uber.org/mock/mockgen/internal/tests/mock_name/mocks go.uber.org/mock/mockgen/internal/tests/mock_name/user Service

この挙動はv0.4.0まではコマンドライン引数で制御できなかったのですが、v0.5.0ではこのコメントを出力しないように --write_command_comment=false コマンドライン引数で設定できるようになりました。

コード生成時に実行されるコマンドは環境依存で、生成されるコードに余計な差分が入ってしまうから無効にできるようにしよう、というのが元のPRのモチベーションです。ワイトもそう思います。

bulkmockgenを作った側としては、モックするinterfaceを追加したときにこのコメントがコンフリクトして困っていました。コード生成後にこのコメントを取り除くスクリプトを回すようにしてなんとかしていましたが、コマンドライン引数が導入されたことでスクリプトの用もなくなって嬉しいですね。

#191: Add --build_constraint flag to add //go:build directives to generated mocks

github.com

生成されたコードに対して --build_constraint コマンドライン引数でbuild constraintを指定できるようにした、という変更です。

mockgenで生成したコードは基本的にテストでしか使わないけど、パッケージが分かれているのでプロダクションコードからimportしてしまう可能性がある、なのでbuild constraintで保護できるようにした、というモチベーションのようです。

github.com

たまに思うけど、テストを走らせるときだけ有効になるbuild tagとかないのかな。

#214: Add gob mode to support custom package loading techniques in place of --exec_only

github.com

reflect modeがpackage modeに代替されたことで、interfaceの情報を得るためにコードをコンパイルして実行する必要がなくなりました。そのため --exec_only コマンドライン引数が削除されたのですが、これだと困る場合があるようです。mockgenによるコード生成結果をうまく制御するために、mockgenのinterfaceの解析結果の内部表現をgob encodingしたものを得て、コネコネしたのちmockgenに渡す、ということが行われていたようです。ここまでやったことがないのでまだモチベーションが掴みきれていません。

この問題を解決するために、--model_gob というコマンドライン引数経由で、内部表現をgob encodingしたもののファイルパスを渡せるようにした、ということのようです。

Changed

#181: Made mockgen faster by changing flags passed to go list.

github.com

mockgenでコード生成するときに go list -json コマンドが叩かれていますが、引数を調整することでコード生成を高速にした、という変更です。

go list -json だけだと依存パッケージの解析なども行われますが、必要なのは ImportPathName だけなので無駄だった、ということですね。

#183: Made Cond matcher generic.

github.com

Cond matcherがジェネリックになりました。godocの差分*3を見るのが一番分かりやすいでしょう。

// これまで
Cond(func(x any){return x.(int) == 1}).Matches(1) // returns true
Cond(func(x any){return x.(int) == 2}).Matches(1) // returns false

// これから
Cond(func(x int){return x == 1}).Matches(1) // returns true
Cond(func(x int){return x == 2}).Matches(1) // returns false

ジェネリックでない既存のコードは型引数が常に any 型に推論されたものだとみなせるので、互換性は保たれています。

#204: Removed ISGOMOCK() from generated mocks.

github.com

生成されるモックに ISGOMOCK() メソッドを生やすのではなく、isgomock というプライベートなフィールドを含めるようにする、という変更です。この差分だけ見てもモチベーションが一見分かりづらいと思いますが、#144 と併せて見ることで謎が解けます。

#207: Deprecated reflect mode and replaced it with the new package mode.

github.com

reflect modeに対応するモードをまったく別の仕組みで書き直して、package modeという名前で導入した、という変更です。

記事の冒頭で触れたように、これが今回のリリースで一番の目玉じゃないでしょうか。

Fixed

#144: Fix a deadlock that can happen when mocking an interface that matches fmt.Stringer.

github.com

fmt.Stringer interfaceに対するモックがdeadlockを引き起こしうる問題が解決されました。

fmt.StringerString() メソッドがモックされていると、モック実装を文字列化する → String() メソッドが呼ばれる → モック実装が適切に呼ばれているかどうかの判定が走る → …… という無限ループになる、ということですかね。さもありなん……。

解決策として、モック実装を文字列化するときは %v ではなく %T というフォーマットで文字列化する、という形を取っています。モック実装かどうかを判定するために ISGOMOCK() メソッドの存在を確認していましたが、これだと名前衝突が起こる可能性が残っています。ということで、メソッドではなくプライベートなフィールドを使って判定するように変更した、というのが #204 なんですね。

#168: Fix an issue where the "generated by" comment was being included in the package comment of generated mocks.

github.com

パッケージ単位のコメントとして // Code generated by MockGen. DO NOT EDIT. から始まる行を含めないようにする、という変更です。差分は非常にシンプルですね。

おわりに

より便利になり、package modeの導入によって限界も超えたgomock (mockgen) v0.5.0がリリースされました。みなさまもどんどん最新バージョンを使っていきましょう。

*1:リリースノート上の日付は2024/10/15

*2:interfaceの型エイリアスに対するモックを生成できない、メソッドの引数名の情報が失われる、など

*3:https://github.com/uber-go/mock/pull/183/files#diff-3e04df284e7d2b35b623a74eb8e9ef27d7637304e4643f7c56256ece54f6b63eL342-R348

YAPC::Hakodate 2024に参加した #yapcjapan

参加しました。この記事はサッポロクラシックを片手に執筆されました。

yapcjapan.org

トーク

fortee.jp

「推測するな、計測せよ」って仮説検証だよね? ということをずっと思っていたのですが、まさにそういう話がされていたと思います。一方でパフォーマンス改善の糸口を掴むきっかけをプロファイラのみから得るのは難しいのは確かに。プロファイラの使い方が分からないと、なんか情報が出た!! にしかならないんですよねー。

fortee.jp

タッキーさんは社内でも随一の引っ越し屋さんだと思うのですが、その経験がよく詰まったトークだったと思います。前回のデータ移行での反省を踏まえて次回は新しい手法を取り入れるということができているのも尊いです。

fortee.jp

「開発環境を整える」ということがチームの文化として浸透して、それが賞賛されるようになっている、ということが何よりも尊いですね。

fortee.jp

普段バリバリに使い倒しているクレカの裏側、身近だけどぜんぜん知らない仕組みを知れておもしろかったです。現実の泥臭い問題を粘り強く解く、ということを実践しているなーと見ていました。

キーノート

speakerdeck.com

moznionさんのキーノートがけっこう暴力といい話を行き来するみたいな感じでよかったです。最初からずっと暴力では??

「個人技」については自分も思うところがあったのだけど、そこがうまく言語化されていたと思います。いつまでもずっと手は動かしていたいし、ギークでありたいですね。強い個人技でチームに良い影響を及ぼせるようになったらそれはもはやチームワークなのかも。

おしゃべり

懇親会やN次会でいろいろ喋ったけど、最後のほうは酒が回りまくっていてあんまり思い出せない、けどきっといい話をしていたんじゃないか。なんか覚えている方がいたら教えてください。

スマートバンクさんは掘り下げるとどんどん面白い話が出てきてすごすぎるのでは??

公立はこだて未来大学

1つの空間がずっと連続していておしゃれ、だけど初見だとけっこう道に迷ってしまいました。

次回

次回は福岡で開催されるっぽい? のでこれもまた行きたいですね。ビアキチにも行きたい。あとそろそろ登壇できるようにネタをちゃんと用意しておきたい!!

全部見る

連休中に、golangci-lintに組み込まれているlinterを全部見た。Linters | golangci-lint から見れるので、みなさまも見てください。

世間でも全部見ている事例はありそう。こちらもあわせてどうぞ。

zenn.dev


これで記事を終わってもいいけど、もうちょっと続ける。

ここで「golangci-lintに組み込まれているlinterを全部見た」とは、以下のことを指している。

  • Linters | golangci-lint を開く
  • linterを上から順に見る
  • それぞれのlinterがどういうコードを検出するlinterなのか・どういう設定項目があるのかを把握する
  • 身近なコードベースに対してlinterを走らせる
  • (余裕があれば) linterのソースコードを眺める

数時間ぐらいで全部見おわり、(一部のルールが多いlinterを除けば) それぞれのlinterの特徴はだいたい分かった。あわせて Configuration | golangci-lint もひととおり目を通した。その上で、いま関わっているプロダクトのコードベースに何をどう適用すると効果的なのか検討し、いくつかのlinterを追加で有効にした。

「全部見る」は必ずしも「完全に理解している」を意味しないが、近いところには到達できると思う。何がどこにあるのか・何ができるかのアタリがつくようになった。裏を返すと早めに知っていればよかったと思うこともあるが、手札が増えたことを喜ぶべきだろう。linterのルールに限らず、ときどき「全部見る」行いをやるのが癖になっている。見えていると思っているものも、ほんの一部・上澄みしか見えていない、ということはよくある。

何が言いたいのかというと、全部見るのは健康にいいです。