Zero Gravity バックエンド

感情記録とAI分析のためのSpring Boot REST API。Time Bucketサンプリングで送信量97%削減、クラウドインフラを自ら構築。

RoleBackend Developer
Timeline2025.10 - 2026.06メンテナンス中
Teamソロ
SkillsSpring Boot, Java, MySQL, Docker, Terraform
概要

一言で

感情記録とAI分析を提供するZero GravityのSpring Boot REST APIです。 フリーティアクラウド上にインフラからデプロイまで自ら構築し、コスト制約の中でAI機能を設計しました。

  • 5つのドメイン、14個のエンドポイント: 認証、感情記録、統計、AI分析を一つのAPIとして設計しました。
  • OCI + Terraform 6モジュールでインフラを自ら構築し、Build-first戦略でZero-Downtimeデプロイを実現しました。
  • Time BucketサンプリングでGemini API送信量を97%削減し、AI機能をコスト上限内で運用しました。
全体構造

チームプロジェクトからプロダクションまで。

チームプロジェクトとして始めたSpring Bootバックエンドをプロダクションサービスに転換しました。 基本的なCRUD APIのみ存在していたプロジェクトに認証、インフラ、デプロイパイプラインを構築し、AI分析機能を追加しました。

  • レイヤードアーキテクチャをドメイン別構造に再構成しました。
  • NextAuth OAuth → JWT認証体系を実装しました。
  • OCI上にインフラを自ら構築し、Zero-Downtimeデプロイを実現しました。
  • Gemini APIベースの感情分析機能を追加しました。

API Endpoints

DomainEndpointsDescription
AuthPOST /auth/verify, /auth/refreshOAuthトークン検証 → JWT発行、更新
UserGET, DELETE /users/me, PUT /users/consent, POST /users/logoutプロフィール照会、アカウント削除、同意管理、ログアウト
EmotionPOST, GET, PUT /emotions/records感情記録の作成・照会・修正
ChartGET /chart/level, count, reason期間別感情統計(レベル、頻度、理由)
AIPOST /ai/emotion-predictions, GET /period-analyses感情予測、期間分析

Infrastructure

OCI Cloud (Terraform 6 Modules)

Ampere A1 ARM64 (4 OCPU · 24GB)

Backend (docker-compose)

Frontend (docker-compose)

SSH + Docker

AWS Route53 zerogv.com · api.zerogv.com

GitHub Actions CI/CD · Zero-Downtime

Load Balancer HTTPS · TLS 1.3

Nginx Reverse Proxy · Rate Limit

Next.js 15 :3000

Spring Boot 3.2 :8080

MySQL 8.0 :3306

レイヤー技術役割
IaCTerraform (6 modules)VCN · Compute · LB · Certificate · Storage · Monitoring
CloudOCI Ampere A1ARM64インスタンス
ProxyNginxリバースプロキシ · Rate Limit · ドメイン別ルーティング
ContainerDocker ComposeFrontend · Backend · MySQL隔離
SSLLet's EncryptACME DNS-01 via Route53 · 自動更新
CI/CDGitHub ActionsSSH + Docker Build → Health Check → Auto-Rollback
AIトークン最適化

全部送ると一ヶ月も持ちません。

Gemini APIで感情記録を分析する機能を実装しようとしました。 1年分の記録を全て送るとリクエストあたりのInputトークンが~55Kに達しました。プロジェクト予算では賄えませんでした。

データを減らすと分析品質が下がり、機能自体を諦めるにはコア機能でした。

バケット一つ、代表記録一つ。

全てを送れないなら、各期間を代表する記録だけを選んで送ればよかったのです。 期間を均等な時間単位(バケット)に分割し、バケットごとに代表記録を1件ずつ選びました。Year分析なら月単位で12個です。 全体から一度に選ぶと記録の多い月に偏り、1年の流れを捉えられないためです。

Periodバケット単位サンプル数削減率
Year月 (MONTH)12件365 → 12 (97%)
Month週 (WEEK)4件~30 → 4 (87%)

残る問題は「代表」をどの基準で選ぶかでした。 基準はすでにサービスの中にありました。チャートが期間ごとに集計していた平均感情レベルと理由統計で代表の条件を定義しました。

  • 平均感情レベルに最も近いこと
  • 最も頻繁に現れた理由を含むこと

理由統計はバケット単位で再集計し、感情統計のために用意していた集計クエリはそのまま再利用しました。

月ごとに繰り返し · 12回

感情記録365件

チャートAPI集計 月別平均レベル

理由統計集計 月別最頻理由

その月の記録をスコアリング レベル60% + 理由40%

最高点1件を選択

代表記録12件 → Gemini送信

レベル60% + 理由40%で点数をつけました。

感情レベル60% + 理由マッチング40%の加重スコアで各バケットの代表記録を選別しました。

private double calculateMatchScore(
    EmotionRecord record, Double targetLevel, String topReason
) {
    // レベルは0~6なので最大の差は6
    double levelScore = 1.0 - (Math.abs(record.getEmotionId() - targetLevel) / 6.0);
 
    double reasonScore = record.getEmotionReasons().contains(topReason) ? 1.0 : 0.0;
 
    return (levelScore * 0.6) + (reasonScore * 0.4);
}

同点の場合は日記が長い順、理由が多い順、最新順でタイブレークしました。

例: Year分析の1月バケット (平均感情レベル: 4.5、最頻理由: "Work")

記録感情理由計算スコア
ALevel 4Work, Family平均と0.5差 + Work含む0.95 ✅
BLevel 5Health平均と0.5差、Workなし0.55
CLevel 2Work平均と2.5差 + Work含む0.75

送信量97%削減、リクエストあたり$0.002

指標Before(全件送信)After(サンプリング)
年間分析送信量365件12件 (97%削減)
リクエストあたりInputトークン~55K tokens~2.4K tokens
リクエストあたりコスト$0.017$0.002

※ AI分析結果は24時間キャッシュし、感情記録が変更されると該当期間のキャッシュを無効化しました。

arrow_downwardさらに詳しく見るarrow_downward