エキサイトブログにおけるマイグレーションの実施例

はじめに

エキサイト株式会社 バックエンドエンジニアの山縣(@zsp2088dev)です。 普段の業務では、エキサイトブログの新機能開発や運用に取り組んでいます。

先日、エキサイトブログのブログテーマ機能について内部的に大きな変更を行いました。 具体的には、ブログテーマ機能と依存しているトラックバック機能を切り離すために、新テーブルへのマイグレーションを実施しました。

本記事では、マイグレーションを実施した背景や実際に行ったことについて紹介します。

ブログテーマ機能とトラックバック機能

エキサイトブログには、エキサイトブログ編集部が提供するテーマについて、ブロガーが自由に投稿できるブログテーマ機能といったものがあります。 テーマ一覧ページには、数多くのテーマに対する様々な視点や熱量の高い記事が集まり、新しい発見や学びを得られます。 このテーマに対して、投稿していただいた記事の中からエキサイトブログ編集部の記事内で紹介することもあります。 この機能は、当初から提供しており、多くのブロガーにご利用いただいております。

ブログテーマに投稿した記事一覧

ここで、ブログテーマ機能と関係の深いトラックバック機能についても紹介します。 トラックバック機能は、「ある記事に対する記事を作成し、相手にも作成した記事を知らせることのできる」機能です。 馴染みのあるもので言えば、X(旧Twitter)の引用リポストのような機能です。

技術的な側面について着目すると、ブログテーマの機能は、このトラックバック機能を使用して実装していました。 テーマ投稿用ユーザーに対してトラックバックをすることで、テーマ投稿した記事として扱うような実装となっています。

2023年1月20日より、利用者数が減少していることを理由に、一般ブロガー間での新規のトラックバックを停止しています。 テーマ投稿については、テーマ投稿用ユーザーへのトラックバックのみを許可することで引き続きテーマ投稿をできるようにしていました。

blog.excite.co.jp

ブログテーマ機能の詳細

続いて、トラックバック機能を使用してテーマ投稿した記事を取得する手順について説明します。 テーマ投稿した記事を取得する仕組みは、以下の図のとおりです。

  1. スキーマAから、トラックバック機能を使用してテーマ投稿した記事URL(https://[ブログURL].exblog.jp/[記事ID])を取得する
  2. アプリケーション内で、正規表現を使用してブログURLと記事IDを抽出する
  3. スキーマBから、2で抽出したブログURLと記事IDをもとに記事情報を取得

この方式では、以下の3つの問題がありました。

  • トラックバック機能とブログテーマ機能に依存関係がある
  • キャッシュが効いていないときのページ応答速度が遅い
  • トラックバックという言葉に馴染みがない

これらを踏まえた上で、テーマ投稿した記事を管理しやすくするためのテーブルを作成し、書き込みと参照先を切り替えることにしました。

マイグレーションツールの作成

新テーブルへのマイグレーションをするにあたり、「スキーマをまたぐ」「データの加工」が必要であることを考慮した結果、 一度アプリケーションに落とし込んでマイグレーションをするのがよいと考えました。 そこで、マイグレーションツールを、普段の開発でも使用しているJava17 / SpringBoot (ShellComponent) / JdbcTemplateを使用して作成することに決めました。

トラックバックテーブルには、一般ブロガー間でのトラックバックを含めた数十万のレコードが格納されています。 その中から、テーマ投稿に関係するレコードのみを抽出したり、削除済みの記事や退会済みブロガーの記事などをマイグレーションの対象外としました。 本当に必要なレコードのみをマイグレーション対象とすることで、稼働中のDBへの負荷を最小限の負荷に留めるようにしました。

ここで、実際に作成したコードの一部について紹介します。 データをまとめて追加する際、基本的にはJdbcTemplateのbatchUpdateを使用してまとめてデータの追加をしつつ、 重複エラー時はupdateを使用して1件ずつ追加するようにしました。 これにより、大量のデータを効率よく追加することができるようになります。 *1

以下にサンプルコードを示します。

public void bulkInsert(List<Item> items) {
    int BULK_INSERT_SIZE = 1000;

    // [1, 2, 3, 4, 5] → [[1, 2], [3, 4], [5]] のように分割する
    List<ArrayList<Item>> splitItems = IntStream
            .range(0, (items.size() + BULK_INSERT_SIZE - 1) / BULK_INSERT_SIZE)
            .mapToObj(i -> new ArrayList<>(items.subList(i * BULK_INSERT_SIZE, Math.min((i + 1) * BULK_INSERT_SIZE, items.size()))))
            .toList();

    splitItems.forEach(this::bulkInsertSplitItems);
}

private void bulkInsertSplitItems(List<Item> items) {
    try {
        jdbcTemplate.batchUpdate("""
                INSERT INTO sample_item (
                    value
                ) VALUES (?)
                """, items, items.size(), (ps, item) -> ps.setString(1, item.getValue())
        );
    } catch (Exception e) {
        items.forEach(this::insert);
    }
}

private void insert(Item item) {
    try {
        jdbcTemplate.update("""
                INSERT INTO sample_item (
                    value
                ) VALUES (?)
                """, item.getValue());
    } catch (Exception ignored) {

    }
}

マイグレーションの実施

マイグレーション前の初期の状態は以下のとおりです。

このとき、テーマ投稿した記事の書き込みと参照は旧テーブルを向いています。

続いて、旧テーブルへの参照と書き込みは行いつつ、新テーブルにも書き込みを行うようにします。 このような処理をダブルライト方式と呼んでいます。

ダブルライトを確認後、作成したマイグレーションツールを実行します。 マイグレーションツールで重複エラーを考慮しているのは、Step2以降でデータが追加されているからです。

エキサイトブログでは、本番環境とステージング環境で同じデータベースを使用しています。 ステージング環境で、テーマ投稿ページを見て、旧テーブルを参照している本番環境と同じ結果になることを確認します。 このとき、一部データの抜けている箇所があったため、データ移行ツールを修正し再度実行し、同じ結果になることを確認しました。

最後に、ダブルライトを停止して新テーブルを参照、書き込みをするようにしました。

おわりに

本記事では、エキサイトブログのブログテーマ機能からトラックバック機能を切り離すために、マイグレーションに取り組んだことについて紹介しました。 マイグレーションツールの作成からマイグレーションの実施、ダブルライトの停止まで、1ヶ月弱で取り組むことができました。 エキサイトブログでは、引き続き既存機能の改修や新機能の開発などに取り組んでいきます。

採用アナウンス

エキサイトではフロントエンジニア、バックエンドエンジニア、アプリエンジニアを随時募集しています。 また、長期インターンも歓迎しています。

カジュアル面談からもOKです。少しでもご興味がございましたら、お気軽にご連絡頂ければ幸いです。

▼ 募集職種一覧 ▼ recruit.jobcan.jp

*1:AWS Dev Day 2023 1日目のZOZO社のセッションを参考に実装しました。