ファイナンス、情報通信技術のスキル・アグリゲーション・サイト

' . iseeit.jp 情報通信技術 . '
 
MongoDB / Access Log ①

なぜログを MongoDB に
スキーマレスが効いた日

5つのサイトを1台のVPSで動かし、全アクセスを1箇所に記録する。最初は数フィールドの素朴なログでした。 ところが運用を始めると「これも残したい」が次々出てくる。後から形が育つデータに、 なぜ RDB ではなく MongoDB が効いたのか——実際の追加履歴から辿ります。

MongoDB スキーマレス アクセスログ 5サイト共通 対比デモ

§ 0 — 導入

1行のログに、何を残すか

このVPSでは、5つのサイト(gwaw / iseeit / appw / ibe / sasagawa)を1台で運用しています。 全サイトのアクセスを1箇所に集めて記録したい——そう考えたとき、最初の問いは 「1アクセスで、何を残すか」でした。

最初に決めたのは4つ。uri(どのページ)、uah(User-Agent)、 addr(IP)、created_at(いつ)。ごく素朴なスタートです。 この時点では、RDB でも MongoDB でも、どちらでも書けます。

この記事の IP はすべてマスク済み。 例に出す 203.0.113.x はドキュメント用に予約されたレンジで、実在のアクセス元ではありません。
§ 1 — 固定スキーマの世界

まず RDB で考えてみる

素直に RDB でテーブルを切るなら、こうなります。

-- RDB での素朴な設計
CREATE TABLE access_log (
  id         BIGINT AUTO_INCREMENT PRIMARY KEY,
  uri        VARCHAR(255),
  uah        VARCHAR(255),
  addr       VARCHAR(45),
  created_at DATETIME
);

RDB の強みははっきりしています。型が保証され、JOIN でき、集計が速い。 ログ用途でも十分に使えます。データの形が変わらなければ、これで何の問題もありません。

問題は、ログというデータが育つことでした。

§ 2 — 後から足したくなる

現実は、フィールドが増えていく

運用を始めると「これも記録したい」が次々と出てきました。実際に後から足したのが2つ。

bot(このアクセスはボットか、という判定フラグ)と、 uqid(同じ訪問者を追うためのセッション識別子)。 どちらも、最初の設計には無かったものです。

RDB だと、これが重い。 ALTER TABLE ... ADD COLUMN でマイグレーション。既存行の扱い、テーブルロック、 5サイト分に膨らんだ行数。「ちょっとフィールドを足す」だけのはずが、本番の大工事になります。

いっぽう MongoDB なら、新しいキーを書き込むだけ。既存のドキュメントはそのまま、 新しく記録するものから botuqid が乗ります。 マイグレーションが要らない。ログのように仕様が育つデータと、スキーマレスは相性が抜群でした。

§ 3 — 1アクセス = 1ドキュメント

MongoDB のドキュメント

いま、1行のアクセスログは MongoDB でこういう姿をしています。

// 1アクセス = 1ドキュメント
{
  "uri":        "/if-sub-260812.html",
  "uah":        "Mozilla/5.0 ... Chrome/...",
  "addr":       "203.0.113.42",
  "bot":        false,
  "uqid":       "a3f9c1e0-...",
  "created_at": ISODate("2026-08-13T05:41:00Z")
}

肝は、フィールドが揃っていなくてもいいことです。 botuqid を導入する前に記録した古いドキュメントには、これらのキーがありません。 でも壊れない。「無い=まだその概念が無かった頃のログ」として、新旧が自然に共存します。

型も柔らかい。bot は真偽値、uqid は文字列、created_at は日時。 1つのドキュメントにさまざまな型が同居してよいのです。

§ 4 — 体感デモ

固いテーブル vs 柔らかいドキュメント

同じ「bot フィールドを後から足す」という操作を、RDB と MongoDB の両方でやってみます。 同じ操作が、左右でまったく違う重さになります。

Interactive · Fixed schema vs Schemaless
固いテーブル vs 柔らかいドキュメント
サーバー・DB通信はありません。スキーマ変更の挙動を再現した可視化です。IPは全てマスク済み。
RDB(固定テーブル) 4 列
列は事前に定義。変更は全行に及ぶ。
「bot を追加」を押すと、ALTER TABLE が走ります。
MongoDB(ドキュメント) 3 docs
各ドキュメントが独立。キーは自由に足せる。
「bot を追加」を押すと、新しい doc から bot が乗ります。
「bot フィールドを追加」を押して、左右の違いを見てください。次に「型の違うデータを入れる」も試すと、型の硬さ/柔らかさが分かります。

RDB は ALTER TABLE全行に bot=NULL の列が一斉に付き、その間テーブルはロックされます。 MongoDB は新しいドキュメントだけbot を持ち、既存はそのまま。追記するだけです。 同じ「フィールドを足す」が、片方は大工事・片方はただの追記になります。

§ 5 — 代償と、向き合い方

スキーマレスは、無秩序ではない

柔らかさには裏返しの代償があります。型が保証されない、表記ゆれが起きうる、 「そのフィールドが無いドキュメント」を常に考慮しないといけない。自由は、放っておくと散らかります。

アプリ側で、書き込む形を統一する

だから最低限の規律を、アプリ側で持ちます。ログを書き込む関数を1つに集約し、 そこで形を揃える。これは TF.js 記事の normalize(データ形状を揃える)と同じ発想です。 スキーマレス=スキーマを考えない、ではありません。DB に強制されない分、アプリで守る。

よく引く軸には、インデックスを張る

スキーマレスでも、検索を速くする仕組みは同じく重要です。created_at(期間で絞る)や addr(IPで追う)など、よく引く軸にはインデックスを張っておきます。

// よく引くフィールドにインデックス
db.access_log.createIndex({ created_at: -1 });
db.access_log.createIndex({ addr: 1 });
柔らかさを活かしつつ、暴れさせない。 DB は自由に、アプリは規律を持って。この2つを分けて考えるのが、スキーマレスと長く付き合うコツです。

この記事のまとめ

  • アクセスログは後から形が育つデータ。botuqid を運用中に追加した。
  • RDB の ALTER TABLE は全行に及ぶ大工事。スキーマレスなら書き込むだけ・マイグレーション不要
  • 1アクセス = 1ドキュメント。フィールドが欠けた古いログとも自然に共存する。
  • 代償は「型が保証されない」こと。アプリ側で書き込み形を統一し、よく引く軸にインデックスを張る。

関連記事

Next — アクセスログ基盤②
PVからセッションへ — uqid が意味を持った日

後から足した uqid は、最初ただの識別子でした。それを「60分無操作で失効するセッション」に変えた瞬間、 ユニーク訪問者・PV/セッション・直帰率が、初めて意味のある数字になります。その転換の物語です。