Recotem v2.0 フルスタックモダナイゼーション

推薦システム構築ツールRecotemを v2.0 へ大幅にリファクタリングしました。バックエンド、フロントエンド、インフラ、CI/CD、ドキュメントまで全レイヤーを刷新し、今後もメンテナンスを続けられる状態にしています。315ファイルの変更、35,697行の追加という大規模な更新です。

リファクタリングの背景

Recotemはirspackをベースにした推薦システムのモデル構築・管理ツールです。しかし、依存ライブラリやフレームワークのバージョンが古くなり、Python 3.9、Django 3.2、Vue 2といった構成ではメンテナンスの継続が難しい状態でした。今後の開発や運用を見据えて、全面的なモダナイゼーションを実施しました。

バックエンドの刷新(Python 3.9→3.12、Django 3.2→5.1)

依存管理の近代化

requirements.txt から pyproject.toml + uv.lock(uv)に移行しました。uvによる高速な依存解決と再現性のある環境構築が可能になります。

主要ライブラリのアップグレード

ライブラリ旧バージョン新バージョン
Python3.93.12
Django3.25.1
djangorestframework3.123.15
celery5.15.4
numpy1.192.1
pandas1.32.2
scikit-learn0.241.6
SQLAlchemy1.42.0
optuna2.104.1
irspack0.1.160.4.0
psycopg2-binarypsycopg[binary] v3

アーキテクチャの改善

  • サービスレイヤーの導入: api/services/ に model、training、tuning、signing、project の各サービスを追加し、ビューからビジネスロジックを分離
  • WebSocketサポート: Django Channels + consumers.py でリアルタイムなジョブステータス通知を実現
  • ViewSetミックスイン: OwnedResourceMixinCreatedByResourceMixin によるオーナーシップベースのアクセス制御
  • APIバージョニング: /api/v1/ のURLプレフィックスを導入
  • 構造化ログ: JSON形式のログ出力とスロットルレート制御
  • マイグレーション整理: 複数のマイグレーションを 0002_schema_upgrade に集約

セキュリティ強化

  • HMAC-SHA256モデル署名: 学習済みモデルファイルに署名を付与し、改ざんを検知
  • 安全でないファイル形式のアップロード禁止: データアップロードから安全でないシリアライゼーション形式を除外し、任意コード実行のリスクを排除
  • ファイル名サニタイズ: Content-Dispositionヘッダーのファイル名をRFC 6266/5987に準拠してサニタイズ
  • resign_models管理コマンド: 既存の未署名モデルファイルにHMAC署名を追加するマイグレーションコマンド

フロントエンドの完全書き換え

フロントエンドはゼロからの書き直しです。

項目旧構成新構成
フレームワークVue 2 + Vuetify + Options APIVue 3.5 + PrimeVue 4 + Composition API
ビルドツールwebpackVite 6
スタイリングTailwind CSS 4
状態管理Pinia + TanStack Query
型システムTypeScript strict mode
APIクライアントopenapi-generator手動 ofetch クライアント
i18n英語・日本語ロケール

ダークモード対応や、ページベースのルーティング、コンポーザブルの活用など、Vue 3のベストプラクティスに沿った構成にしています。

インフラストラクチャの統合

Dockerの刷新

バックエンド用とCelery用で分かれていたDockerfileを、マルチステージビルドの単一Dockerfileに統合しました。非rootコンテナとしてポート8080でリッスンする構成に変更しています。

Docker Composeも5サービス構成(PostgreSQL、Redis、バックエンド、ワーカー、プロキシ)に再編成し、CI用のオーバーレイ(compose.ci.yaml)も用意しました。

nginxプロキシ

非rootのnginxをポート8000で統一的に配置し、SPA・API・WebSocket・Admin画面のリバースプロキシを一つの設定で管理します。WebSocketルートではJWTトークンがログに漏洩しないようサニタイズされたログフォーマットを使用しています。

Helmチャート

Kubernetes向けのHelmチャートにServiceAccount、PodDisruptionBudget、NetworkPolicy、HorizontalPodAutoscalerを追加し、本番運用に耐える構成にしました。

CI/CDの改善

  • GitHub Actions v4/v5へのアクション更新
  • CodeQLによるセキュリティスキャンの追加
  • Dependabotによる依存関係の自動更新
  • マルチアーキテクチャビルド + Trivyによる脆弱性スキャン
  • Node.js 16→20、Python 3.8→3.12のアップグレード

テストカバレッジ

バックエンド

17の新規/更新テストファイルをpytest + pytest-djangoで整備しました。

  • アクセス制御、WebSocketコンシューマー、シリアライザ、スロットル
  • モデルサービス、トレーニングサービス、署名検証
  • プロジェクト名のユニーク制約、ファイルミックスイン、ページネーション

フロントエンド

  • 30以上のユニットテスト(Vitest)
  • 10以上のE2Eテスト(Playwright)
  • コンポーネント、コンポーザブル、レイアウト、ページ、ルーター、ユーティリティのテスト

ドキュメントの整備

  • CLAUDE.mdCONTRIBUTING.mdSECURITY.mdCODE_OF_CONDUCT.md の追加
  • AWS、GCP、Kubernetes、Docker Composeのデプロイメントガイド
  • .env.example ファイルによる環境変数の説明

まとめ

Recotem v2.0では、バックエンドからフロントエンド、インフラ、CI/CDまで全面的にモダナイゼーションしました。Python 3.12 + Django 5.1、Vue 3.5 + Vite 6といった最新スタックへの移行に加えて、サービスレイヤーの導入やセキュリティ強化、テストカバレッジの充実により、今後のメンテナンスや機能追加がしやすい基盤を整えました。

  • PR #18: v2.0: Full-stack modernization

Fess 15.5.0のリリース

内容的な話はリリースノートを参照してもらえば良いかと思いますが、今回もいろいろな修正が入っています。

今回は実験的な機能として、AI検索モードが追加されています。AI検索モードは、AIエージェントが検索をしてくれるみたいな感じの機能です。なので、入力された質問文に対して、AIエージェントが検索クエリーを作成して、検索して、検索結果を評価した上で、検索結果を返してくれるみたいな感じです。なので、従来通りのクロールしてインデクシングできるので、ベクトル検索などを使う難しさなどはなく、利用することができます。実験的な機能なので、継続的に改善していければと思います。

他には、インデックスされたドキュメントをダンプする機能が欲しかったので、JSONやHTMLとしてファイルに出力する機能を追加しました。スケジューラーのジョブに登録してあるので、実行するとfess_config.propertiesで指定したパスに出力することができます。スクロールAPIとかでも全件取得はできますが、Fessのジョブとして、実行するだけでファイルに書き出せる感じです。

あとは、Fessをデモ的に公開していると、激しくアクセスしてくるクローラーとかがいて、Lightsailとかで運用していると負荷でクレジットを使い切ってしまい、サーバーがフリーズしてしまうことがあったので、検索画面へのアクセスにおいて、OpenSearchの負荷が高い場合は、混み合ってます的に429を返すみたいな処理を入れました。

という感じで、これら以外にもhttpclient5に移行したり、いろいろと修正は入っているので、試してみてください。今回もほぼほぼClaude Codeで開発して、自らコードをほとんど書いてない感じです。IDEも問題調査が必要なときにデバッグして、確認するくらいしか、Eclipseを開くことがなくなってきました。今も、この文を書いている裏で、実装してくれていますし。Fessの開発の仕方も変わってきてます。

次回のリリースもいろいろと変更が入ったりするかもしれませんが、何か要望があれば、フォーラムとかに投げてもらえれば、Claude Codeにやってもらって実現できるかもしれません。

libdxfrwのDWGパース時の整数アンダーフロー修正

libdxfrwは、DXF/DWGファイルの読み書きを行うC++ライブラリです。FessでDWGファイルのテキスト抽出に利用しているのですが、特定のDWGファイルをパースする際に、メモリを80GB以上消費して実質的に無限ループに陥るという問題が発生しました。

原因

問題の原因は、DWGファイルのクラス情報をパースする処理における符号なし整数のアンダーフローでした。

dwgreader18.cppにある元のコードは以下のようになっていました。

duint32 endDataPos = maxClassNum - 499;

DWGフォーマットでは、クラス番号0〜499は予約済みの組み込みクラスで、カスタムクラスは500番から始まります。そのため、maxClassNumが499以下の場合、カスタムクラスは存在しません。

しかし、duint32(符号なし32ビット整数)でmaxClassNum - 499を計算すると、maxClassNumが499以下の場合にアンダーフローが発生します。例えば、DWG TrueViewで作成されたAC1032形式のファイルではmaxClassNum = 236となることがあり、この場合:

  • 元のコード: 236 - 499 = 4294967033(符号なし整数のラップアラウンド)

この巨大な値がループ回数として使われるため、メモリの大量消費と実質的な無限ループが発生していました。

修正内容

修正はシンプルで、減算前に境界チェックを追加しました。

// Classes 0-499 are built-in; custom classes start at 500.
// If maxClassNum <= 499, there are no custom classes to parse.
duint32 endDataPos = (maxClassNum > 499) ? (maxClassNum - 499) : 0;

maxClassNumが499以下の場合はendDataPos = 0となり、カスタムクラスのパースをスキップします。この修正はdwgreader18.cppdwgreader21.cppの両方に適用しました。

修正後の計算結果:

  • maxClassNum = 236: endDataPos = 0(カスタムクラスなし)
  • maxClassNum = 499: endDataPos = 0(カスタムクラスなし)
  • maxClassNum = 500: endDataPos = 1(カスタムクラス1つ)
  • maxClassNum = 1000: endDataPos = 501(カスタムクラス501個)

テスト

今回の修正に合わせて、ユニットテストとインテグレーションテストも追加しました。境界値のテストや、メモリ安全性の確認を行っています。

符号なし整数のアンダーフローは、C/C++ではよくあるバグですが、今回のようにメモリ消費が爆発するケースは影響が大きいので、早めに対処できてよかったです。

関連リンク