every Tech Blog

株式会社エブリーのTech Blogです。

Laravel開発で注意したい Eloquentの落とし穴と正しい使い方

はじめに

こんにちは、リテールハブ開発部でバックエンドエンジニアをしているホシと言います。
現在、小売アプリの開発でLaravel11を利用してAPI開発を行っています。

今回はとても便利で、開発効率を大きく上げてくれるツール「LaravelのEloquent ORM」についてお話できればと思います。
ただ、Eloquentに限った話ではなくORM全体の話でもあるのですが、使い方を間違えるとパフォーマンス低下や予期しないバグを引き起こすこともあります。
実際に使用してみて、便利さの裏にある注意点や、SQLの知識・理解が非常に重要であることを実感したので、今回はその点についてお話しできればと思います。

1. Eloquentとは? Laravelでのデータ操作をシンプルにするORM

Eloquentは、Laravelに標準搭載されているORM(Object-Relational Mapping)です。
これを利用することで、データベースのテーブルをPHPのオブジェクトとして扱えるようになります。

通常、データベース操作を行うにはSQLを書く必要がありますが、Eloquentを使えばSQLを使用せず、PHPコードだけでデータの取得・更新・削除などができます。

たとえば、usersテーブルのID=1のnameデータを取得する場合、SQLの場合は以下のように書きます。

SELECT name FROM users WHERE id = 1;

ただ、上記はデータベースから取得するためのSQLというだけで、 実際のコードでは、このSQLを実行した上で、結果からnameを取り出す処理を別途記述する必要があります。

Eloquentを使用すれば、上記の処理が含まれた状態でデータ取得できます。 その代わり、Eloquentはモデルの定義が事前に必要です。

このコードでID=1のユーザのnameを取得できます。

<?php

$users = User::find(1);
$users->name; // ユーザ名の取得

Userはusersテーブルに対応する「モデル」クラスです。 モデルを通じて、EloquentはSQLを自動で生成・実行してくれます。 このモデルを使用することで、取得したデータも簡単にアクセスできます。

モデルとテーブルの関係

Laravelでは、モデル名とテーブル名は命名規則に従って自動的に対応してくれます。 あらかじめUserモデルを定義しておくことで、上記のようなEloquentを使用したデータ取得ができます。

モデル名:User
対応テーブル名:users

また、Eloquentの強みの1つが「リレーション(テーブル間の関連)」を簡単に扱える点です。 モデル間のリレーションも簡単に定義することができます。

例えば、1人のユーザが複数の投稿(Post)を持つ「1対多」の関係を定義する場合:

<?php

// Userモデル
public function posts() {
    return $this->hasMany(Post::class);
}

と定義することで、ユーザの投稿一覧も簡単に取得できるようになります。

以下のコードでID=1のユーザの投稿内容を取得できます。

<?php

$user = User::find(1);
$posts = $user->posts;

SQLのようにJOINを意識せず、オブジェクトの形で関連データを取得できます。

このように、データの取得からアクセスまでを一貫して扱える点が、Eloquentの大きな利点です。

2. Eloquentの「落とし穴」:気付きにくいN+1問題とその回避法

前述の通り非常に簡単にデータ取得ができてしまい、様々なケースでも何となくの理解で私自身使用していました。 ただ、実際は裏でどのように動いているのかを理解して使用しないと思わぬ落とし穴があることがわかりました。
そこで、Eloquentの動きを理解せず使用していると特に遭遇しやすいN+1問題についてお話します。

N+1問題とは?

例えば、ユーザ一覧とそれぞれの投稿数を表示したいとします。

usersテーブルとpostsテーブルは1対多の関係で定義しているとします。

ユーザ毎の投稿数を表示するコード:

<?php

$users = User::all();
foreach ($users as $user) {
    echo $user->posts->count();
}

一見シンプルで正しく動作しているように見えますが、実際にはパフォーマンスが大幅に低下する書き方です。

データ取得の流れとして、

  • 最初にusersテーブルを1回検索
  • その後、各userに対して postsテーブルへクエリ(投稿数N回)を繰り返し検索

つまり、合計で1 + N回のクエリが実行されてしまいます。
例えばユーザが1000人いれば、クエリは合計で1001回実行されることになり、パフォーマンスに大きな影響を与えます。
開発時はデータ数も限られているため、特に遅くなるわけでもエラーになるわけでもなく正常に取得できるため、動きを理解していないと問題に気付きにくいです。

ORMにおける「Eager」と「Lazy」の考え方

Eloquent(ORM)でリレーションを扱うときに登場する重要な概念が、「Eager Loading(イーガーローディング)」「Lazy Loading(レイジーローディング)」があります。

どちらもリレーション先のデータを取得する方法ですが、パフォーマンスに大きな影響を与えるため、使い分けが非常に重要です。

Lazy Loading(遅延読み込み)

デフォルトでは、Eloquentはリレーションを「必要になったとき」に読み込みます。
これがLazy(レイジー)=遅延読み込みです。

この方法の大きなメリットは上述の通り必要な時に読み込むということになります。
例えばメインデータを取得時点では不要なケースで、ある操作や表示時などの必要な時だけに読み込むようにすれば、 余計な読み込みが減り、リソースを効率的に使用できます。

しかし、その代わり繰り返し取得しないといけないケースなどではパフォーマンスに大きな懸念があります。

Eager Loading(即時読み込み)

上記を回避するために使うのが、Eager Loadingです。 リレーションを最初からまとめて読み込む方法です。

コード例:
「with」を使用して関係テーブルを記述します。

<?php

$users = User::with('posts')->get();

foreach ($users as $user) {
    echo $user->posts->count(); // 追加クエリなしでアクセスできる
}

この場合、発行されるSQLは以下の2回のみになります。

SELECT * FROM users;

SELECT * FROM posts WHERE user_id IN (...);

→これがN+1問題の回避につながります。

ただし、Eager Loadingにも注意すべき点があります。
上記はwith指定があるため、Eager Loadingとして、

SELECT * FROM posts WHERE user_id IN (...);

を一緒に実行して、1回の検索で必要なposts情報が取得できています。
しかし、このIN句に入るIDがどの程度の規模かをしっかり把握しておかないと予期せぬ大量データの事前取得につながります。
ここがSQLのJOINを使用した考え方と大きく異なるところかと思います。 もちろんJOINでも大量データの考慮は必要ですが、JOINの場合は1クエリで取得でき、より大量データ取得とのパフォーマンスは高くなります。
こちらの件はうまく使い分けることの重要性の話でもあり、後ほど説明できればと思います。

以下のSQLのように、

select * from users inner join posts on users.id = posts.user_id

この結合であれば、仮に1万のusersデータがあっても問題なく取得はできますが、 Eager Loadingでは、user_id IN (...) のように複数IDを一括で取得するため、IN句に大量のIDが含まれる可能性があり、DBの制約、メモリ使用量や処理時間に影響を及ぼします。 この事象が起きることは常に考慮してEloquentを使用する必要があります。

N+1を避けるには?

リレーションをループで使うときは必ずwith()の使用を検討します。
また、例えばusersにposts、postsにcommentsが関連している場合、正しくwithを指定します。

<?php

User::with(['posts', 'posts.comments'])->get();

とそれぞれ指定する必要があります。
これも理解していれば特に問題はないのですが、私自身「'posts.comments'」だけでどちらのテーブルも入っているので十分だと思っていた時期がありました。
普通に動くので問題があることがしばらく気付けず・・・。

また、当たり前ですがプログラム上で繰り返し取得するようなケースを書いている場合もN+1問題と同様の事象になるので、 そういったコードについても対策は必ず行う必要があります。

3. Eloquentを使用する場合もSQL知識、理解は非常に重要

データを取得する際、シンプルな取得であれば前述したような1行、2行で記載でき、特にSQLを意識する必要はあまりありませんが、 少し複雑な条件であったり、複数のテーブルをまとめて取得するようなケースでは、Eloquentのコードだけでは、どのようなSQLが生成・実行されているかを把握しづらいことがあります。

少ないデータやシンプルなテーブル構成というのは実際のサービスではほとんどないと思いますので、 コードを実装する上では結局どのようなSQLを使用してデータを取得しているのかをしっかりと把握する必要があります。

特に重要なSQL理解

  • INNER JOIN、LEFT or RIGHT JOINなどの結合仕様
  • リレーション先のテーブルに対してのWHERE条件のための結合やサブクエリの知識
  • 今後のデータ数に合わせたパフォーマンス考慮、インデックス設定の検討
実行SQLを確認する方法(何となく取得できていそうを避ける)

Eloquentを使っていると、裏でどんなSQLが発行されているか分かりづらいことがあるため、 開発時は常にどのようなSQLが発行されているかを以下のログ出力を利用して確認できるようにしておきます。

私の失敗例:

<?php

$query = User::where('email', 'like', '%@example.com');
dd($query->toSql());

上記は以下のSQLが返ってきます。

select * from `users` where `email` like ?

しかし、実際の値が分からないことに加え、Eloquentでは単なるSQLの実行とは異なり、 モデル経由で値を取得する際に、Lazy Loadingによって上記以外の場所で意図せず追加のSQLが実行されているケースもあります。

ここで私は他にSQL発行していることに気づけず、どのタイミングでどのようにpostsのデータが取得できているのかをしばらく調べることになりました・・・。

正しい確認方法:DB::listen()を使用する
<?php

use Illuminate\Support\Facades\DB;

DB::listen(function ($query) {
    logger()->info('SQL実行ログ', [
        'sql' => $query->sql,
        'bindings' => $query->bindings,
        'time' => $query->time . ' ms',
    ]);
});

※このログ出力は開発環境のみに限定するなど制御することで、不要なログ蓄積やセキュリティリスクを防げます。

このコードをLaravelにあるAppServiceProviderのboot()メソッドに仕込むことで、アプリ全体のクエリをログで確認できます。 これにより、どのようなSQLが実行されているかをすべてログで確認することができるようになります。

なぜこのログ出力が大事か

ログ設定を行った場合に実際に出力されるログ内容です。

[2025-05-XX 12:34:56] local.INFO: SQL実行ログ {
  sql: "select * from `users` where `email` like ?",
  bindings: ["%@example.com"],
  time: "1.22 ms"
}

このログ出力は、開発において非常に重要な情報源となります。

  • 想定しているクエリが発行されているか確認できる。
  • パフォーマンスのボトルネックを見つけやすくなる。(実行時間、実行回数など)
  • 中間処理やAPIレスポンスの生成時など、意図していなかった箇所でクエリが実行されていることがわかるようになる。

他にツールの設定などでN+1問題を見つける方法などもありますので、そういったものも併用するとより解消しやすいかと思います。 ただ、上記を行うだけでも格段にSQLの問題は見つけやすくなります。

これまでの内容を踏まえ、Laravelのデータ操作の最適な方法は?

Laravelには2つの主要なデータベース操作手段があります:

  • Eloquent:LaravelのORM。モデルベースで直感的・オブジェクト指向。
  • Query Builder:SQL構文ベース。柔軟で高速。

両者には得意不得意があり、場面によって使い分けることが大切です。

以下のようなケースでは無理にEloquentにこだわらず、状況によってはQuery Builderの使用も検討すべきです。

  • 集計・統計クエリ(GROUP BY / COUNT / JOIN)を使用したデータ取得
  • 大量データの取得、更新処理など
  • サブクエリ・複雑なWHERE句を使用したデータ取得
  • モデルが不要な一時的なテーブル操作やJOINを使用したデータ取得

それぞれの補完関係を表にしてみました。

EloquentとQuery Builderの使い分け比較

比較項目 Eloquent Query Builder
可読性 ◯(SQLに近い)
モデルの活用
複雑なクエリ構造
パフォーマンス △(特に大量処理)
柔軟な構文制御
チーム開発との親和性 ◎(モデルベースで役割明確) ◯(要コメントや命名工夫)

まとめ:Eloquentを安全に、賢く使うために

いかがでしたでしょうか。
Eloquentは非常に便利ですが、SQL知識、理解があってこそ真価を発揮するのではないかと思います。
それぞれの特性を活かして、最適なデータ取得ができるようにしていきたいです。
また、実際に実行されるSQLを常に意識し、必要に応じてログを確認する習慣を持つことが重要だと思います。
特にAPI作成の上でパフォーマンスを意識するなら、SQLの知識も必須かと思います。
Eloquentの便利さを活かしながらも、裏側の仕組みや発行されるSQLを意識し、より安定した高パフォーマンスな開発ができるようにしていきたいです。

もしSQLをログで確認していない方は、ぜひログ設定をして実行されるSQLを確認していきましょう!

今回の記事が少しでも皆さんの開発のヒントになれば幸いです。
最後までお読みいただきありがとうございました。

TSKaigi 2025 に参加してきました!

TSKaigi 2025 に参加してきました!

TSKaigi 2025 に参加してきました!

目次

はじめに

Dev Enableチームのhondと庄司( ktanonymous )です。

2025年5月23日(金)、24日(土)の2日間に渡って開催されたTSKaigi 2025に参加してきましたので、イベントの様子や印象に残ったセッションをいくつかご紹介します。

各セッションのアーカイブも公開予定とのことですので、ぜひ公式サイト・YouTubeチャンネルなどをチェックしてみてください。

2025.tskaigi.org

www.youtube.com

イベントの様子

TSKaigi 2025は、昨年のいきおいをそのままに、2days開催に拡大してのイベント開催となりました。 TypeScriptを中心にしたカンファレンスとしてTypeScriptの最新情報や活用事例などが紹介され、コミュニティの盛り上がりも感じられました。

昨年と同じく、会場には多くのエンジニアが集まり非常に盛り上がっていました。 会場内では様々なブースが設けられ、各スポンサー企業でのTS活用事例などの紹介が行われていました。

オープニングの様子
オープニングの様子
株式会社KINTO様のブース
株式会社KINTO様のブース
株式会社ドワンゴ様のブース
株式会社ドワンゴ様のブース

株式会社ドワンゴ様が 「any 型を使うのはOK?」というタイトルでアンケートを実施されていました。 ボード上のグラフを突き抜けて回答している方もいて印象に残っていますが、そのほかにもアンケートやTypeScriptにちなんだクイズなどのコンテンツを提供されているブースも多く、どこも盛況の様子でした。

参加レポート

The New Powerful ESLint Config with Type Safety

発表者: Anthony Fuさん レポート: hond

talks.antfu.me

この発表ではLegacy ConfigとFlat Configの比較や移行方法、管理・運用していく中でのノウハウが紹介されていました。
その中でも特に管理・運用に便利なツールが特に印象に残ったので紹介していきます。

  • ESLint Config Inspector ESLint Config InspectorはESLintの設定がどのように設定されているか視覚的に把握するためのツールです。どのようなルールがどのファイルに適用されているかを簡単に把握することができます。

  • eslint-flat-config-utils eslint-flat-config-utilsはFlat Configをより簡単に管理・操作するためのユーティリティ集となっています。複数の設定を結合したり、ルールの無効化などの操作、設定のチェインを行えるようにするためのComposerが提供されています。

  • eslint-typegen eslint-typegenはESLintのルールスキーマから型を自動作成するツールとなっています。これを用いることでエディタでルールオプションの自動補完や型チェックを行えるのでルールの記述が容易になります。

まだ弊社ではFlat Config対応できていない部分もあるのでこれらのツールを用いて適切に管理していきたいです。

AI Coding Agent Enablements in TypeScript - エージェントを自走させよう

発表者: Yuku Kotani さん (https://yuku.dev/) レポート: 庄司

speakerdeck.com

こちらのセッションでは、AIコーディングエージェントをより自律的に動かすことで、より開発者のタスクを減らしていくためのアプローチが紹介されました。

基本的な方針は、AIエージェントに探索させる「解空間を絞り込む」ということです。 これは、発表での表現をお借りすると、『「任意のTypeScript」くらいの広い解空間』から解(出力)を探索するのは精度が低くなるから、ドメインなどのプロジェクト固有の知識を与えることで探索するべき解空間を狭めようという考え方に基づいています。
解空間を絞り込むためのアプローチとして、以下のポイントが挙げられました。

  • コンテキストを注入する(e.g. cursor rules など)
    • 「解空間の定義」を与える
  • 機械的検査
    • 出力を検査して、NGな出力にフィードバックして解空間に押し戻す

また、具体的に取り組まれているアプローチとして、以下のようなものが紹介されました。

  • 型情報を与えることで、解空間を絞り込む
  • 静的解析や自動テストを組み込むことで、解空間を絞り込む
  • デザインシステムを MCP サーバー化することで、解空間を絞り込む

生成AIは発展が著しく、いかに活用するかを模索中の方も多いかと思いますが、「解空間を絞り込む」という考え方を改めて言語化されたことは、とても納得感のある提案だと感じました。 また、30分で実装する開発者と30秒で実装するAIとでは、1分の静的解析に対するボトルネックの程度が段違いであるため、ツールチェインなどの速度もシビアになってくるという点も発表の中でも言及がありました。 そういった視点は自分自身はあまり意識できていませんでしたが、数字を見て、確かに両者の持つ時間スケールは変わってくるなと思い、とても印象に残りました。

複雑なフォームを継続的に開発していくための技術選定・設計・実装

発表者: izumin5210 さん (https://x.com/izumin5210) レポート: 庄司

speakerdeck.com

こちらのセッションでは、複雑で難しくなりがちなフォーム開発について、それに対する技術選定や設計・実装の工夫について解説されました。

フォームの開発は、入力値の管理や入力に基づく状態の構築、同期・非同期処理、そして最終的な出力まで、種々の工程が絡み合うため非常に複雑になりやすいという課題があります。 発表ではまず、react-hook-formのようなフォームの管理を便利にしてくれるフォームライブラリの使用することが紹介されました。
一方で、バリデーションのようなロジックなどがUIの記述に埋もれやすいというデメリットがあるため、複雑さに対処するためにも、「構造」や「ロジック」、「振る舞い」をしっかり分離してモデリングすることが重要だと言及されていました。 加えて、zod のようなバリデーションスキーマライブラリを活用することで、UIと制約の分離や管理に対して自然と注目できるようになるメリットがあります。さらに、不要な「状態」を持たせずに「値」として扱う設計にすることで、よりシンプルで保守しやすい実装が可能になることへの言及もありました。

解決したい課題によって適切なモデルは変わってくるため、プロジェクトに応じたモデリングや技術選定・設計が重要になってくると思いました。

例示するフォームが徐々に複雑なものに置き換えていきながら具体的にどういったアプローチが考えられるのかが紹介され、フォーム開発の難しさやそれに対するアプローチの考え方を再認識できたと思います。
弊社でも複雑なフォームの開発をしている領域があるため、非常に共感できる内容で、参考になりそうなものは積極的に導入していきたいと感じました。

TS特化Clineプログラミング

発表者: mizchiさん レポート: hond

https://tskaigi.mizchi.workers.dev/

この発表では開発を行う中で実際にうまくいったプロンプト、うまくいかなかったプロンプトやそれらの考察が語られていました。
特に印象的だったものについて紹介していきます。

  • 効くプロンプト: テスト駆動開発 (最重要)
    AI自身が壊れたことを早期に検知し自己修復するためにTDDは大事とのことです。TSKaigiの他の発表でもありましたがtest等を採用せず人間が評価する方針(ヒューマンインザループ)をとると人間の評価時間がボトルネックになるのでこの方針は大事だなと感じました。特にcommit粒度を高くしていれば壊れた時点で元の状態に戻しそこから改めて作業を再開することもできると思いました。

  • 効くプロンプト: コメントによる自己記述
    実装時にコードだけでなくそのコードの実装の概要をコメントで記述させることで修正を行う際に一貫性を持たせるとのことでした。個人的にも実装時と修正時で同じようなプロンプトを与えているがずれてしまうことを問題に感じていてメモリバンクを使ってAIが行った作業を思い出させようとしていましたが、コードと一対一で対応するわけではないので完全な一貫性を持たせることはできていませんでした。コメントをAIにかかせることで前述の通りAIに修正を行わせる際に一貫性が生まれるだけでなく、AIが作成したコードを人間が修正するときにもその助けになると感じました。

  • 効く: URL を読む能力 (MCP)
    mizchiさんが作成したURLの先のサイトの本文抽出を行いmarkdownに変換するツールが紹介されていました。そのツールを用いて取得した内容をさらに要約してdocsに保存するとありました。cursorやgeminiではweb検索の機能はありますがそれを行うとコンテキストが膨れ上がり精度の低下を感じていたので検索、要約を明確に分けて要約した結果のみをプロンプトとして与えるのは有効な方法だなと思いました。

今回ピックアップしたもの以外にもそれぞれのプロンプトがうまくいった理由といかなかった理由を丁寧に説明されていました。また、それぞれのプロンプトがスライドに記述されていてすぐに使えるかたちになっていたのでTSKaigiが終了次第すぐ試してみようと思います!

まとめ

TSKaigi 2025は、TypeScriptを中心にしたカンファレンスとして、多くのエンジニアにとって有益な情報が得られるイベントでした。

TypeScriptの最新情報や活用事例を学ぶことができ、新しい技術やアイデアに触れることができました。

今後も、TypeScriptコミュニティの発展と、エンジニアのスキルアップに貢献するイベントとして、TSKaigiが続けられていくことを期待しています。

また、今回の参加レポートが、TypeScriptを学びたい方や、TypeScriptを活用したい方の参考になれば幸いです。

最後に

エブリーでは、ともに働く仲間を募集しています。

テックブログを読んで少しでもエブリーに興味を持っていただけた方は、ぜひ一度カジュアル面談にお越しください!

corp.every.tv

最後までお読みいただき、ありがとうございました!

TestFlightアプリでSandbox課金テストを行う方法

はじめに

こんにちは、デリッシュキッチン開発部でソフトウェアエンジニアをしている新谷です。 新卒で入社してから早1年が経ち、時の流れの速さを感じています。

今回は、アプリ課金システムにおけるサーバー側のテスト方法についてご紹介します。 最近、デリッシュキッチンとヘルシカにおけるアプリ課金システムのサーバー側の修正を行いました。 その際、テスト方法に苦戦したので、その内容をまとめたいと思います。

アプリ課金システムの概要

デリッシュキッチンとヘルシカでは、iOSとAndroidの両方でアプリ課金ができますが、今回はiOSの課金についてのご紹介です。 そもそもアプリ課金には、以下の2種類があります。

  • 一度切りの買い切り型
  • サブスクリプション型(定期課金)

今回、扱うのはサブスクリプション型(定期課金)の課金です。 サブスクリプションの課金は、処理として大きく2つに分けられ、最初の購入処理とその後の通知処理です。 通知処理とは、Appleから通知される購読状態を受けて、ユーザーの状態を更新する処理です。 1つ目の購入処理の概要は以下の図のようになり、購入したレシートの検証とレシートの保存が主な処理です。 ここでは、subscription処理と呼ぶことにします。

2つ目の通知処理の概要は以下の図のようになり、ユーザーの課金状態を更新するのが主な処理です。 ここでは、notification処理と呼ぶことにします。

デリッシュキッチンとヘルシカでは、課金サーバーが分かれているので、厳密には上記の図ではないのですが、行っている処理は大枠では同じです。 ヘルシカの課金サーバーについては、以下の記事で解説されています。

tech.every.tv

サーバー側でテストするべき箇所

サーバー側でテストするべき箇所は、subscription処理とnotification処理の内、課金サーバー側で行う処理です。 テスト項目については、今回省略しますが、基本的にはsubscription処理とnotification処理で、DBに保存しているレシートの情報が正しいかどうかをテストすることになります。

テスト方法

iOS課金には、テストする方法として、以下の2つがあり、どちらとも実際に課金しても請求は発生しません。

  • TestFlightでの課金
  • Sandboxアカウントでの課金

TestFlightでの課金

TestFlightでの課金は、TestFlightで配布されたテストバージョンのアプリで課金を行う方法です。 TestFlightで課金すると、更新頻度は通常より短くなり以下の表のようになります。

1週間 1ヶ月 2ヶ月 3ヶ月 6ヶ月 1年
更新頻度 1日 1日 1日 1日 1日 1日

詳しい情報は公式サイトを参照してください。

developer.apple.com

Sandboxアカウントでの課金

Sandboxアカウントでの課金は、SandboxにしたいAppleアカウントをApp Store Connectに登録することで、Sandboxアカウントで課金を行えます。 Sandbox課金は、更新頻度はApp Store Connectで設定することができ、デフォルトが5分となっています。 表にすると以下のようになります。

更新頻度 1週間 1ヶ月 2ヶ月 3ヶ月 6ヶ月 1年
更新頻度 3分 5分 10分 15分 30分 1時間

更新頻度は以下の候補があります。

  • 3分
  • 5分(デフォルト)
  • 30分
  • 1時間

こちらも、詳しい情報は公式サイトを参照してください。

developer.apple.com

TestFlightで配布しているアプリでSandbox課金を行う方法

TestFlightで課金する場合の注意点として、課金してしまうとキャンセルすることができません。また、更新頻度が1日に1回で1週間続くので、notification処理のテストが1日に1回しか確認できず、キャンセルまで1週間待つ必要があります。

そのため、基本テストするときはSandboxアカウントで課金を行いたいです。 しかし、TestFlightで配布しているアプリでは、TestFlightで登録されているAppleアカウントで課金させられてしまいます。

これは、TestFlightで配布しているアプリでは、TestFlightのアカウントで課金させられてしまうためです。 そのため、一度、Apple Storeからサインアウトを行うことで、TestFlightのアカウントからサインアウトします。 その後、課金をしようとすると以下のようにログイン画面に遷移します。

ここで、Sandboxアカウントでログインを行うことで、Sandbox課金を行うことができます。

まとめ

今回は、iOS課金のサーバー側のテスト方法についてご紹介しました。 Androidの課金については、またやり方が異なるので、また記事にしたいと思います。 iOS課金の実装やテストを行う際の参考になれば幸いです。

AWS ALBのIPアドレスを固定するには

概要

TIMELINE開発部の内原です。

今回はAWS ALBに対するリクエスト時、送信先となるIPアドレスを固定する方法について調査しましたのでその共有です。そこまで一般的な要件ではない気はしますが、参考になれば幸いです。

背景

とある環境において、ALBに対する送信元側がIPアドレスのホワイトリスト形式で通信を許可する構成になっているため前述の要件を満たす必要がありました。

ただ、AWSのALBはIPアドレスが固定されておらず、状況によって変動するという仕様になっています。このため、DNSでALBを指定するにはALB DNS名をCNAMEで指定するか、Route53のAlias機能を用いて指定するのが一般的です。

このやり方だとIPアドレスが変動するため、接続元から接続先IPアドレスを固定したいというニーズは満たせません。

ALBの場合はEIPを用いて固定化することもできない仕様です。

解決策

いくつか選択肢が考えられます。

Global Accelerator(GA) を用いる

GAは本来高速化、安定化の文脈で用いられる機能ですが、副次的に固定IPアドレス(2個)が付与され、このIPアドレス経由で通信可能になるためIPアドレスの固定化をすることができます。

GAはエンドポイントとしてALB, NLB, EC2, EIPを選択することが可能です。つまりALBに対するProxyとして機能し、その中でNetwork経路の最適化やMulti-AZでのfail overといった高速化、安定化が実現できます。

構築手順は以下の通りです。

  1. Global Acceleratorを追加
  2. リスナーを指定。今回はSSLのみ対応すればよいため443, TCPを指定
  3. エンドポイントタイプとしてALB、ロードバランサとして既存ALBを選択
  4. 作成後、プロビジョニング完了を待つ

上記が完了するとGAのDNS名と固定IPアドレス(2個)が発行されます。またDNS名を解決すると2個のAレコードが返却されます。この際、片方のIPアドレスのみでも通信は可能ですが、可用性を考慮すると冗長な構成が望ましいためDNS経由での通信が適当かと考えます。

その際、SSL証明書はALBが保持しているものが利用されるため、接続する際のホスト名はSSL証明書と合致している必要があります。

Network Load Balancer(NLB) + Elastic IP(EIP) を用いる

NLBは固定IPアドレスを付与することが可能なロードバランサーです。ALBと異なり、レイヤー4のロードバランサーとなるため、SSL終端はできません。そのため、SSL終端はALBで行い、NLBはALBの前段に配置する構成となります。

構築手順は以下の通りです。

  1. NLBを作成
  2. リスナーを指定。今回はSSLのみ対応すればよいため443, TCPを指定
  3. ターゲットグループを作成し、既存ALBを指定
  4. EIPを取得し、作成済みNLBにアタッチ

上記が完了するとNLBに固定IPアドレスが付与されます。またNLBのDNS名を解決するとEIPのAレコードが返却されるため、このIPアドレス経由で通信が可能になります。

その際、SSL証明書はALBが保持しているものが利用されるため、接続する際のホスト名はSSL証明書と合致している必要があります。

EC2 + Elastic IP(EIP) を用いる

EC2インスタンスにReverse Proxy(例: Nginx)を構築し、EIPを付与する方法です。ALBの前段に配置し、ALBに対するProxyとして機能します。

構築手順は以下の通りです。

  1. EC2インスタンスを作成
  2. Nginx等のReverse Proxyをインストール
  3. Reverse Proxyの設定を行う
  4. EIPを取得し、EC2インスタンスにアタッチ

上記が完了するとEC2インスタンスに固定IPアドレスが付与されます。またDNSを解決するとEIPのAレコードが返却されるため、このIPアドレス経由で通信が可能になります。

その際、SSL証明書はEC2インスタンス上に設置する必要がありますが、ALBの証明書と異なっていてもよいことになります。

比較

それぞれの手法において特徴を確認します。

なおいずれの手法においても既存のALBの前段にアタッチする形式となるため、サービスのダウンタイムは発生しません。(ただし接続先の切り替え作業は必要です)

また機能要件としては以下を想定します。

  • 接続はSSL
  • 東京リージョン(送信元は日本国内)
  • 100GB/月のデータ転送
  • 24時間稼働

比較表

今回のユースケースにおけるコストや機能についての比較表は以下です。

なお、GAやNLBにおいて最適な利用シーンは元々差異がありますが、今回の用途において特に影響がなかった部分は無視しています。

手法 費用 冗長性 経路最適化 SSL終端化 セキュリティ保護
GA GA本体 $0.025 × 24h × 30日 = $18.00
データ転送 $0.010 × 100GB = $1.00
IPアドレス 2個 × $0.005 × 24h × 30日 = $7.20
合計約 $26.20/月
あり 不要 あり
NLB+EIP NLB本体 $0.0243 × 24h × 30日 = $17.49
LCU $0.006 × 24h × 30日 = $4.32
EIP $0.005 × 24h × 30日 = $3.60
合計約 $25.41/月
なし 不要 なし
EC2+EIP EC2 (t3.micro) $0.0136 × 24h × 30日 = $9.79
データ転送 $0.114 × 100GB = $11.40
EIP $0.005 × 24h × 30日 = $3.60
合計 約 $24.79/月
なし 必要 なし(要実装)

コスト的にはどの選択肢も大きな違いはないようです。

ただEC2+EIPは別途SSL終端化が必要であり、かつ別途インスタンスのメンテナンスも必要となり運用コストが高くなります。また冗長性にも課題があるため、この選択肢を採る利点は薄いように思われます。

GAとNLB+EIPとではコスト的には多少後者が低いもののほぼ同レベルです。GAのほうは経路最適化が可能ですが、日本国内間の通信であればさほど影響はないかもしれません。

GAのほうが新機能であることを踏まえると、より活発な機能追加が行われる可能性は高そうなのでやや有利と言えるかもしれません。

まとめ

ALBのIPアドレスを固定化する複数の手法について比較検討してみました。 今回のユースケースにおいてはGAを用いるのが一番適当に思われますが、状況によっては別の選択肢もあり得そうです。

JSAI2025 (2025 年度 人工知能学会全国大会) にプラチナスポンサーとして協賛します!

目次

はじめに

こんにちは、トモニテ開発部ソフトウェアエンジニア兼、CTO 室 Dev Enable グループの rymiyamoto です。

この度、株式会社エブリーは、2025 年 5 月 27 日(火)から 30 日(金)に開催される「JSAI2025 (2025 年度 人工知能学会全国大会)」に、プラチナスポンサーとして協賛することになりました!

www.ai-gakkai.or.jp

(2025/06/04追記) 参加レポートはこちら

tech.every.tv

JSAI とは?

JSAI は、日本人工知能学会の略称です。人工知能に関する研究の進展と知識の普及を図り、学術・技術ならびに産業・社会の発展に寄与することを目的として設立された、日本の学会です。
今年で 39 回目を迎える全国大会は、大阪の大阪国際会議場(グランキューブ大阪)で開催されます。

エブリーにおける AI 利用に関する取り組み

メインサービスである「デリッシュキッチン」では、「作りたい!が見つかる」をサービスのコンセプトとして、様々な機能を提供してきました。
一方、ユーザーひとりひとりの多様なニーズに合わせたレシピを提案していくには既存機能だけでは難しい部分があり、AI による料理アシスタントとして「デリッシュ AI」を一部ユーザー向けに提供し始めています。

tech.every.tv

また、これらの AI 活用を支える CI/CD パイプラインの構成にも力を入れています。
その中で使っている Databricks Asset Bundles は、データや AI プロジェクトにおいて、ソフトウェア開発におけるソース管理、コードレビュー、テスト、CI/CD といったプラクティスの導入を容易にするツールです。これにより、Databricks の各種リソースを Infrastructure-as-Code(IaC)として管理しています。

tech.every.tv

「デリッシュ AI」の内部では OpenAI API を利用しており Structured Outputs についても触れているので、ぜひご覧ください。

tech.every.tv

イベント当日について

当日は弊社ブースにて、「デリッシュ AI」のポスター展示やデモを行います。
また、ノベルティも配布予定ですので、ぜひご興味のある方はお越しください!

最後に

エブリーでは、ともに働く仲間を募集しています。

このテックブログを読んで少しでもエブリーに興味を持っていただけた方は、ぜひ一度カジュアル面談にお越しください!

corp.every.tv

最後までお読みいただき、ありがとうございました!