はじめに 〜こんなことを真顔で考える金曜日〜
突然ですが、みなさんは GmailFS を覚えていますか?
2004年、Googleが「無料で1GBのメールボックス」を提供開始した時、世界中のギークたちは度肝を抜かれました。当時の無料Webメールといえば数MBが当たり前の時代に、1GBですよ、1GB。
そして必ず一定数いるんですよね、こういうことを言い出す人が。
「1GBのストレージがタダで使えるなら、これってもうファイルシステムじゃね?」
その狂気の発想を実装してしまったのが、Richard Jones氏作の GmailFS でした。LinuxのFUSEを使って、ファイルをチャンク分割してGmailにメールとして送信、IMAPで読み書きする。件名にメタデータを埋め込み、ラベルでディレクトリ構造を表現する。「全てのサービスはファイルシステムになりたがる」 というUNIX哲学の体現でした。
…で、ここで本題です。
「2026年の今、同じノリで作るとしたら、何をストレージにすればいい?」
私の答えは、決まりました。
X(旧Twitter)です。
凍結のリスク、APIの月額課金、利用規約。すべての困難を乗り越えた先に、ロマンがあります。本記事では、設計から実装、そして「なぜこれが1.4億倍(実は300億倍)非効率なのか」までを真面目に解説していきます。
ちなみに、最後まで読むと 実際に動くソースコード が手に入ります。WSLでもLinuxでもOKです。
この記事で呼んでいるX-FSは真面目な方のXFSとは別物です。
紛らわしくてゴメンナサイ。
第1章: そもそもファイルシステムとは何か
ファイルシステムの構成要素をざっくり整理してみましょう。
| 構成要素 | 役割 |
|---|---|
| スーパーブロック | FSのメタ情報(ルート位置など) |
| inode | ファイル/ディレクトリの実体(メタデータ) |
| ディレクトリエントリ | 名前 → inode のマッピング |
| データブロック | 実データを保存する単位 |
| ジャーナル | 整合性のためのログ |
これを、Xの世界の概念にマッピングしてみます。
| FSの概念 | X-FSでの対応 |
|---|---|
| スーパーブロック | ピン留めポスト 📌 |
| inode | ポストID(18桁) |
| ディレクトリエントリ | リプライ関係 |
| データブロック | ポスト本文(Base64) |
| ハードリンク | リツイート |
| シンボリックリンク | 引用ポスト |
| inode検索 |
ハッシュタグ #inode_42
|
| mtime | ポスト作成日時 |
| ファイル削除 | ポスト削除 |
…ちょっと待ってください。これ、意外と全部マッピングできるんですよ。
私もここまで整理してみて、「あれ、もしかして真面目に動くのでは?」と気づいてしまいました。気づいてしまったら、もう作るしかない。
第2章: X-FSの全体設計
ストレージ階層
┌──────────────────────────────────────────────────┐
│ 📌 [ピン留め] ROOT / SUPERBLOCK #x_fs_root │ ← スーパーブロック
└──────────────────────────────────────────────────┘
│
├── 💬 [リプライ] 📁 mkdir docs #x_fs_dir ← ディレクトリ
│ │
│ ├── 💬 [リプライ] 📄 touch hello.txt ← ファイル(inode)
│ │ │
│ │ ├── 💬 [X-FS_CHUNK:0] 44GT44KT... ← データブロック
│ │ ├── 💬 [X-FS_CHUNK:1] 44Gr44Gh...
│ │ └── 💬 [X-FS_CHUNK:2] 44Gv44CB...
│ │
│ └── 💬 [リプライ] 📄 touch neko.jpg
│ └── ... (5,000ポスト続く)
│
└── 💬 [リプライ] 📁 mkdir photos #x_fs_dir
つまり、全てがリプライツリーで表現されるわけです。あなたのファイルシステムが、誰かのスマホで通知される世界。控えめに言って最悪です。最高ですね。
チャンクサイズの決定
Xのポストは280文字制限があります。バイナリデータはBase64エンコードで詰め込みますが、
- Base64は 3バイト → 4文字 に膨張する
- プレフィックス
[X-FS_CHUNK:9999]で約20文字消費
なので、安全圏は次の通り:
$$
\text{最大バイナリサイズ} = \frac{(280 - 20) \times 3}{4} = 195 \text{ バイト}
$$
実装では安全マージンを取って 1ポスト = 180バイト としました。
第3章: 「1.4億倍非効率」の根拠(実は控えめでした)
このセクションを書くにあたって、実際に計算してみたところ、控えめに見積もっても300億倍ぐらい非効率 であることが判明しました。タイトル詐欺すみません。
速度の試算
1GBのファイルを保存することを考えます。
必要なポスト数 = 1,073,741,824 バイト ÷ 180 バイト/post
≈ 5,965,232 ポスト
1GB = 約600万ポスト です。これ書き込み終わるまでに何年かかるかわかります?
X API 無料枠 (月500ポスト) で1GB保存する場合
5,965,232 ÷ 500 = 11,930ヶ月
= 994年
994年です。 平安時代から書き込み始めて、今ようやく1GBが終わる頃合いです。源頼朝が生まれる前から書き始めて、ちょうど令和の今、書き込み完了という壮大なプロジェクト。
SSDとの比較
通常のSSDで1GB書き込みに約1秒として、
X-FS(無料枠): 994年 × 365日 × 24時間 × 3600秒 = 約3.14×10¹⁰ 秒
通常SSD: 1 秒
比率: 約 314億倍 遅い
「1.4億倍」と書きましたが、実際は約314億倍 でした。タイトルが控えめすぎて反省しています。
金額の試算
X API Basic プラン(月$200で月10,000ポスト投稿可)で同じことをやると、
5,965,232 ポスト ÷ 50 ポスト/$ = $119,305
1GBで約1,200万円 です。Google Drive 100GBが年間2,500円、AWS S3 が1GBあたり月数円であることを考えると、なかなかの強気価格設定ですね。
ストレージ効率の試算
180バイトのデータを保存するために、X側のデータベースには:
- ポスト本文: 約260バイト
- メタデータ(post_id, user_id, created_at, in_reply_to, lang, source, etc.): 約500バイト〜
- インデックス、レプリケーション含めて推定: 約2KB
ストレージ膨張率: 2,000 ÷ 180 ≈ 11.1倍
実データ180バイトを保存するのに、Xのサーバには2KB書かれます。Elonさんごめんなさい。
第4章: 実装
ここからが本題です。実際にFUSEを使って実装していきます。WSL上のPythonで動かします。
⚠️ 重大な免責事項
本記事の実装は、本物のX APIには接続しません。ローカルJSONでXを完全シミュレートするモックバックエンド を使います。
理由は明白で、本物に繋いだら以下が確定するからです:
- 連投スパム判定で即凍結
- 月$200〜のAPI課金で破産
- 利用規約違反による永久BAN
「自分のアカウントで挑戦してみたい!」という方は、
MockXBackendクラスを差し替えるだけで動く設計にしてあります。自己責任 でどうぞ。一応墓碑銘くらいは用意しておきます。
モックバックエンド
まずはXをシミュレートするバックエンドです。json ファイルに「ポスト一覧」を貯めていきます。
class MockXBackend:
"""X APIのモック。すべてローカルJSONに保存。"""
def __init__(self, db_path):
self.db_path = Path(db_path)
if self.db_path.exists():
self.db = json.loads(self.db_path.read_text(encoding="utf-8"))
else:
self.db = {
"next_id": 1000000000000000000, # X風の18桁ID
"posts": {}, # post_id -> {text, in_reply_to, created_at, meta}
"pinned": None, # ルートのポストID
}
self._save()
def post(self, text, in_reply_to=None, meta=None):
if len(text) > 280:
raise ValueError("280文字を超えています")
pid = str(self.db["next_id"])
self.db["next_id"] += 1
self.db["posts"][pid] = {
"text": text,
"in_reply_to": in_reply_to,
"created_at": time.time(),
"meta": meta or {},
}
self._save()
return pid
ポイントは、280文字制限を真面目に再現している ことです。本物のXに対する敬意です。
FUSEオペレーションの実装
FUSEは「ファイルシステム操作をユーザー空間のコードで処理する」仕組みです。fusepy を使うと、Pythonで getattr, readdir, read, write といったメソッドを実装するだけでファイルシステムが作れます。
データの読み出しは、対象ファイルポストへのリプライを全部集めて、Base64デコードして連結 するだけです。
def _read_file_data(self, file_pid):
"""ファイルポストの子リプライからデータを連結して復元"""
chunks = self.backend.get_replies(file_pid)
data = b""
for cid, c in chunks:
if c["meta"].get("type") != "chunk":
continue
# ポスト本文の "[X-FS_CHUNK:N] " プレフィックスを剥がす
payload = c["text"].split("] ", 1)[1]
data += base64.b64decode(payload)
return data
書き込みは逆向きです。既存チャンク(リプライ群)を削除して、新しいデータを180バイトずつBase64エンコードして連投します。
def _write_file_data(self, file_pid, data):
# 既存チャンクを全削除
for cid, c in self.backend.get_replies(file_pid):
if c["meta"].get("type") == "chunk":
self.backend.delete(cid)
# 新しいチャンクを連投
for idx, off in enumerate(range(0, len(data), CHUNK_SIZE)):
chunk = data[off:off + CHUNK_SIZE]
encoded = base64.b64encode(chunk).decode("ascii")
text = f"[X-FS_CHUNK:{idx}] {encoded}"
self.backend.post(text, in_reply_to=file_pid,
meta={"type": "chunk", "idx": idx})
このコードを本物のX APIに繋ぐと、ファイル1個保存するたびに数千件の連投が走り、即凍結します。素晴らしいですね。
ディレクトリ作成 = ポスト投稿
mkdir も、結局のところ「親ポストへのリプライを1件作る」だけです。
def mkdir(self, path, mode):
parent_pid, name = self._resolve_parent(path)
self.backend.post(
f"[X-FS] 📁 mkdir {name} #x_fs_dir",
in_reply_to=parent_pid,
meta={"type": "dir", "name": name, "mode": mode}
)
return 0
mkdir hoge を実行すると、Xに [X-FS] 📁 mkdir hoge #x_fs_dir というポストが投稿されます。フォロワーから見たら本物のホラー ですね。
第5章: 動作確認
セットアップ
# WSL/Linux 環境で
bash setup.sh
# → fuse + fusepy を入れてくれる
起動
ターミナル1:
python3 x_fs.py /tmp/xmount
ターミナル2:
$ echo "hello X-FS" > /tmp/xmount/test.txt
$ cat /tmp/xmount/test.txt
hello X-FS
$ mkdir /tmp/xmount/photos
$ ls -la /tmp/xmount
total 0
drwxr-xr-x 1 ebe ebe 4096 May 22 12:00 photos
-rw-r--r-- 1 ebe ebe 11 May 22 12:00 test.txt
ちゃんとファイルシステムです。 Linuxから見れば、これがXに保存されているとは思いもしません。
裏側を覗く
x_fs_timeline.json を覗くと、あなたのファイル操作が「ポスト」として並んでいます。
{
"posts": {
"1000000000000000000": {
"text": "[X-FS] 📌 ROOT / SUPERBLOCK #x_fs_root",
"in_reply_to": null
},
"1000000000000000001": {
"text": "[X-FS] 📁 mkdir photos #x_fs_dir",
"in_reply_to": "1000000000000000000"
},
"1000000000000000002": {
"text": "[X-FS] 📄 touch test.txt #x_fs_file",
"in_reply_to": "1000000000000000000"
},
"1000000000000000003": {
"text": "[X-FS_CHUNK:0] aGVsbG8gWC1GUwo=",
"in_reply_to": "1000000000000000002"
}
}
}
本物のXのスレッドそっくり で、地味にツボります。あなたの hello X-FS という11バイトのテキストが、3つのXポストとして永遠に残るかもしれない世界。
第6章: なぜこんなものを作ったのか
ここまで読んでくださった方は、薄々お気づきだと思います。
完全に実用性はありません。
でも、こういうものを作る過程で得られるものは、意外と多いんですよね。
学べたこと
-
FUSEの基本:
getattr,readdir,read,write,create,unlinkなどの実装 - ファイルシステムの内部構造: スーパーブロック、inode、データブロックの関係
- チャンク分割と再構成: 制約のあるストレージへの適応
- AIによる設計レビュー: 今回の実装はClaude (Anthropic) と何度も対話しながら詰めました
そして何より、
「全てのサービスはファイルシステムになりたがる」を体感できる
UNIX哲学に /proc, /sys のような「あらゆるものをファイルとして抽象化する」思想があります。GmailFSはその思想をサービス越しにやった先駆者でした。
そして20年後、Xでも同じことができることが(理論上)証明されました。SlackFS, NotionFS, DiscordFS だってきっと作れます。実用性?知らない子ですね。
業務にどう活きる?
…活きません。正直に書きます。活きません。
でも、こういう「絶対に役に立たないもの」を本気で作ろうとする時間が、エンジニアの引き出しを広げてくれるんじゃないかな、と思っています。少なくとも私は、この記事を書くために行った計算で「Base64のオーバーヘッドって33%もあるのか」と再認識しました。
業務で似たような制約に当たった時、「あ、これX-FSで考えたやつだ」と思い出せるかもしれません。多分思い出さないですが。
おわりに
GmailFS から22年。
時代は変わり、ストレージは安くなり、APIは有料化され、利用規約は厳しくなりました。それでも「サービスをファイルシステムとして使う」というロマンは、ギークの心に生き続けています。
もし本記事を読んで「俺ならNotionでやる」「私はSlackで作りたい」と思った方がいたら、ぜひ作って共有してください。きっと記事がバズります。たぶん。
最後に、本物のX APIに繋ぐコードは入れていません。「凍結覚悟で挑む勇者」のために、拡張ポイントだけは残してあります。墓碑銘も書きます。
それでは、良いハック生活を。
参考リンク
- ソースコード一式: (リポジトリURL)
- GmailFS (Internet Archive): https://web.archive.org/web/2004*/gmailfs
- fusepy: https://github.com/fusepy/fusepy
- X API 料金プラン: https://developer.x.com/en/products/x-api
追記: 想定問答
Q. これ本当に動くんですか?
A. モックバックエンドでは動きます。本物のX APIに繋ぐと凍結するまでは動きます。
Q. 規約違反では?
A. ローカルJSONをバックエンドにする限り、X社には1バイトも送信されません。規約違反するためには、追加実装+勇気が必要です。
Q. なぜ「1.4億倍」だったタイトルなのに、本文で300億倍と書いたのか?
A. タイトルを書いた時点では雑な見積もりでした。本気で計算したら更にひどかったというオチです。タイトル詐欺で申し訳ない。