以下の内容はhttps://www.okb-shelf.work/より取得しました。


UPDATEクエリの実行時間をどう予測するか

実行時間を知りたい

サービスの運用中には、テーブル構造を変更するマイグレーションがよく発生します。
頻出なのは新規カラムを追加するケースでしょうか。テーブルに新たなカラムを nullable で追加して、DDL の完了後に手動でUPDATEを実行して null を埋めるような対応です。 その際に、対象となるレコードが 100万件を超えるような場合、どれぐらいクエリの実行時間が必要なのかを事前に把握して、ユーザー影響が少ないように実行タイミングの判断をする必要があります。

考えられる選択肢

  • 何も気にせず実行する
  • アクセスの少ない時間帯(夜間)に実行する
  • バッチ分割して少しずつ実行する
  • メンテナンスモード中に実行する

コストを知ることはできる

クエリ実行のコストを分析するにはEXPLAINを使います。
しばしばSELECTのクエリに対してEXPLAINを実行してきましたが、実はUPDATEDELETEであってもEXPLAINは有効であり、実際にはデータの更新、削除はされないため、安心して実行をすることができます。そのため、大量のレコードに対して更新クエリを実行したい場合、事前に副作用なく安心してコストを知ることが可能です。

EXPLAIN [ ( option [, ...] ) ] statement
EXPLAIN [ ANALYZE ] [ VERBOSE ] statement

EXPLAIN

しかし、実行計画で知れるのはコストであって、実際の実行時間の情報ではありません。 EXPLAIN ANALYZE UPDATE...とすれば、実際の実行時間を知ることができますが、実際にデータが更新されてしまうため採用できません。

仮説: 実行時間は対象レコード数に比例する

どうすれば更新クエリの実行時間を知ることができるでしょうか。
実際に実行してみる以外で、よく見かけるアプローチは、事前に任意件を更新対象としたクエリを実行・計測をして、件数を増やした場合の実行時間を予測するというものです。 何となく良さそうに聞こえますが、この予測は「実行時間が対象レコード数に比例する」という前提の元で成り立っています。本当にそうなのでしょうか。

データベースにもよりますが、更新といっても実際にはWALへの書き込み、VACUUMの実行などの処理が実行されており、これらを無視して比例を前提とするのは、やや楽観的だと感じました。

計測する

ということで、実際に計測してみたいと思います。
更新対象となるデータ件数を 10倍ずつ増やしながら、実際の実行時間を信頼性を高めるため、EXPLAIN ANALYZEを用いて5回ずつ計測します。 完全にクリーンなテーブルに対して実行するケースと、既存のテーブルに対して実行するケースでは結果に差異がありそうなので、合わせて検証してみます。

  • 毎回テーブル再作成: 計測ごとにTRUNCATEを実行して計測する
  • VACUUM FULL: データはそのままでVACUUM FULLを実行後、計測する
  • クリーンアップなし: 何もせずデータを増やして計測する

シナリオ

  • RLS(RowLevelSecurity) を導入するため、全てのテーブルに組織IDを定義したい
  • ユーザーが投稿した記事を管理するテーブルに組織IDがないため、DDL で organization_id: nullable を追加した
  • 手動でorganization_idを埋めるクエリの実行をする
UPDATE user_posts
SET organization_id = users.organization_id
FROM users
WHERE
  user_posts.user_id = users.id AND user_posts.organization_id IS NULL
;

詳細なスペック

  • マシン: MacBook Air (Apple M1, 8コア, 16GB RAM)
    • Docker Desktop for Mac
    • PostgreSQL 16 (公式Dockerイメージ)
    • ストレージ: tmpfs (RAM上, 4GB)
      • ディスクI/Oノイズ排除のため
    • 共有メモリ: 512MB (shm_size)
  • テーブル構成
    • organizations: 10件 (固定)
    • users: 1,000件 (固定, 1組織あたり約100人)
    • user_posts: 100 / 1,000 / 10,000 / 100,000 / 1,000,000 件 (可変)
      • organization_id に関するインデックスはなし

結果と考察

図1より、実行時間とデータ件数には、ほぼ比例(原点を通っていないですが、0件で計測すれば0となる)の関係があることが分かります。 実行ケースによる違いもほとんどなかったのは思わぬ結果でした。一方で、図2からはクリーンアップなしのケースでは実行時間に悪化があり、何らかの影響を受けていると考えられますが、データ件数が要因だと結論を出すことはできません。1件あたりの実行時間はデータ件数との明確な関係性はなさそうです。

あまり考察しがいのない結果となりました...
今回の計測結果からは「実行時間は対象レコード数にほぼ比例する」と判断できそうです。

注意点

今回の計測はtmpfs(RAM)で行っており、HDD / SSD では1件あたりの実行コストは大きくなります。件数に対する増加パターン(線形性)はリソース境界を超えない限り同様と考えられますが、同じような結果になる保証はありません。

あくまで理想状態での計測

今回は、データベースに対して他のアクセスがない、言ってみれば理想状態での計測を行いました。 実際の本番環境では、複数プロセスからテーブルへの読み込み・書き込みやロックを扱っていたり...と複雑な状況があります。より信頼性のある計測をするには、同じような複雑な状態を再現したい気持ちはありますが、手元で簡単に再現できるものではありません。 結局、稼働状況によっては比例の関係を覆す変数が容易に存在するし、その影響はデカいという事実からは逃げられません。

なので、参考値程度に比例を前提に予測をするのが、現実的な受け止め方でしょうか。

実行時間は予測できないもの

そもそも、計測できないものを予測しようという考えが良くないかもしれません。
実行時間は予測できないものとして制約を受け入れてアプローチを検討した方が良いのではないでしょうか。 たとえば、1つのクエリとして実行するのではなく、データ整合性に注意しつつ、バッチ単位で小さく更新を行うといった方針は検討できます。
nullable のカラムを足すようなテーブル変更の段階で求められる整合性は、外部キー制約ぐらいな気がするので、前提を切り替えてこのアプローチを第一に考えた方が良さそうです。

2月の振り返り

少し暖かさを感じるようになりましたね。
最近は目的地に向かう時は徒歩で。帰りはシェアサイクルを使って自転車で帰ってくるという遊びをしています。だいたい 5km 程度であれば、徒歩移動の対象としています。何よりも交通費が節約できるのと、歩くのが気持ち良いです。

もう桜が咲いていた。何という品種なのか分からない

2月度の成果

品質

- 専門的なテスト知識の獲得
- アプリケーション品質の向上
  - E2E テストの棚卸し(Flaky テストの撲滅とテストピラミッドのバランシング)
  - リグレッションテストの再設計(初期のものから更新ができていない)
  - テスト戦略の定義・チームでの合意
  - テストガバレッジの確立(品質の数値化を検討)

今月は手を動かすことを重視しました。
主にテストピラミッドのバランシングを行い E2E テストとして実装されているテストを見直し 5% ほどを単体テストへ移動させました。大したことをしたというわけではなく、E2E テストを地道に確認して、単体テスト化できそうなものをひたすら潰したという感じです。テスト可能か・何をアサートしたいのかという視点で無駄を省きましたが、地味な作業でコストがかかります。

結果的に Flaky テストが解消された(テスト数が減った)のと、E2E テストの月間クレジットの節約にも貢献することができました。また、より高速に実行可能なテストとなったことで、検証容易性が向上したという嬉しいフィードバックも頂きました。

反省点

体制などが変わったこともあり、自分がテスト方針を定める必要性は弱くなりつつあります。 テスト戦略の定義をする機会はなくなりそうですが、テストガバレッジの確立(品質の数値化)に関しては、ほとんど手を動かせませんでした。関心あるメンバーと少し話をしましたが、ファーストアクションができていない。

少し話は変わりますが 「SREをはじめよう」という書籍を読了しました。
長くなるので詳細は別途、書こうかなと思っていますが、SRE 視点でどうすればシステム品質を担保する(信頼性)シグナルを拾えるのかを考え始めています。自分が考えていた品質というのはテストやカバレッジに関するものでしたが、もっと視野は広くユーザー目線で健全な状態なのか、信頼できる状態を観測することに価値があるかも?と感じており、アプローチを改める必要性がありそうです。

プロダクトと向き合う

- 複雑なドメイン知識と向き合う
  - 医療制度(特に負担額計算領域)の理解
  - 知識をコードに落とし込む経験をより積む(eg: いわゆるDDDの実践的トライ)
- 歴史あるコードとの向き合い知識の獲得
  - レガシーコード改善ガイドを読む(購入済み)
  - 単体テストの考え方/使い方

AI をゴリゴリ使った開発をしている事もあり、精算業務からレセプトまで広いテーマに関わる機会を得られました。先月は樹形図が読めるようになり、ようやくコード上でどう表現されているのか、相関が少しずつ見えるようになってきたのが嬉しいです。何件か関連する不具合の調査・修正もやりました。

レガシコード改善ガイドについては読了して、書評を記事にしました。

www.okb-shelf.work

反省点

不具合の原因は分かれど、ドメイン知識や設計に至ったコンテキスト情報などが圧倒的に不足しているので、どのように修正するかの判断が難しいと感じています。AI によってアウトプット速度は上がっていますが、最終的に自分が責任を持って OK の判断ができるかは重要です。 知識も増えて、手を動かす速度は確実に上がってきているので、引き続き向き合っていきたい。

総評

成果を意識して手を動かせたのは良かったです。 E2E テストの削減のような分かりやすいものから、ドメイン知識を深めて沼へ足を踏み込めている感覚があるのは継続していきたい。ここには書けない試作・改善なんかもやっているので、満足度が高めの月ではありました。とはいえレセコン開発(負担額計算)がシンプルに難しい...

レガシーコード改善ガイドを読んだ

最近、書評をあまり書いていなかったので久しぶりに書いてみようと思います。
というのも LLM が発達した今日、個人が書評を書くことにあまり提供できる価値を感じていなかったのですが、将来の自分のために知識をまとめておきたいと思います。

あいかわらず紙で読んでいます

書籍の概要

レガシーコードの正体

本書のタイトルにも記載されている「レガシーコード」は「誰かから引き継いだテストがないコード」と定義されています。かなりニュアンスが省略されており、保守などで辛い思いをしていないと意味を読み取れないと感じました。

少し捕捉すると、テストがないために期待する振る舞いが分からない。結果的に手がつけられず、長い時間放置されしまいレガシーとなってしまったコードというニュアンスが含まれているのかなと考えます。

一貫したテーマ

400P ほどある分厚い書籍ではありますが、コアにあるのは「どのようにして安全にコードを変更するか」というテーマです。そのために、問題を特定可能な十分に小さく繰り返し実行できるコストの低いテスト(単体テスト)が必要不可欠であり、テストができるように依存を排除する・構造を意識した設計・変更を行うためのプラクティス集です。

拙作のサンプル

以下の税込み価格を算出するサービスクラスはDatabaseConnectionに依存しており、このクラスの単体テストを書くには実際のデータベースとテーブル、適当なデータを用意する必要があります。

class DatabaseConnection {
  fun init(hostname: String, port: Int): DatabaseConnection.Ready {
    // 接続をトライして実際にDBにアクセスできることを確認する
    // このコードは適当に書いています。実際のライブラリを使用するものではありません
    return DBM.initFromResult(
      postgresql.manager.init(hostname, port)
    )
  }
}

class ProductCalculator(
  private val db: DatabaseConnection.Ready
) {
  fun calculate(products: List<Product>): UInt {
    // 商品価格を取得する
    val today = LocalDateTime.now()
    val productPrices = db
      .exec("SELECT * FROM product_prices WHERE $today >= create_time AND ...")
      .map(::buildProducts)
      .associate { it.id to it }

    val 税抜の合計金額 = products.fold(0: UInt) { acc, product ->
      val price = productPrices.get(product.id) ?: error("該当する商品が見つかりませんでした。")
      acc + price 
    }

    val taxRate = db
      .exec("SELECT * FROM tax_rates WHERE ...")?.let { buildTaxRate(it) } ?: error("税額が設定されていません。")
    val 税込の合計金額 = 税抜の合計金額 * (taxRate + 1.00)

    return 税込の合計金額
  }
}

このクラスのテスタ容易性を向上させるにはDatabaseConnectionの依存を排除して、内部で実際に SQL を実行する処理を取り除くのが良いでしょう。interfaceや Repository パターンを活用すれば簡単に完了することが想像できます。

銀の弾丸はない

Claude に本書を推薦され、レガシーコードを改善するための超絶☆最強テクニックがあるのではないかなと期待して読みましたが、そんなものは当然ありませんでした。銀の弾丸はないとは散々、言われ尽くしたことですが、レガシーコード改善についても同様です。

改善のヒントとなるプラクティスはあれど、システムの状況やアーキテクチャはどれも異なり、何がどう使えるかはコンテキスト次第です。実際のプラクティスについては数が多いので省略しますが、多くはコードから依存を排除するためのものでした。

感想

出版されたのは 2009 年なので、もう数十年前ということになります。
interfaceを使って依存を排除したり、スーパークラスを活用する方法などが、数十年前には提案されていたと考えると驚嘆させられます...。しかし、現代ではすでに基本的なテクニックとして昇華されていると感じるものが多く、あえて今、この書籍を読む強い理由はないかなと私は感じました。

なぜ「SOLIDの原則」や GoF(デザインパターン)で「継承より委譲」などが推奨されるのかの答え合わせができたのは良かったです。

1月の振り返りとAI環境整備

早いもので1月があっという間に終わってしまいました。
2026年の目標 - やわらかテック を計画していたので、振り返りつつ、開発環境の話題にも触れておきたいと思います。

1月の成果

品質

- 専門的なテスト知識の獲得
  - JSTQB シラバスの学習
  - JSTQB Foundation Level 資格の取得
  - ソフトウェアテスト技法練習帳に取り組む(過去に少し触ってる)
- アプリケーション品質の向上
  - E2E テストの棚卸し(Flaky テストの撲滅とテストピラミッドのバランシング)
  - リグレッションテストの再設計(初期のものから更新ができていない)
  - テスト戦略の定義・チームでの合意
  - テストガバレッジの確立(品質の数値化を検討)

JSTQB のシラバスを中心に、テストに関しての学習を進めました。
無事に JSTQB Foundation Level に合格できました。テス友をメインで使って学習をしていましたが、実際の試験問題とはかなり異なっているように感じました。3問ほど解いたところで「やべぇ、落ちた...」と思いましたが、合格通知があり安心しました。
(受験料が 22,000 なので洒落にならない...)

反省点

知識はついたと感じる一方で具体的な成果・アウトプットが弱かったのは反省点です。
テストレイヤーのバランシングやガバレッジの確立など、やることは明確ですが、時間をあまりかけられなかった。品質関連の MTG にいくらか参加したりするようになったのは、種まきの時間だったとも言えるかなと思います。

プロダクトと向き合う

- 複雑なドメイン知識と向き合う
  - 医療制度(特に負担額計算領域)の理解
  - 知識をコードに落とし込む経験をより積む(eg: いわゆるDDDの実践的トライ)
- 歴史あるコードとの向き合い知識の獲得
  - 前職でゴリゴリとコードを書いたが、歴史あるコードを安全に変更する術に弱い
  - レガシーコード改善ガイドを読む(購入済み)
  - 単体テストの考え方/使い方

レセプト(UKE)・負担額計算に関するドメイン知識はかなり実践的レベルまで向上したと感じています。 樹形図と呼ばれる、負担額計算にて登場する計算過程を図化したものを読めるようになりましたし、レセプト・UKE に関する要件も厚生労働省が公開している資料から読み取れるようになり、関連するバグ修正を何件か完了させられました。

樹形図

反省点

見せられないですが、負担額計算に関するコードを FigJam で図化したりしました。
この図化する取り組みはとても学びがありました。全体像が見えるようになったのと、登場するエンティティの網羅、どんな責務を持ってほしいかが見えるようになったので、コードレビューのクオリティが向上したと感じています。

一方で、実際にコードがどのように動くのか・どんな値が来るのかの理解が弱いです。
デバッガを使って把握する取り組みを思案したものの、滞ってしまっています。実際に OJT を通して理解を深めたい気持ちもありますが、そんなに都合よく OJT があるわけではないので、積極的に自由研究をしないといけない。

総評

自身の財産となるような知識の獲得ができたのは良かったです。 時間をつくれていないという共通課題はあるのですが、なぜ時間をつくれていないのか?については、分析が足りていない感が否めない。 ただ、種まきをして下地を作れたと思うので、成果を生み出す観点をより重視して2月は動いていく計画です。以上、1月の振り返りでした。

AI環境整備

背景

話題は変わりますが、一記事にするほどの内容でもないので、ここに書きます。
LLM の発展によって、プログラミングが楽しくなくなった・別に変わらない話があるかと思いますが、自分は前者の感覚が近いです。どこかプログラミングをアートの制作過程のように捉えている節があり、それを楽しんでいました。 しかし、結局のところ、利用者にとっては期待通り動くソフトフェアであれば良いだけで、この事実は今までもこれからも変わらないので、良いものを作るという理想に対しての how が変わっただけかなと、やや悲観的かつ現実的に捉えています。

重い腰を上げて

以上の背景から AI を利用した開発については遅れをとっている自覚がありました。
流石にやばいな...と感じたので 12月に Claude Code の pro プランを契約して使い始めましたが、明らかなゲームチェンジャーであることを痛感して、2月に max 5k プランに乗り換えました。

また巷で有名になっている everything-claude-code を参考にskilsrulesなどを整備しています。プロジェクトを問わずポータブルなものを dotfiles として管理するようにしました。

github.com

また精度検証の回数が足りていないですが、少しずつ投資していきます。

2026年の目標

新年、あけましておめでとうございます🎍
毎年恒例では全くないのですが、2026 年の目標について記しておこうと思います。 というのも、歳を取るにつれて何となく目の前の仕事や好きなことに取り組んでいるだけで良いんだっけ?という思いが強くなってきました。
また、少し前にエンジニアリングリーダーを読んだのですが、人生の全体のキャリアを考える必要も強く感じていますし、新しくチームの一員に加わった id:msksgm からの刺激もあり、はじめて自主的に目標を設定しました。

経験が少なくゼロから目標を設定するのは難易度が高いため Claude にいい感じにインタビューをしてもらうことで目標を設定しました。 Claude Code を使うために有料プランを使っているので、Sonnet 4.5 がガンガン使えてありがたい。

前提

職業のキャリアを中心に5〜10年後を見据えた計画をしています。
いつになるか分かりませんが、何となく将来的には岐阜に帰って IT の仕事をしたい(リモートワークではなく小さな自分の事業としてやりたい)という気持ちがあります。今はそのための準備をしている期間と捉えており、経験と実績を積むことを重視しています。この投稿では簡単のために期間の概念を吹き飛ばしていますが、別途、Obisidian で管理しているファイルにはマイルストーンの設定なんかもしています。えらい。

業務的なやつ

自分の関心とビジネスが win-win になるように意識してます。

  • アプリケーション品質の向上
    • E2E テストの棚卸し(Flaky テストの撲滅とテストピラミッドのバランシング)
    • リグレッションテストの再設計(初期のものから更新ができていない)
    • テスト戦略の定義・チームでの合意
    • テストガバレッジの確立(品質の数値化を検討)
      • かっこいい言葉を使えば品質のオブザーバービリティ
  • 専門的なテスト知識の獲得
    • JSTQB シラバスの学習
    • JSTQB Foundation Level 資格の取得
    • ソフトウェアテスト技法練習帳に取り組む(過去に少し触ってる)
  • 複雑なドメイン知識と向き合う
    • 医療制度(特に負担額計算領域)の理解
    • 知識をコードに落とし込む経験をより積む(eg: いわゆるDDDの実践的トライ)
  • 歴史あるコードとの向き合い知識の獲得
    • 前職でゴリゴリとコードを書いたが、歴史あるコードを安全に変更する術に弱い
    • レガシーコード改善ガイドを読む(購入済み)
    • 単体テストの考え方/使い方

掛け算の取り組み

  • 高品質なアプリケーションを実現する
    • テスト知識・歴史あるコードとの向き合い方を会得し、改善に取り組む
    • プロダクトリスクを下げて、改善しやすい状態を目指す
  • 複雑なドメイン知識の設計・実装
    • 医療制度をコードに落とし込む設計と実装
    • トレードオフを体感して、経験へと昇華する

個人的なやつ

  • 技術
    • Nvim をもっと効率的に使えるようになる
    • dotfiles を引き続き改善する
      • lua で nvim のプラグインを作れるようにする
      • 自作のプラグインを使って、怠惰な作業を排除する
    • 何かしらのイベントにプロポーサルを出す
    • TypeScript・Kotlin を引き続き学ぶ
      • 個人的に関心のある Zig も触ろうかな...
    • 個人開発のアプリケーションリリース
      • いくらかストックしているアイディアがあるので、実現する
  • 趣味
    • My Soul, Your Beats! をピアノで弾けるようにする
      • ピアノ完全初心者ですが、バイエルを1ヶ月ぐらい練習中
    • 股関節の柔軟を継続して、180度の開脚ができるようにする
    • 引き続きジムに通いコンパウンド種目に取り組む

以上です。 今年もよろしくお願いします。




以上の内容はhttps://www.okb-shelf.work/より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14