本当にデータベースが必要なのですか?

2026/04/15 21:26

本当にデータベースが必要なのですか?

RSS: https://news.ycombinator.com/rss

要約

日本語翻訳:

従来のデータベースはすべてのアプリケーションに必須ではないという核心的なメッセージであり、初期段階のシステムでは規模とニーズに応じて単純な平文ファイルが有効に機能します。ファイルベースのストレージと複雑なデータベースの間での選択は、最終的にデータセットサイズ、特定のクエリング要件、および利用可能なシステムリソースによって決定されます。Go、Bun、Rust を使用したテストから明らかになったのは、メモリマップが最も高速なパフォーマンス(Rust では約 169k req/s)を提供する一方で、データを RAM に全て載せる必要があるためスケーラビリティに制限がかかること、および大規模ファイルに対するラインアースキャンはインデックスがない場合により深刻なパフォーマンス低下を引き起こすこと(Go で 100 万レコードの場合、インデックスなしのラインアースキャンでは 23 req/s に低下)です。対照的に、SQLite は内部でインデックスを管理することで一貫した効率性(約 25k req/s)を維持し、幅固定インデックス付きソート済みディスクファイルでのバイナリ検索は高いスループット(約 40k req/s)を実現し、純粋な ID ロックアップでは SQLite よりも约 1.7 倍のスループット向上をもたらしました。平文ファイルのアプローチでは、ラインアースキャンが失敗するまでに約 9,000 万の日次アクティブユーザーを支持できるため、複雑なジョインや原子性トランザクションはまだ必要とされない大規模なデータセットに対して実用的です。メモリ制約に到達した場合、またはデータ整合性の要件が進化した場合、平文ファイルの移植性によりデータベースへの移行は簡単に行え、ベンダーロックインを回避しつつ、高度なクエリング能力(非 ID ロックアップ、ジョイン、同時書き込み、ACID トランザクション)を必要とするシナリオに重層なデータベースを留保できます。

本文

データベースとは単にファイルの集合体に過ぎません。SQLite はディスク上の単一ファイルであり、PostgreSQL はその前にプロセスが座っている一連のファイルからなるディレクトリです。あなたが過去に使ってきたあらゆるデータベースは、コードで

open()
を呼び出した時と同じように、ファイルシステムに対して読み書きを行います。つまり、「ファイルを使うべきか」という問いではなく、いつも使っているのはファイルです。問題は「データベースのファイルを使うのか、それともご自身のファイルを使うのか」です。特に初期段階の多くのアプリケーションにおいて、答えは「ご自身で管理する」ことになるかもしれません。

もちろん、我々はデータベースを愛しています。「DB Pro」は Mac、Windows、Linux に対応したデータベースクライアントの開発を行っており、事実上の回答でもあります。しかし、「本当に一つ必要なのか」という問いへの正直な答は「スケーリング(規模)」に依存します。そして、多くのアプリケーションはそのほど大規模ではないのが実情です。我々はこれを検証しました。Go、Bun、Rust を使用して同一の HTTP サーバーを構築し、2 つのストレージ戦略を採用した上で、wrk というツールで過酷な負荷テストを行いました。その結果の数値は以下の通りです。

環境設定 3 つのフラットファイル(

users.jsonl
products.jsonl
orders.jsonl
)を使用します。形式は newline-delimited JSON(JSONL)であり、各行に 1 件のレコードが記録され、書き込み時に追記されます。各ファイルは単一のエンティティタイプのみを保持します。 2 つの HTTP エンドポイント:ユーザー作成のための
POST /users
と ID で取得するための
GET /users/:id
です。ベンチマークは GET パス上で実施しました。読み取り動作において、戦略の違いが顕著に現れます。

  • アプローチ 1: ファイルを毎回読み取る 最もシンプルな方法です:ユーザー「abc-123」の依頼があり次第、ファイルを開き、全ての行をスキャンし、各行を JSON パースして ID を照合します。一致するものを発見すると即座に返却します。

    • Go: ...
    • TypeScript (Bun): ...
    • Rust: ... これはいわゆる O(n) 処理です。各依頼でファイル全体を先頭から読み込むため、平均して目標を見つけるまでに半分ほどのスキャンが必要です。ファイルが大きくなると、各依頼の処理速度も低下します。
  • アプローチ 2: メモリに読み込む サーバー起動時にファイルを一度全量読み込み、すべてのレコードを ID でキーとするハッシュマップに格納します。書き込みはマップと両方のファイルへ行われ、読み取りは単一回のマップ検索で完了します。 ファイルは永続的なバックエンドストアとして機能し、マップはインデックスです。プロセスが再起動した場合は、ファイルを再読み込みします。

    • Go: ...
    • TypeScript (Bun): ...
    • Rust: ... 読み取り経路は規模に関係なく O(1) となります。Go の
      sync.RWMutex
      や Rust の
      RwLock
      を活用することで、複数のリーダが並列に進むため、同時期の依頼同士がブロックし合うことはありません。
  • アプローチ 3: ディスク上のバイナリサーチ メモリに全データをロードする必要があるのに飽き足らず、かつ全ファイルスキャンも避けたい場合、どうすればよいでしょうか。中間の策として、データファイルを ID でソートし、それに伴う固定幅のインデックスを構築します。そして

    ReadAt
    を用いてそのインデックスにバイナリサーチを行います。各検索で O(log n) の Seek(100 万レコードなら約 20 回)を行い、データファイルから正確な 1 レコードを読み出します。 インデックス形式は単純です:各レコード 1 行あたり固定の 58 バイト
    <36 キャラの UUID>:<データファイル内のオフセット (20 デシマル)>\n
    です。固定幅であるため、単一の
    ReadAt(buf, entryIndex * 58)
    で任意のエントリにジャンプできます。 データファイルはインデックス構築前に ID でソートされていなければなりません。種付け時または既存の JSONL ファイル上のワンタイム移行ステップとしてソートを行います。新規レコードの追記はソート順序を壊すため、実システムでは定期的にインデックスを再構築するか、非ソートの書込前バッファ(Write-ahead buffer)を持ち、それらをマージする仕組みが必要となります。このマージパターンこそが LSM ツリーが行うことです。

ベンチマーク 種付けデータとして 1 万件・10 万件・100 万件の 3 つのデータセットを用意し、各サーバーに対して wrk を用いて 10 秒間の負荷テストを行いました(スレッド数:4 連、同時接続:50、ランダムな GET リクエストで実 ID のサンプリングリストから選択)。 全てのサーバーは同一のマシン(Apple M1 Mac mini、macOS 15)上で動作しました。Go 1.26、Bun 1.3、Rust 1.94(リリースビルド)を使用しています。 さらに Go において、ディスク上のソート済みファイルへのバイナリサーチ、および

modernc.org/sqlite
を使った SQLite(純粋な Go 実装、CGO なし)のテストも行いました。このバイナリサーチでは固定幅インデックスファイル(エントリあたり 58 バイト:
<uuid>:<offset>
)を用いて O(log n) の
ReadAt
呼び出しを行い、その後該当レコードへと直接 Seek します。データは RAM に読み込ませません。

結果

1 秒あたりの処理依頼数(多いほど良い)

1 万件レコード10 万件レコード100 万件レコード
Go: リニアスキャン7838523
Go: バイナリサーチ (ディスク)45,74241,66138,866
SQLite (Go)26,00025,50725,085
Go: メモリマップ97,04098,27797,829
Bun: リニアスキャン4696119
Bun: メモリマップ106,064107,058105,367
Rust: リニアスキャン2,88325,152-
Rust: メモリマップ163,687155,364169,106

1 依頼あたりの平均レイテンシ(少ないほど良い)

1 万件レコード10 万件レコード100 万件レコード
Go: リニアスキャン84ms552ms1,010ms
Go: バイナリサーチ (ディスク)1.2ms1.4ms1.4ms
SQLite (Go)2.0ms2.0ms2.1ms
Go: メモリマップ497µs571µs584µs
Bun: リニアスキャン101ms754ms1,060ms
Bun: メモリマップ449µs443µs463µs
Rust: リニアスキャン17ms195ms753ms
Rust: メモリマップ231µs482µs221µs

いくつか注目すべき点

  • リニアスキャンは線形に劣化する。 100 万件のレコードでは、Go は 1 秒間に 23 件の依頼しか処理できず、Bun では 1 件あたり平均 1 秒以上を要します。この時点でパフォーマンスチューニングではなく、「なぜページが読み込めないのか」をユーザーに説明する段階です。
  • ディスク上のバイナリサーチは高速でフラット。 1 万件では 4.5 万 req/s、100 万件でも 3.8 万 req/s です。データセットが 100 倍拡大しても性能低下はわずか 15% です。ここで OS のページキャッシュが大きな役割を果たしています:ウォームアップ期間後、1 万件用のインデックスファイル(566KB)は完全にキャッシュに収まります。100 万件ではインデックスが約 55MB になりますが、バイナリサーチの上層階級は常に同じオフセット付近をアクセスするため、どのキーを検索してもそれらのページは「ホット」として維持されます。各検索でインデックスに対し約 20 回の
    ReadAt
    とデータファイルへの 1 回の Seek が発生します。
  • バイナリサーチが SQLite を上回る。 これは予想外です。手動で作成したソート済みファイル+ハンドローされたインデックスは、あらゆるスケールで SQLite の B-ツリーよりも約 1.7 倍高速です。SQLite も単純なプライマリキー読み込みであっても、バイナリサーチよりも多くの処理を行うためです。機能が必要になる際にはそのオーバーヘッドも価値がありますが、純粋な ID 検索のみであれば、使用していない機材分のコストを支払うことになります。
  • SQLite は安定して高速。 データセット規模に関係なく、2.5 万〜2.6 万 req/s、平均レイテンシ 2ms を維持します。B-ツリーインデックスのため、レコード数が 1 万件から 100 万件に増えようとも検索時間の増大は僅かです。
  • メモリマップが天井(上限)です。 あらゆるスケールで 9.7 万 req/s とサブミリ秒のレイテンシを実現。データセットを RAM に収められれば、ディスク上のいずれものもこれに迫ることはできません。
  • Bun (JavaScript) はマップアプローチにおいて Go を上回る。 Bun の HTTP サーバーは平均 10.6 万 req/s against Go の 9.7 万です。Bun は JavaScriptCore を JS エンジンとし、uWebSockets で Zig にてネイティブに HTTP レア実装しているため、libuv を完全にバイパスしています。ここでは言語よりもランタイムが重要になります。
  • Rust はリニアスキャンにおいて著しく勝つ。 1 万件では Rust が 2,883 req/s vs Go の 783 と Bun の 469 です。単純なファイルスキャンでは 3〜6 倍高速で、これは I/O オーバーヘッドの低さと、serde を用いた高速な JSON デシリアライズによるものと思われます。マップアプローチでも Rust が先行していますが、差は著しく縮まっています。

ユースケース別推奨:

ユースケース勝者絶対的な最高スループットRAM に全データをロードせずの最高値後で SQL クエリが必要の場合最速のリリース速度
RRust: メモリマップ (169k req/s)Go: ディスク上のバイナリサーチ (~40k req/s)SQLite (Go) (25k req/s、必要な時に完全な SQL)Go: リニアスキャン (インデックス不要、セットアップなし、コード行数 ~20)

「1 秒あたりに 2.5 万のリクエスト」とは実際何を意味するのか? データベースが必要になる時期について話す前に、これらの数値の文脈を整理しましょう。「1 秒間に 2.5 万リクエスト」という数字は多く響きますが、実際にはその種負荷を生み出すようなプロダクト是什么样的なものかを考える方が重要です。

トラフィックは一様ではありません。ユーザーは昼間起きていて、夜間は寝ています。ウェブアプリケーションの容量計画ガイドでは、B2B および B2C プロダクトにおいてピークと平均の比を 1.5〜2.0 と仮定するのが一般的です(ByteByteGo, Geek Culture)。ここでは 2:1 とします。つまり、平均して全日の 1.25 万 req/s を得ているプロダクトでも、最繁忙時は 2.5 万 req/s にピークに達する可能性があります。

その逆算を行います。アクティブユーザー 1 人あたり時間に約 10 のデータベース検索(プロフィールの読み込みやデータ取得など)を発生させるものとして仮定しましょう(概数であり、アプリによっては異なるかもしれません)。また、最繁忙時においても同時にオンラインな日次アクティブユーザー (DAU) は 10% とします。

ピーク req/s = DAU × 0.10 × (10 検索/時間 ÷ 3600 秒/時間) = DAU × 0.000278

各アプローチを飽和させるための DAU を求めるため、これを裏返します。

アプローチピーク容量飽和させるための DAU
Go: リニアスキャン (1 万件レコード)783 req/s280 万人
Go: バイナリサーチ (ディスク)40,000 req/s1.44 億人
SQLite (Go)25,000 req/s9,000 万人
Go: メモリマップ97,000 req/s3.49 億人
Bun: メモリマップ106,000 req/s3.81 億人
Rust: メモリマップ169,000 req/s6.08 億人

リニアスキャンは、1 万件レコードのファイルに対して約 300 万人の DAU という現実的なプロダクト規模で壊れます。これは意味のある数値です。他のもうひとつ? これらアプローチを押し付けるには何千万という DAU を必要とします。Instagram は 4 億人の DAU でも PostgreSQL を主要データストアとして運用しています(Instagram Engineering)。多くのプロダクトはそのような規模には達しません。

より現実的な例を与えます:月間有料顧客が 1 万人で、各顧客が毎日アプリを一度使用する SaaS では、上記の仮定に基づけばピークは約 3 req/s です。DAU が 10 万人の消費者向けアプリでは、同様の仮定に基づくピークは約 30 req/s です。どちらも我々がテストしたアプローチのいずれにも及んでいません。

「本当にデータベースが必要なのか」という問いへの正直な答は:おそらくまだ必要ではないということです。そして実際に必要になった時には、フラットファイル上で動作する SQLite が単一のサーバーで 9,000 万人の DAU を処理できることを覚えておいてください。

実際にはいつデータベースが必要なのでしょうか? ID による検索の場合:メモリマップが約 9.7 万 req/s、ディスク上のバイナリサーチが約 4 万 req/s、SQLite が約 2.5 万 req/s です。全てのアプローチは単一のサーバーから得られる大多数のアプリケーションを超える能力を持っています。

フラットファイルからの成長が見られるケース:

  • データセットが RAM に収まらない。 メモリマップアプローチは起動時に全データをロードする必要があります。数百万件の小規模なレコードであれば問題ありませんが、数千万件になるとインデックスだけでもギガバイト単位の RAM を必要とします。データをページングする仕組みが必要です。データベースはこの点を手助けてくれます。
  • 1 つのフィールド以上の検索が必要。 現在、高速な操作は「ID での検索」だけです。「ユーザー X の全オーダーを検索する」や「価格が 50 ドル未満の製品を検索する」といった必要があれば、ファイルのスキャンか追加マップの維持が必要になります。そうしたものが 3〜4 つある時点で、クエリエンジンを構築したことになります。
  • 結合 (Joins) の必要性。 オーダー、ユーザー、製品を単一のレスポンスで組み合わせるには、3 つのファイルから読み込み、アプリケーションコード内で結果を組み立てなければなりません。SQL はこれらを効率的に行います。
  • 複数プロセスが同時に書き込む必要がある。 これらのサーバーでの RwLock は 1 プロセス内の同時アクセスを保護します。しかし、サーバーインスタンスを 2 つ起動してそれぞれ独立したメモリマップを持つと、データは乖離します。外部の真理ソースが必要です。それがデータベースです。
  • エンティティ間でのアトミックな書き込みが必要。 オーダーを作成しながら在庫を減らす処理は、双方が成功するか失敗するかどちらかです。別々のファイルでは取引ログ(Transaction log)を実装する必要があります。データベースは ACID 保証でこれを解決します。

これらの制約は多くのアプリケーションには適用されません。社内ツール、サイドプロジェクト、早期段階のプロダクトでは、単一サーバーの RAM に収まらないデータセットを持つことは決してなく、負荷下でもテーブルを超えた結合を行う必要もありませんし、より多くのインスタンスを動かすこともないでしょう。そのようなアプリケーションにとってはこのアプローチは有効です。ファイルが存在するため後から移行したい場合はいつでも可能です。JSONL はあらゆるデータベースへ簡単にインポートできます。ロックインされるわけではありません。

上記には 3 つの言語全てのサーバーコードが埋め込まれています。種付けスクリプト、ベンチマークランナー、および wrk の Lua スクリプトは内行表示されていません。独自に実行するには全プロジェクトをダウンロードしてください:ベンチマークコードのダウンロード (.zip)

アーカイブには

go-server/
bun-server/
rust-server/
seed.ts
、および
run_bench.sh
が含まれています。ベンチマークスクリプトは 3 つのスケールでデータを種付けし、サンプリングされた実 ID を持つ Lua スクリプトを生成し、各サーバーを起動して wrk を実行し、やがて停止します。

クイックスタート:

同じ日のほかのニュース

一覧に戻る →

2026/04/15 3:08

現在、サイバーセキュリティ分野において「証明作業」のような性質を帯びているようです。

## Japanese Translation: Anthropic の Mythos は、重要なソフトウェアメーカーがその能力に対してシステムを強化するまで非公開に保たれた高度な AI モデルであり、「低温くじ(low temperature lottery)」機構を通じてモデルが複雑な企業ネットワーク攻撃を実行できることを示している。この機構では、クリプトカレンシーのプルーフ・オブ・ワークのように、高額なトークン予算によってまれな成功が生じる。AI セキュリティ研究所(AISI)は、Mythos が 10 の試みのうち 3 でシミュレーションされた 32 ステップの企業ネットワーク攻撃を完了したことを確認したが、同様の条件下では Opus 4.6 と GPT-5.4 は失敗した。このタスクは「ラスト・ワンの」シミュレーションとして知られ、人間の完了には約 20 時間が必要と推定されている。特に注目すべきは、Mythos のパフォーマンスは、ランごとのトークン予算を 1 億トークンまで増加させた場合でも低下しなかったことだ。これは、1 つの試行あたり 12,500 ドル、完全な 10 回のラン基準テストスイート全体では 125,000 ドルのコストに対応している。この発見は、まれなセキュリティ監査から、市場価格で取引されるエクスプロイト価値に駆動される継続的なプロセスへの重要な移行を浮き彫りにしている。オープンソースソフトウェアは依然として重要であり、広く使用されているパッケージをクラッキングする方がワンオフの実装よりも攻撃者に高い投資対効果をもたらすためである。したがって、開発者は、専用ハードニングフェーズと常にレビューを行うことを含む新しい 3 フェージサイクルを採用することが推奨される。潜在的には Anthropic の新製品であるコードレビュー製品の活用も可能であり、その価格は 1 つのレビューあたり 15〜20 ドルである。企業は今や、資金が尽きるまで自律的なエクスプロイト特定を制限するトークン予算に対して大規模なリソースを割り当てるという現実と直面している。これとは対照的に、以前はまれで不整合だった慣行は存在しなかった。

2026/04/16 7:27

ターミナル用のページャーを作成しました。

## Japanese Translation: 著者は、ターミナルユーザーインターフェース(TUI)構築の中核エンジンとなる再利用可能な Go のビューポートコンポーネントを開発した。このモジュール化されたシステムは、Kubernetes ログを表示するための **kl** や、Nomad を表示するための **wander**、および `$PAGER` 環境変数を尊重して複数ページの内容を処理するdaily utility **lore**といったツールを稼働させている。アーキテクチャは、リサイズ、スクロール、検索(ショートカット `/`、`r`、`i` に正規表現対応付き)、水平方向のパニング、アイテム選択を含む必須機能をサポートしている。システムは、文字の折り返しおよびセル幅の計算(バイトをグリフに正確にマッピング)を行う `Item` インターフェース、表示向けの `Viewport`、検索機能向けの `FilterableViewport` の 3 つの主要モジュールを通じて、複数行および動的コンテンツを管理する。`MultiItem`のような高度な変種は、行番号などの動的プレフィックスをサポートしており、実装では特殊文字および絵文字の堅牢な描画が保証されている。開発者は、これらのユーティリティをテストまたは実行するために Go または Docker を使用でき、堅牢なコマンドラインアプリケーションの作成を容易にしている。今後の作業には、**libghostty**との統合およびエコシステムのさらなる改善が焦点となる。

2026/04/16 6:21

Excel 向けの ChatGPT

## Japanese Translation: Microsoft は、形式を整えたスプレッドシートや数式を用いた作成・更新を、ゼロから作業を開始する代わりに自然言語で行えるようにするための ChatGPT for Excel のベータ版を発売しました。このアドインは、ユーザーにデータの質問への明確な要約、タブ間での理解とデバッグ、パターン発見、そしてデータを実行可能な洞察への変換などを可能にし、かつ自らの行動を説明し、答えを特定のセルに関連付け、形式を整え、変更を行う前に許可を求めることができます。 このツールは ChatGPT Business、Enterprise、Edu、Teachers および K-12 ユーザー向けに世界中で提供されており、EU 外のプロとプラスユーザーにも利用可能です。インストールはホームからアドインを検索して「ChatGPT」を探し、Excel リボンに追加することで行うことができ、有効化には OpenAI アカウントを使用して ChatGPT Plus、Pro、Business、または Enterprise プランを持つ必要があります。 主要なユースケースとしては、アンケート分析、割引キャッシュフローモデル、ビジネス計画提案、財務諸表の数秒での分析などが挙げられます。将来のアップデートでは一般公開範囲の拡大、数理論理の改善、Slack、GitHub、Google Drive などの外部プラットフォームとのより深い連携が予定されています。この進化は、Excel 内での完全に対話的なデータ操作への重要なステップとなります。

本当にデータベースが必要なのですか? | そっか~ニュース