42 ポイント 投稿者 GN⁺ 14 일 전 | 26件のコメント | WhatsAppで共有
  • すべてのデータベースは結局 ファイルシステム上の構造化されたファイル群 であり、初期段階のアプリケーションではファイルを直接管理しても十分な性能を確保できる
  • 同一のサーバーを Go、Bun、Rust で実装し、ファイルスキャン・インメモリマップ・ディスク二分探索 の3つのアプローチを比較した結果、単純なファイルアクセスだけでも高いスループットを達成できた
  • インメモリマップ方式が最高性能(最大 169k req/s) を示し、SQLite は 25k req/s で安定しているがオーバーヘッドがある
  • ほとんどのサービスは SQLite の単一ファイルだけでも 9,000万 DAU 規模まで処理可能 であり、初期プロダクト段階では別個のデータベースは不要
  • データセットが RAM を超える、または 結合・複数条件検索・同時書き込み・トランザクション が必要になる時点から、データベースの導入が必要になる

データベースは本当に必要か

  • データベースは結局ファイルの集合 であり、SQLite は単一ファイル、PostgreSQL はディレクトリとプロセスで構成される
    • すべてのデータベースはファイルシステムに対して読み書きを行い、コードで open() を呼ぶのと同じ仕組みで動作する
    • したがって本質は「ファイルを書くかどうか」ではなく、データベースのファイルを書くのか、自分で管理するのか という点にある
    • 初期段階の多くのアプリケーションでは、自前で管理しても十分な性能を確保できる

実験構成

  • 同一の HTTP サーバーを Go、Bun(TypeScript)、Rust で実装し、2つの保存戦略を比較
    • users.jsonlproducts.jsonlorders.jsonl の3つの JSONL ファイルを使用
    • POST /users で作成し、GET /users/:id で取得
    • 取得経路(GET)のみをベンチマーク対象に設定
  • アプローチ 1: リクエストごとにファイルを読み込む

    • リクエスト時にファイルを開き、すべての行をスキャンして JSON をパースした後、ID 一致の有無を確認
    • 平均するとファイルの半分を読む必要があるため、複雑度は O(n)
    • データが大きくなるほど、リクエスト処理速度は急激に低下
  • アプローチ 2: 全体をメモリにロード

    • 起動時にファイル全体を読み込み、ID ベースのハッシュマップ に保存
    • 書き込みはマップとファイルの両方に反映し、読み取りは単一のマップ参照で O(1)
    • ファイルは永続ストレージの役割を担い、マップはインデックスとして機能する
    • Go の sync.RWMutex、Rust の RwLock により並列読み取りをサポート
  • アプローチ 3: ディスク上で二分探索

    • すべてのデータを RAM に載せなくても高速取得を可能にする中間的な解法
    • ID 基準でソートされたデータファイルと、固定幅インデックスファイル(58 バイト/レコード) を生成
    • ReadAt でインデックスを O(log n) で探索した後、該当オフセットから単一レコードを読み込む
    • 新しいレコードを追加するとソート順が崩れるため、定期的なインデックス再生成またはマージが必要
    • このマージパターンは LSM-tree の動作に似ている

ベンチマーク環境

  • データセット規模: 10k、100k、1M レコード
  • 負荷ツール: wrk、10 秒間、4 スレッド・50 同時接続でランダムな GET リクエストを実行
  • 同一マシン(Apple M1 Mac mini、macOS 15)で Go 1.26、Bun 1.3、Rust 1.94 によりテスト
  • Go では追加で 二分探索(ディスク) および SQLite(modernc.org/sqlite) も比較

主な結果

  • 線形スキャンの性能低下: 1M レコードでは Go 23 req/s、Bun 19 req/s まで急激に低下
  • 二分探索(ディスク): 10k〜1M レコードの範囲で 45k→38k req/s と、低下はわずか 15%
    • OS のページキャッシュ効果により、上位インデックス領域は常にメモリ上に維持される
  • SQLite: 25k req/s、平均レイテンシ 2ms で 一貫した性能 を維持
  • 二分探索は SQLite より約 1.7 倍高速 で、単純な PK 取得では SQLite のオーバーヘッドが存在
  • インメモリマップ方式が最高性能: 97k〜169k req/s、レイテンシは 0.5ms 以下
  • Bun は Go より高速: Bun 106k req/s、Go 97k req/s
    • Bun は JavaScriptCore + Zig(uWebSockets) ベースで、libuv をバイパスする
  • Rust は線形スキャンで圧倒的: Go 比で 3〜6 倍高速で、JSON パースと I/O 効率によるものと推定
  • 用途別の最適解

    • 絶対的な最高スループット: Rust インメモリマップ(169k req/s)
    • RAM 非常駐条件で最高: Go 二分探索(約 40k req/s)
    • SQL が必要な場合: SQLite(25k req/s)
    • 最も簡単な実装: Go 線形スキャン(約 20 行のコード)

25,000 req/s の意味

  • 一般的な Web トラフィックは ピーク:平均 = 2:1 の比率を仮定
    • 平均 12,500 req/s → ピーク 25,000 req/s 程度
  • アクティブユーザーが 1 時間あたり 10 回参照し、ピーク時の同時接続率を 10% と仮定
    • ピークリクエストの式: DAU × 0.000278
  • 各アプローチの飽和 DAU の計算結果
    • Go 線形スキャン: 2.8M
    • Go 二分探索: 144M
    • SQLite: 90M
    • Go インメモリマップ: 349M
    • Bun インメモリマップ: 381M
    • Rust インメモリマップ: 608M
  • ほとんどのプロダクトはこの数値に到達しない
    • 例: SaaS 顧客 10,000 人 → 3 req/s、100,000 DAU のアプリ → 30 req/s
  • 結論として ほとんどの初期プロダクトにデータベースは必要ない
    • 必要になったとしても、SQLite の単一ファイルで 9,000万 DAU まで処理可能

データベースが必要になる時点

  • データセットが RAM に収まらないとき

    • 数千万レコードを超えると、インデックスだけでも数 GB が必要
    • データページングが必要になり、データベースがこれを自動で処理する
  • ID 以外のフィールドで検索が必要なとき

    • 複数条件検索では、ファイルスキャンまたは追加マップが必要
    • 複数のマップを維持すると、実質的にクエリエンジンを自作することになる
  • 結合が必要な場合

    • 複数ファイルを読み込んで組み合わせる必要があり、SQL の方が効率的
  • 複数プロセスから同時書き込みが発生するとき

    • 各インスタンスのインメモリマップが分離され、一貫性が失われる
    • 外部の単一の真実のソースが必要になり、それがデータベースの役割となる
  • エンティティ間の原子的な書き込みが必要なとき

    • 注文作成と在庫差し引きの同時成功/失敗保証が必要
    • 別途トランザクションログ実装が必要になり、DB はこれを ACID で解決する
    • こうした制約がない 内部ツール、サイドプロジェクト、初期プロダクト
    • 単一サーバーの RAM 内で十分に動作可能
    • JSONL ファイルは後からデータベースへ 容易にマイグレーション可能

付録およびコード提供

  • Go、Bun、Rust のサーバーコードを含む
  • データシード、ベンチマーク実行スクリプト(run_bench.sh)を別途提供
  • ZIP ファイルには go-server/bun-server/rust-server/seed.ts を含む
  • スクリプトは3つの規模のデータをシードし、wrk で負荷テスト後に終了する

DB Pro 関連案内

  • DB Proは Mac、Windows、Linux 向けのデータベースクライアント

    • クエリ、探索、管理機能を統合提供
    • コラボレーション対応の Web プラットフォームおよび内蔵 AI をサポート
    • 最新バージョンでは Val Town の SQLite データベース接続をサポート
    • v1.3.0 ではデータベース作成、複数クエリエディタ、PlanetScale Vitess 接続機能を追加

26件のコメント

 
happing94 13 일 전

これは何を言ってるんだ
dbは性能のために使うものだと思ってるのか

 
unknowncyder 13 일 전

そうですね。何か新しいインサイトがあるのかと思って原文も見てみたのですが、これがいったい何なのかと……

メモリが高いからディスクを使う話とか、プロダクション運用の安定性のためとか、原子性とか、
そういうベーシックな話で導入するのではなく、いきなり速度比較を始めているので失笑してしまいます。

『うちはDBを売っているけど、DBがいつも必要なわけではありません!』という記事を出しながら、こういうことも平然と言ってしまうあたり、マーケティングがしたいのかと思ってしまいます -_-... 好意的に見ようとしても、たまにシニカルになってしまうというか。

ベンチマークが得られただけでもよしとするしかないですね

 
myc0058 6 일 전

典型的な机上コーディングですね。

 
botplaysdice 13 일 전

とても良い文章だと思います。特にああいう『数字』が入っている資料は貴重です。私たちが作るコードや、持ってきて使う技術スタックにどんなオーバーヘッドがあるのか、『大まかな感覚でも持っている』開発者を見るのが簡単ではない時代ですが、楽しく読みました。

 
foriequal0 12 일 전

私も同感です。Mechanical sympathy や開発の緩急調整に重要な直感を与えてくれる資料だと思います。Latency Numbers Every Programmer Should Know のように。
そして私は、この記事が特定の方向性が無条件に優れていると読めるものではなかったと思いました。むしろ、文中で言及されたすべての方式が示した数値は「ほとんどのビジネスでは余るほど十分な性能」に見えたので、問題の状況に適した方式を選ぼう、という趣旨に読めました。

 
botplaysdice 13 일 전

返信の珠玉のコメントはおまけです。

 
white9s 13 일 전

そうする理由があるなら、検討する必要はあるでしょうね? たとえば性能面の制約が非常に厳しいとか。
でも、ほとんどの場合にこれをあえて選ぶ理由があるでしょうか? DBがもたらす利点がないわけでもないですし……

 
m00nlygreat 13 일 전

ただの発想の転換として読めるだけなのに、みんな敏感ですね

 
tazuya 13 일 전

そうですね。事業の初期に利用者がまだ多くないときは、DBを導入したり複雑にしたりせず、基本的なファイルI/Oだけで事業が軌道に乗るところまで行ける、という提案として受け取ればよいのでは。

 
smash8106 13 일 전

私も同意します。サービスにおいてDBが必要以上に重要視されることがしばしばあり、ときには正規化が崩れると大変なことになるかのように、過剰なくらい設計に投資することもありますよね。
DBを使うなという話ではなく、なぜ使うのか、そしてサービスの根本とは何なのか、という点をあらためて考え直すくらいの受け止め方でも十分に良いと思います。
結局、いつでもバランスが大事ですね。

 
cafedead 13 일 전

SQLiteを本番サーバー向けに選択した瞬間から、いつ乗り換えるべきかを絶えず悩むことになります。
昔はDB自体のコスト(サーバー購入費、IDC、ライセンス費用など)が高かったので悩む価値がありましたが、
最近はいわゆるワンクリックで構築できるのに、本当に悩む必要があるのでしょうか?

 
roxie 8 일 전

今でもDBは高いですからね

 
csjune 12 일 전

もちろん「初期段階のプロジェクトや規模の小さいアプリケーション」なら、DBが不要なこともあるでしょう。DBだけでなく、ほかの要素も適当に何でもよかったりします。問題はスケールが大きくなるときです。まあ、ただ面白半分で数値を見ているだけの文章ですね。

 
carnoxen 13 일 전

https://hackers.pub/@gnh1201/2025/…

ときには、別途データベースをインストールする必要がない場合もあります。Windows限定ですが...

 
roxie 8 일 전

タイトルを見て思わず吹き出しましたね

 
kuthia 13 일 전

主要なエンティティが本当にRDBMSを通じて永続性を保証しなければならないのか、と時々考えます。SSOTを提供するための代替技術(?)もかなりありますから。

 
neptune 13 일 전

SQLite が壊れたら打つ手なし…。

 
okxrr 13 일 전

sqlite が壊れるケースはありますか? 気になります。異常なファイル移動や削除は除いて。

 
GN⁺ 14 일 전
Hacker Newsの意見
  • この記事は本当に気に入った。コンピュータがどれだけ速いかをよく示している
    ただし最後の結論には同意しない。著者は「複数のプロセスが同時に書く必要がある」という制約の多いアプリには当てはまらないと言っていたが、実際には初期段階のプロダクトでも cron や message queue のような別ワーカーが同時に書き込む必要があることは多い
    メインサーバーだけが書くように作ることもできるが、それはアーキテクチャの複雑さを高める
    だから純粋なスケールの観点では著者の言う通りだと思うが、もう少し広く見ればデータベースを使う方がよいと思う。特に SQLite は妥当な選択だ
    スケールが必要なら頻繁にアクセスするデータをメモリにキャッシュすればよい。私が使っている組み合わせは SQLite + インメモリキャッシュだ

    • 私も似たような状況をよく経験する。サーバー1台で十分だとしても、サーバー冗長化が必要になった瞬間にネットワークストレージが必要になり、結局はネットワーク経由でアクセスできる DB に傾く
      S3 がたまに使えることはあるが、それでも完全な代替として使うには制約が多い
    • 最近は新しいプロジェクトを始めるとき、基本的に SQLite を使っている。性能が非常に高く、後で規模が大きくなったら Postgres に移行するのも簡単だ
      別の DB サーバーを管理したりバックアップしたりする必要がなく、ずっとシンプルで安価だ
    • Rust 1M ベンチマークを見て、コンピュータがどれほど速いのかを改めて実感した
  • SQLite は本当に好きだが、すべての問題の答えではないことも分かった
    クライアント側の辞書アプリを作る際に SQLite の wasm ポートを使ってみたが、DB ファイルが予想より大きく、圧縮もあまり効かず、読み込みも遅かった
    最終的には元の TSV ファイルから直接インデックスを作って zstd で圧縮し、wasm で毎回展開する方式に変えた。これが SQLite よりずっと速かった
    モジュールサイズも 800KB から 52KB に減り、複数インスタンスを同時に立ち上げても負担がなかった
    文字列検索には stringzilla を使ったが、とんでもなく速い
    SQLite は素晴らしいが、あらゆる状況の正解ではない

  • SQLite ベンチマークは最適化が足りていない
    単に

    db.SetMaxOpenConns(runtime.NumCPU())
    db.SetMaxIdleConns(runtime.NumCPU())
    

    これを追加するだけでも、私のマシンでは性能が 27,700 r/s から 89,687 r/s に跳ね上がった
    prepared statement や timestamp の int 化も試したが、大きな違いはなかった

  • 記事自体は悪くなかったが、「すべての DB はファイルシステムに open() でアクセスする」という部分は正確ではない
    SQLite のようなアプリは mmap を使ってファイルをメモリ空間に直接マップする。この方式なら syscall を飛ばしてはるかに高速にアクセスできる
    記事の後半ではファイル全体をメモリに読む過程を説明していたが、mmap を使っていればもっとよかったと思う

    • 記事が DB の IO を単純化して説明していたのは確かだ
      ただ、mmap が常に優れているとまでは言いにくい。OS API に依存するより、アプリケーションロジック側で直接処理したい人もいる
      関連論文は CMUの mmap 研究 を参照
    • mmap が使うバックエンドストアも結局はファイルシステム上のファイルだ
      「open() のように動作する」という表現はやや単純化ではあるが、技術的には間違っていない
  • 昔、Perl で小さな販売用 Web アプリを作ったことがあるが、ISP のサーバーには何もインストールできなかったので、ファイルベースのハッシュを使っていた
    クライアントは 20年以上それをそのまま使い続け、その後亡くなり、家族が引き継いで Wordpress に切り替えた
    最後に確認したときには注文数が数十万件だったが、それでも性能は問題なかった
    ハードウェアの進歩のおかげで、このハック的な構成は予想以上に長く持ちこたえた。今なら SQLite でも十分だったと思う

    • どんな製品を売っていたサイトだったのか気になる
  • ストレージを自前で実装してみると、DB がどう動くのか理解できる
    インデックスやデータ構造を効率よく扱わなければならず、結局「おもちゃでないなら最初から DB を使うべきだった」という結論に至る

  • Relational Databases Aren’t Dinosaurs, They’re Sharks
    小さなアプリで得られるわずかな利得よりも、車輪の再発明に費やす時間の無駄の方がはるかに大きい

    • サメ vs 恐竜の比喩は本当に的確だ
      白亜紀の時代、サメはすでに今とほぼ同じ形をしており、その後も大きく変わらず生き残ってきた
      一方で恐竜や翼竜、モササウルスは消えたが、サメ・ワニ・大型のヘビは最適化された設計のおかげで今までほぼそのまま存在している
      関係データベースもそういう存在だと思う
  • こういう記事を読むのは楽しい
    それでも私は依然として 99% のケースで SQL とトランザクションのある DB を使う
    ただ最近の個人プロジェクトでは、YAML ファイルベースの単純なファイルシステムでデータを管理してみたが、自分の規模では性能上の問題はまったくなかった
    人間が読めて diff できることの方が、性能より重要だった
    それでも大半のケースでは、クエリ言語と保証された一貫性を持つ DB を選ぶと思う

  • 結局はいつも DB の機能と ACID 保証が必要になる
    たまにレガシーなフラットファイルストアを使わなければならないたびに、一貫性・トランザクション・クエリ言語を無理やり付け足す羽目になる。結局は車輪の再発明だ

  • 原子性が必要になる瞬間、DB は不可欠だ
    ファイルシステム上で原子的な書き込みを実装するのは非常に脆い
    こうした理由で、多くの DB がクラッシュ時のデータ破損問題に悩まされる。以前は Windows 上の RocksDB がそうだった

    • ファイルに原子的な変更が必要なら、私なら素直に SQLite を使う
      自前で実装するのは正気の沙汰ではないと感じる。OS API で安全に書く方法を学ぶのは良いことだが、今ではそれはあまりにニッチな技術
      しかも後任がそれを保守できない可能性が高い。結局 DB に置き換えることになる
    • 記事のコードは、いつか停電でも起きたら空ファイルになるだろう
      少なくとも同じファイルシステム内の一時ファイルに書き、fsync 後に rename で置き換えるべきだ
    • 単純なケースなら、そこまで脆くはない
      DB 全体を一時ファイルに書き、flush 後に move で置き換えれば Unix では原子的だ
      ただしこれはまったくスケールしない。小さな更新でもファイル全体を書き直す必要があり、ロック管理も必要になる。ACID の一部しか解決しない
    • こうして見ると、すでに ACID の A を扱っていることになる
      ちなみに OLAP DB の DuckDB は out-of-core ワークロードでも素晴らしく動作する
    • 2025年時点で Linux + ext4 は単一および複数ブロックの原子的書き込みをサポートしている
      公式ドキュメントへのリンク
 
mstorm 13 일 전

冷蔵庫なしでも暮らせますが、不便ではありますよね。
使える冷蔵庫があるのに、使わない理由はありません。

 
foobarman 12 일 전

ノラさんはイルベユーザーなんですか

 
alfenmage 5 일 전

違うと言ったらみんなイルベなのか? 俺、慶尚道の人間なんだけど?

 
foobarman 5 일 전

通報したいのですが、通報のやり方が分かりません。ああ。

 
okxrr 13 일 전

このコメントは、韓国の開発者たちの閉じた考え方とGeekNewsの水準を物語っているように見えます。

 
alfenmage 5 일 전

そのレベルというのが具体的にどの程度のレベルなのか、お前がそのレベルだと評価した理由は何なのか、論理・ファクト・科学・統計のうち2つ以上を使って話してみろ うんうん

 
foobarman 5 일 전

はは、単語を見ただけでもディシ、イルベ、ペムコ系の人ですね。気にしないでください。