# プロジェクト概要
登録ユーザー約4,000名の釣具部品修理システムにおいて、基幹システムから連携されるマスタCSVの取込処理をはじめとした保守・性能改善を担当しました。主な実績として、3時間以上経過しても完了しなかった製品構成マスタ取込バッチを、集合処理とバルクDMLを中心とした実装へ全面的に書き換えました。
# 期間
2025年7月〜2025年9月
# チーム情報
- 開発担当:1名(私)
- 同一リポジトリを担当する別メンバー:1名
別メンバーによる暫定的な性能対策の後、私は対象バッチの全面書き換え、ログ整備、動作確認、本番反映を担当しました。
# データ規模
- ログインユーザー:約4,100件
- 製品マスタ:約14万件
- 部品マスタ:約16万件
- 製品構成マスタ:約61万件
- 修理受付:約133万件
- 修理報告部品明細:約393万件
# 開発・実装内容1
## 製品構成マスタ取込バッチの全面書き換え
### 課題
既存バッチはCSVを2回読み込み、各行について製品ID・部品IDを取得するSELECTと、DELETE・INSERTを個別に発行していました。検索に利用する製品コード・部品コードや、削除条件となる製品IDには索引がなく、CSV1行ごとに大規模テーブルの全走査が発生していました。本番稼働中の中核マスタであり、DDL変更の承認が得られなかったため、索引を追加せずに改善する必要がありました。既存処理を実測したところ、3時間以上経過しても完了せず打ち切りました。
### 自身が行ったこと
スキーマを変更せず、SQLの発行方法を行単位から集合単位へ変更しました。
- CSVを1回だけ読み、製品コードと部品コードを集合化
- 800件単位のIN句でコードとIDの対応を一括取得
- 削除対象の製品IDを800件単位のDELETEへ集約
- 800行単位のマルチVALUES INSERTへ変更
- プレースホルダとバインド値を使用
- DELETEからINSERTまでを単一トランザクション化
- 例外時はロールバックし、原因をログへ出力
800件という単位は、1行8列で約6,400プレースホルダとなり、SQL文長とプレースホルダ数を抑えながらDBとの往復を削減できる値として設定しました。最大負荷日のデータ量を基準に、SQL発行数を4万本以上から約40本へ削減しました。
### 成果
本番ログ358回、約11ヶ月分を集計した結果、以下の実績を確認しました。
- 平均処理時間:1.10秒
- 中央値:1.0秒
- 最長:2.68秒
- 最大取込件数:14,675件
- 異常終了:0件
本番DBの索引を追加せず、3時間以上経過しても完了しなかった処理を、最大負荷時でも3秒以内で完了する状態へ改善しました。
# 開発・実装内容2
## バッチ結果とマスタ不整合の可視化
### 課題
既存処理は標準出力への表示のみで、夜間バッチが正常終了したか、何件取り込めなかったかを後から確認できませんでした。
また、基幹側の製品構成CSVに存在するコードが、商品マスタに存在しない場合も、詳細を追跡できませんでした。
### 自身が行ったこと
正常終了、異常終了、CSV未着の各経路で、以下をログへ出力するようにしました。
- 開始・終了時刻
- 経過秒
- 削除・挿入・スキップ件数
- 引き当てに失敗した製品コード・部品コード
- CSV未着、空ファイル、ファイルオープン失敗
- 例外の原因
本番反映前に手動実行で取込結果を確認し、正常動作を確認した翌日から夜間cronへ移行しました。
### 成果
約11ヶ月の運用で、53回・累計3,009行のマスタ不整合をログから特定できました。
CSV未着時もログを確認して翌朝に手動実行できるようになり、夜間バッチの成否とリカバリ状況を追跡可能な状態にしました。
# 開発・実装内容3
## 大規模データを扱う保守・性能改善
製品構成バッチ以外にも、次の対応を行いました。
- 約3万件の旧・新部品コード対照データを用い、約150万件規模の更新を3本のSQLとして生成
- 大規模テーブルを結合してサーバー停止の原因となっていた画面SQLから、不要な結合と取得列を削除
- 海外版カタログ向けに、製品構成を事前結合したテーブルからXMLを生成する連携処理を実装
性能問題が発生した際は、SQLの本数、取得列、結合、処理場所を整理し、PHPで実行する範囲とDB側で処理する範囲を判断して対応しました。