Railwayでデプロイし、同時性問題を考慮したいいね機能の追加

1. Spring Bootアプリケーションのデプロイとデータベース接続エラーの解決

プロジェクトの初期段階でデプロイの利便性を考慮し、Railwayプラットフォームを選択しました。Dockerfileベースの自動デプロイシステムを構築しましたが、実行段階でJDBCConnectionExceptionが発生する問題に直面しました。このエラーの主な原因は、データベース接続文字列の形式の不一致でした。

RailwayがHILOするDATABASE_URLは標準PostgreSQL形式に従っていますが、Spring BootのJDBCドライバーは明示的なドライバー識別子を含むアドレスを要求します。これを解決するために、単一のURL変数に依存するのではなく、Railwayが提供する個別の環境変数であるPGHOSTPGPORTPGUSERPGPASSWORDPGDATABASEを組み合わせてapplication.ymlに設定しました。この方法は設定の明確性を高めるだけでなく、環境の変化にもより柔軟に対応できるようになります。

また、サーバーの物理的な位置が応答速度に与える影響を確認しました。韓国のユーザーを主な対象とするサービスの場合、アメリカよりもシンガポール地域にサーバーとデータベースを配置することで、ネットワーク遅延時間を有意に短縮できることを確認しました。この際、サーバーとデータベースを必ず同じ地域に配置し、内部ネットワーク通信を誘導することがパフォーマンス最適化の鍵となります。


2. 効率的な「いいね」機能実装のためのデータベース設計

投稿へのいいね機能は、単純なカウント以上のデータ整合性を要求します。単純に投稿テーブルにカウントカラムを置く方式では、誰がいいねを押したかを識別できず、重複リクエストを制御することが困難です。そのため、「ユーザー」と「投稿」間の多対多関係を解消するための別のPostLikeエンティティを導入しました。

エンティティ設計時にクラスの命名に注意を払いました。SQL予約語であるLIKEとの衝突を防ぐためにPostLikeという名称を使用し、userIdpostIdの組み合わせにユニーク制約条件を付与してデータの重複を根本的に遮断しました。また、特定ユーザーが押したいいねリストを照会するパフォーマンスを高めるために、userIdカラムにインデックスを追加しました。

識別子戦略としては、複合キーの代わりに自動生成される主キー(Long ID)を使用する代理キー戦略を選択しました。これはJPA実装の複雑性を下げ、今後の要件変更に柔軟に対処するためです。エンティティ間の関連関係設定時には、パフォーマンス最適化のために遅延ロード(FetchType.LAZY)を基本として適用しました。


3. 同時性問題の制御とJPA永続性管理

サービス規模が拡大するにつれて、複数のユーザーが同時にいいねを押す際に発生する同時性問題、すなわち「更新損失(Lost Update)」問題を考慮する必要がありました。Javaアプリケーションレベルで単純に値を読み取って増加させた後に再保存する方式は、データ整合性を保証できません。

これを解決するために、データベースのアトミック演算(Atomic Update)を活用しました。JPAの@ModifyingアノテーションとともにUPDATE Post p SET p.likeCount = p.likeCount + 1のようなJPQLを作成して、データベースが直接値を演算するように誘導しました。この方式は行単位のロックを通じて、複数のリクエストが同時に入ってきても正確なカウントを保証します。

ここで注意すべき点は、JPAの永続性コンテキスト管理です。バルク修正クエリは永続性コンテキストを経由せずにデータベースに直接反映されるため、メモリ上のエンティティ状態とデータベースの実際の状態との間に不一致が発生する可能性があります。これを防ぐために@Modifying(clearAutomatically = true)オプションを使用してクエリ実行直後に永続性コンテキストを初期化することで、以降の照会時に最新データを保証されるように設計しました。


4. インフラ運用コストの分析と最適化戦略

開発が進むにつれて、運用環境と開発環境を分離しながらインフラコストに対する検討が必要になりました。RailwayのHobby Planは月5ドルのクレジットを提供しますが、リソースを多く占有するSpring Bootサーバー2台とPostgreSQLデータベースを24時間稼働させる場合、月額推定コストが約18ドルから23ドルに達するという結果を得ました。

コスト効率化のために、まず1つのPostgreSQLインスタンス内に運用用(prod)と開発用(dev)のデータベースをそれぞれ作成して共有する方式を導入しました。これはデータベースサービスを追加で作成することで発生する固定費を節減する効果があります。

さらに、固定費モデルであるAWS Lightsailへの移行を検討しました。月5ドルのLightsail Planは1GBのRAMを提供し、これはDockerを活用して運用サーバー、開発サーバー、データベースを同時に稼働させるのに十分な仕様です。初期設定とGitHub Actionsを通じたCI/CD構築過程がRailwayに比べて複雑である可能性がありますが、長期的な運用コスト面とCI/CD構築経験という学習的価値を考慮すると、Lightsailがより合理的な選択肢であることを確認しました。


5. 結論と今後の計画

本プロジェクトを通じて、単純な機能実装を超えて、デプロイ環境でのネットワーク遅延問題、データベースの論理的・物理的設計、トランザクションと同時性制御、そしてインフラコスト管理まで、サービス全般のメカニズムを深く理解することができました。特に抽象化されたフレームワークの背後で起こるデータベースと永続性コンテキストの相互作用を把握したことは大きな成果でした。

今後はAWS Lightsail環境にインフラを移行し、GitHub ActionsとDocker Composeを組み合わせて安定したデプロイ自動化パイプラインを構築する予定です。また、いいね機能以外にもトラフィックが集中する可能性のある主要機能に対してキャッシング戦略を導入し、パフォーマンスをさらに高度化していく計画です。

リンク:
リンク: » 韓国語で見る (한국어로 보기)
リンク: » 英語版を見る (Switch to English)
シェア: