Activity
Stars and mentions · Last 30 days, Oct 6 – Nov 4
Top repositories
flyerhzm’s repositories ranked by pull request authors, as on trending.
Contributes to
Repositories outside flyerhzm’s account where flyerhzm opened or reviewed pull requests in the past 365 days.
Who noticed flyerhzm
Everyone who starred or forked flyerhzm’s repositories since the archive began in 2011.
Notable accounts
Ordered by the stars their own repositories have received.
Biggest fans
Accounts that starred the most of these repositories.
Breakout weeks
Each repository’s best week of new stars, with the release published around it.
Achievements
Awards earned by the repositories flyerhzm owns. Select an award to embed its badge.
No awards yet
Repositories earn awards by reaching the top three in a completed trending day or week.
A repository earns an award for finishing first, second or third in a completed trending day or week, ranked by new stars, new forks or pull request authors. Awards follow a repository through renames and transfers and count toward its current owner. Weeks run Monday to Sunday, UTC.
Mentions
N+1の検出は機械に、修正はAIに任せる。Laravel DebugbarとRails Bulletで回す性能改善ループの分業設計 私が担当した業務システムの案件で、応答が徐々に悪くなり、MySQLのスロークエリログには毎日200件を超える遅いクエリの記録が残っていた。ワースト10を特定してインデックスとクエリを直したところ、そのクエリ群の平均応答が3秒から0.5秒になった(スロークエリログ上の平均実行時間。計測条件の詳細は記録に残っていません)。「どこが遅いか」はログが教えてくれる。問題はそのあとの「どう直すか」を誰に任せるかです。 私が今回書きたいのは直し方ではなく、「見つける」と「直す」を誰に割り当てるかです。検出は機械(Railsは Bullet、Laravel は Bullet のような自動検出が無いので Debugbar で見える化して人間が拾う)に任せ、その出力をそのままAIに渡して修正案を出させる。そして、AIに渡してはいけない修正も明確にある。LaravelとRailsの両方を運用してきた立場で、その分業の線引きを整理します。 > 先に断っておくこと: 本記事の「AIに渡して修正させるループ」は、設計と運用方針の提案です。掲載コードはすべて未実行(not-executed)で、公式ドキュメントの例をもとにしています。数値(200回→3回など)は、AIを使わずに手で直した過去案件の実績です。 前提 - 対象: Laravel(Eloquent)と Rails(ActiveRecord)の業務システム - ドキュメント確認日: 2026-10-31(Debugbar の config のみ 2026-11-03。Laravel 12.x 版ドキュメント、Rails Guides 最新版、Bullet / Laravel Debugbar の README) - AI側は Claude Code を想定。ただし以下のループは「検出ログを貼って修正案をもらう」だけなので、ツールには依存しません きっかけ:8秒の顧客一覧と、見逃しだらけの目視レビュー 最初に痛い目を見たのはLaravelの案件でした。顧客一覧画面の表示に8秒以上かかるとクレームが来て、調べるとEloquentのN+1で約200回のクエリが発行されていました。Laravel Debugbarでクエリ一覧を見て問題箇所を特定し、with() によるEager Loadingに書き換えたうえで、selectするカラムも必要最小限に絞りました。結果はクエリ200回→3回、表示8秒→0.4秒です。 この件の教訓は「Debugbarは開発中に常時ONにしておくべき」でした。Debugbar は、既定ではアプリの debug 設定が true のときに有効になります(確認日 2026-11-03 時点の fruitcake/laravel-debugbar の config/debugbar.php のコメントでは、debug モードかつ本番ではない場合にだけ有効にできると説明されています。版によって細部は異なり得ます)。また、公開サイトでは使わず開発時のみ使うよう README に明記されています(Laravel Debugbar README。リポジトリは移管されており、旧 barryvdh/ からもリダイレクトされます)。README が説明しているのは、全クエリをバインド値と実行時間つきで表示するQueryCollectorまでで、N+1を自動判定して、どの関連を with すべきかの提案まで出す機能は書かれていません(重複クエリの表示などはN+1に気づく手がかりになりますが、先読みすべき関連の提案は出しません)。つまりDebugbarは「見る道具」で、クエリ一覧から同型クエリの並びに気づいて直し方を決めるのは人間側の仕事です。 もう一つの案件はRailsで、遅い画面が複数あるのにN+1の発見が手動レビュー頼みで、見逃しが多い状態でした。そこでBullet gemを開発環境に入れ、ブラウザ通知とログ出力を有効にして、検出されたら includes / preload で即直す運用にしました。1ヶ月で23箇所のN+1を発見・修正し、ページ表示速度は平均で約40%改善しています。ただしこの40%は、当時の記録に測定条件(対象画面・計測回数)まで残っておらず、あくまで体感に近い目安です。 検出ツールの出力は、AIにそのまま渡せる形をしている ここで気づいたのは、Bulletの出力が「何をどう直せばいいか」まで書いてある点です。README の例では、ログは次の形式です。 2009-08-25 20:40:17[INFO] USE eager loading detected: Post => [:comments] Add to your query: .includes([:comments]) 2009-08-25 20:40:17[INFO] Call stack /Users/richard/Downloads/test/app/views/posts/index.html.erb:8:in `each' /Users/richard/Downloads/test/app/controllers/posts_controller.rb:7:in `index' (出典: Bullet README のログ出力例) Bulletが検出するのは、README によるとN+1クエリ、不要なEager Loading、カウンターキャッシュの不足の3種類です。この3種類はデフォルトですべて有効で、個別にオフにもできます。つまり、検出ログは 「場所(続く行に出るコールスタック)」「対象の関連(Post => [:comments])」「提案(.includes)」の3点セットが機械可読に近い形で出る。これはAIへの入力として都合がいい。 Bulletの設定は README の例をもとに、開発環境だけで有効にします。README は「開発モードか、staging / profile などのカスタムモードで使うのがベストプラクティス」としています。 # config/environments/development.rb # 出典: https://github.com/flyerhzm/bullet の Configuration 節をもとに最小化 config.after_initialize do Bullet.enable = true Bullet.bullet_logger = true # log/bullet.log に出力 Bullet.rails_logger = true # Rails のログにも出力 Bullet.add_footer = true # 画面左下に詳細を表示 Bullet.alert = true # ブラウザにアラートを表示(ブラウザ通知) Bullet.raise = false # true にすると検出時に例外(テストで落とす用途) end このコードは未実行です。Bullet.raise = true にするとspecで落とせる、というのも README の記述に基づくもので、私の環境で試した結果ではありません。 修正の分業:AIに渡すと速い/人間が決める ここが本題です。N+1の修正を何十箇所も続けていると、修正は2種類に割れることに気づきます。 | 修正の種類 | 例 | 担当 | 理由 | |---|---|---|---| | 関連の追加(比較的機械化しやすい候補) | Book::all() → Book::with('author')->get()、.includes(:comments) | AIが案を出し、人間が検証 | 検出ログに修正候補が書いてあり、差分が小さいことが多い。ただし関連定義・件数・生成SQL・画面の結果は人間が確認する | | 取得カラムの絞り込み | with('author:id,name,book_id') | 人間が決め、AIが適用 | 後述。キー漏れで壊れる | | 条件つきの先読み | with(['posts' => fn($q) => $q->where(...)]) | 人間が条件を決める | 業務ルールの話で、AIはその画面の意図を知らない | | キャッシュ導入 | 集計結果のキャッシュ、TTL設計 | 人間 | 何をキャッシュするかより「いつ無効化するか」の設計 | | クエリ条件の共通化 | scope化 | 人間がAIに方針を指示 | 命名がドメイン用語に対応する必要がある | AIに渡すと速い:eager loadの追加 Laravel公式ドキュメントのN+1の説明はこうです。25冊の本をループして著者名を出すと、本の取得1回+著者の取得25回で26クエリになる。with('author') を付けると、本の取得と著者の取得の2クエリになる(Eloquent: Relationships - Eager Loading)。 // 修正前: 本の取得1回 + 著者の取得が本の数だけ $books = Book::all(); foreach ($books as $book) { echo $book->author->name; } // 修正後: 2クエリ $books = Book::with('author')->get(); (上記はLaravel公式ドキュメントの例。私の環境では未実行) Railsでも構造は同じです。Rails Guides では、Book.limit(10) のあと各 book.author にアクセスすると計11クエリ、Book.includes(:author).limit(10) なら2クエリです。includes は preload か eager_load を状況に応じて選びます。関連テーブルの条件を参照する場合(references や関連テーブルを使う where など)は JOIN を使う eager_load 相当、それ以外は別クエリの preload になるのが基本です(Active Record Query Interface - Eager Loading Associations)。 この種の修正は、Bulletの提案行(Add to your query: .includes([:comments]))をそのままAIに渡し、該当コントローラを直させるだけで済みます。単純な関連追加なら差分が1行で、修正候補がログに書いてあるので、人間の確認は差分レビューと画面・クエリ数の再確認が中心になります。ただし、ログは候補を示すだけで、includes の追加が業務上正しいことまでは保証しません。条件・ページネーション・既存の select / joins・関連の件数によっては結果やメモリ使用量、SQL が変わります。差分が複数箇所に及ぶときや、不要な先読みの検出など誤検知かもしれないときは、人間の判断に戻します。 人間が決める1:select の絞り込み ここは「AIに任せたら壊れる」側です。Laravel公式はこう書いています。リレーション側のカラムを絞れるが、「id カラムと関連する外部キーのカラムを必ず含めること」(同上ドキュメント、Eager Loading Specific Columns 節)。 // 公式ドキュメントの例。id と外部キー(book_id)を含めている // 含めるべきキーはリレーションの種類(belongsTo / hasMany など)で変わるので、自分のスキーマに合わせて読み替える $books = Book::with('author:id,name,book_id')->get(); 先ほどの8秒→0.4秒の案件でも、with() への書き換えに加えてselectするカラムを必要最小限に絞りました。ここは「画面がどのカラムを使うか」を知らないと決められない作業です。公式が注意している通り、キーを落とすと関連が組み立てられなくなるので、AIに「不要なカラムを削って」と丸投げする設計は避けたい、というのが私の判断です(この点で実際に失敗した経験があるわけではなく、設計上の懸念です)。また、N+1自体は解消されるので、N+1の検出ツールは絞り込みの正しさを教えてくれません。だから絞り込みは、人間が画面の用途から必要カラムを決めて、AIには適用だけさせる方針にしています。 人間が決める2:キャッシュ 別の案件ではダッシュボードが毎回10個以上の集計クエリを実行して5秒以上かかっていました。変更頻度の低い集計をRedisにキャッシュし、TTLを15分にして、データ更新時には明示的にクリアする仕組みを足しました。結果は表示5秒→0.3秒、DBサーバーのCPU使用率も20%下がっています。 ここで効いたのは「何をキャッシュするか」ではなく「いつ無効化するか」の設計でした。更新経路を全部知っているのは人間側だけで、検出ツールの出力にもAIの視界にも入っていません。キャッシュは検出ログから導けない種類の修正なので、このループには載せません。 人間が決める3:クエリ条件の共通化 同じWHERE条件(有効フラグ、期間、ステータス)がリポジトリ層に10箇所以上コピペされていた案件では、Eloquentのスコープ(scopeActive() など)で1箇所に集約しました。Railsでも、コントローラに散らばった where チェーンを名前付きscopeにまとめ、クエリ関連のコード重複が70%減りました(当時の記録上の数値で、測定方法までは残っていません)。 N+1の修正でこの種の整理が必要になるのは、先読みの条件が画面ごとに微妙に違うときです。ここで重要なのはscopeの名前で、ドメイン用語に対応していないとあとで読めなくなります。これはAIに丸投げせず、命名方針を人間が先に決めてから渡しています。 回すループ 分業が決まれば、ループ自体は単純です。 1. 開発環境で画面を操作する(Rails: Bullet / Laravel: Debugbarでクエリ数を見る) 2. 検出ログ(Debugbarの場合は、クエリ一覧を見て同型クエリが並んでいる箇所を人間が拾う)を取り出す 3. 上の表で「AI向き」に当たるものだけをAIに渡す 4. 差分をレビューして、画面で再確認する 5. 「人間が決める」側は課題リストに積み、自分で設計する AIに渡すプロンプトの骨格は、たとえば次のようなものです(擬似コードではなく、文章のテンプレートです)。 以下はBulletの検出ログです。 <ログ貼り付け> 制約: - 修正は includes / preload の追加のみ。select の絞り込み・キャッシュ・クエリ条件の変更はしない - 対象はログのコールスタックに出ているファイルだけ - 修正後、該当リクエストのクエリ数が減る見込みを1行で説明する ポイントは「やってはいけないこと」を先に書くことです。指示の範囲が曖昧だと、AI が select を削ったりキャッシュを足したりする変更案を出すことがあるので(私が見た範囲の話で、発生率は測っていません)、範囲を縛らないと「人間が決める」領域に踏み込んできます。 再発防止:検出を「落ちる」側に寄せる 直したあとにまたN+1を入れてしまうのが、一番がっかりする瞬間です。Laravelには Model::preventLazyLoading() があり、公式ドキュメントでは AppServiceProvider の boot で次のように呼ぶ例が示されています。本番では通常動作を保つため、非本番環境でのみ有効にする書き方です。 // 出典: https://laravel.com/docs/12.x/eloquent-relationships#preventing-lazy-loading use Illuminate\Database\Eloquent\Model; public function boot(): void { Model::preventLazyLoading(! $this->app->isProduction()); } 有効にしたあとでLazy Loadingが起きると、Eloquentは Illuminate\Database\LazyLoadingViolationException を投げます。例外ではなくログだけにしたい場合は handleLazyLoadingViolationUsing でカスタマイズできると、同じ節に書かれています。 このコードは未実行です。また、私が確認したのは公式ドキュメントの記述までで、既存の画面にいきなり入れたときに何件落ちるかは試していません。既存プロジェクトに入れるなら、最初はログのみにして件数を見てから例外に切り替えるのが安全だと考えています(これは私の運用上の判断で、公式の推奨ではありません)。 検出ツールが保証しないこと(私の整理) 検出件数が多いと、全部まとめてAIに渡したくなります。ただ、絞り込みやキャッシュを含む修正は、N+1が消えたことと画面が正しいことが別物です。クエリ数は減っても表示が正しいかは、検出ツールの守備範囲外になります。この「検出ツールが緑でも保証されない」領域を人間側に残す、というのが上の分業表の考え方です。これは設計上の整理であり、AIに全部任せて壊した実体験を書いているわけではありません。 まとめ - 検出は機械(Bullet)に任せ、Debugbarはクエリを見える化する道具として使う(拾うのは人間)。私の案件では、手動レビューでN+1の見逃しが出た - 修正のうち、検出ログに修正候補が書いてある関連の追加だけをAIに渡し、人間が検証する - select の絞り込み・キャッシュ・クエリ条件の共通化は、人間が設計する。検出ツールは正しさを保証しない - 再発防止は preventLazyLoading などで「落ちる」側に寄せる 結局のところ、今回いちばん効いたのは「AIに任せる範囲を、検出ログに修正候補が書いてあるものに絞る」という割り切りでした。速くなったのは修正作業そのものより、判断が要る修正を最初から人間の机に載せておけたことだと感じています。 --- 正直、分業の線引きは「検出ログに答えが書いてあるか」に落ち着くまで、何度も考え直しました。同じところで迷っている人が遠回りせずに済めば、この記事を書いた意味があります。「うちはここまでAIに任せている」「この修正は渡して痛い目を見た」といった経験があれば、ぜひコメントで共有してください。 --- この記事について AIエージェントに会社の実務を任せる試みを続けています。この記事もその過程で出てきたものです。 同じ試みの記録を note で連載しています(うまくいかなかった話も含めて)。 📘 AI組織のつくりかた — note.com 続きが気になる方は Qiita のフォローをしていただけると、新しい記事が届きます。