- सभी डेटाबेस आखिरकार फ़ाइल सिस्टम पर संरचित फ़ाइलों का एक संग्रह होते हैं, इसलिए शुरुआती चरण के applications में फ़ाइलों को सीधे मैनेज करके भी पर्याप्त performance हासिल की जा सकती है
- एक ही server को Go, Bun, Rust में implement करके फ़ाइल स्कैन·in-memory map·disk binary search इन तीन approaches की तुलना करने पर, केवल साधारण फ़ाइल access से भी उच्च throughput हासिल किया जा सकता है
- in-memory map approach ने सबसे अच्छा performance (अधिकतम 169k req/s) दिखाया, जबकि SQLite 25k req/s के साथ स्थिर रहा लेकिन उसमें overhead मौजूद था
- ज़्यादातर services SQLite की एक single file से भी 9 करोड़ DAU स्तर तक संभाल सकती हैं, और शुरुआती product stage में अलग डेटाबेस ज़रूरी नहीं होता
- जब dataset RAM से बड़ा हो जाए या join·multi-condition search·concurrent writes·transaction की ज़रूरत पड़े, तब डेटाबेस अपनाने की आवश्यकता होती है
क्या डेटाबेस सच में ज़रूरी है
- डेटाबेस अंततः फ़ाइलों का ही संग्रह है, SQLite एक single file है, जबकि PostgreSQL directory और process से मिलकर बनता है
- सभी डेटाबेस file system पर read/write करते हैं, और यह code में
open() call करने के समान तरीके से काम करता है
- इसलिए असली सवाल “फ़ाइल लिखनी है या नहीं” नहीं, बल्कि “डेटाबेस की फ़ाइल लिखनी है या उसे खुद मैनेज करना है” है
- शुरुआती चरण के कई applications में इसे सीधे मैनेज करके भी पर्याप्त performance पाया जा सकता है
प्रयोग की संरचना
- एक ही HTTP server को Go, Bun(TypeScript), Rust में implement किया गया और दो storage strategies की तुलना की गई
users.jsonl, products.jsonl, orders.jsonl तीन JSONL फ़ाइलों का उपयोग
POST /users से create, GET /users/:id से fetch
- benchmark के लिए केवल read path(GET) को target बनाया गया
-
तरीका 1: हर request पर फ़ाइल पढ़ना
- request आने पर फ़ाइल खोली जाती है, सभी lines scan की जाती हैं, JSON parse करके ID match देखा जाता है
- औसतन फ़ाइल का आधा हिस्सा पढ़ना पड़ता है, इसलिए complexity O(n) है
- data बढ़ने के साथ request processing speed तेजी से गिरती है
-
तरीका 2: सब कुछ memory में load करना
- startup पर पूरी फ़ाइल पढ़कर उसे ID-based hash map में रखा जाता है
- write को map और file दोनों में साथ reflect किया जाता है, और read एक single map lookup से O(1) में होता है
- file persistent storage की भूमिका निभाती है, और map index की
- Go का
sync.RWMutex और Rust का RwLock parallel reads को support करते हैं
-
तरीका 3: disk पर binary search
- सारा data RAM में लाए बिना तेज lookup के लिए एक बीच का समाधान
- ID के आधार पर sorted data file और fixed-width index file (58 bytes/record) बनाई जाती है
ReadAt से index को O(log n) में खोजकर, उस offset से single record पढ़ा जाता है
- नए records जुड़ने पर sorting टूट जाती है, इसलिए समय-समय पर index rebuild या merge करना पड़ता है
- यह merge pattern LSM-tree के काम करने के तरीके से मिलता-जुलता है
benchmark environment
- dataset size: 10k, 100k, 1M records
- load tool: wrk, 10 सेकंड तक 4 threads और 50 concurrent connections के साथ random GET requests
- उसी machine (Apple M1 Mac mini, macOS 15) पर Go 1.26, Bun 1.3, Rust 1.94 से test
- Go में अतिरिक्त रूप से binary search (disk) और SQLite(modernc.org/sqlite) की भी तुलना की गई
मुख्य परिणाम
- linear scan performance में गिरावट: 1M records पर Go 23 req/s, Bun 19 req/s तक बहुत धीमा हो गया
- binary search (disk): 10k~1M record range में 45k→38k req/s, यानी केवल 15% गिरावट
- OS page cache के प्रभाव से index का ऊपरी हिस्सा हमेशा memory में बना रहा
- SQLite: 25k req/s, औसत latency 2ms के साथ consistent performance बनाए रखा
- binary search, SQLite से लगभग 1.7x तेज था, simple PK lookup में SQLite का overhead दिखा
- memory map approach ने सबसे अच्छा performance दिया: 97k~169k req/s, latency 0.5ms से कम
- Bun, Go से तेज निकला: Bun 106k req/s, Go 97k req/s
- Bun, JavaScriptCore + Zig(uWebSockets) पर आधारित है और libuv को bypass करता है
- Rust linear scan में दबदबे वाला रहा: Go से 3~6x तेज, संभवतः JSON parsing और I/O efficiency के कारण
-
उपयोग के हिसाब से सर्वोत्तम विकल्प
- absolute highest throughput: Rust in-memory map (169k req/s)
- RAM में load किए बिना सर्वश्रेष्ठ: Go binary search (~40k req/s)
- SQL चाहिए तो: SQLite (25k req/s)
- सबसे सरल implementation: Go linear scan (~20 lines of code)
25,000 req/s का मतलब
- सामान्य web traffic के लिए peak:average = 2:1 ratio माना गया
- average 12,500 req/s → peak 25,000 req/s स्तर
- माना गया कि active user प्रति घंटे 10 बार fetch करता है, और peak पर concurrent access rate 10% है
- peak request formula: DAU × 0.000278
- हर approach के saturation DAU का हिसाब
- Go linear scan: 2.8M
- Go binary search: 144M
- SQLite: 90M
- Go in-memory map: 349M
- Bun in-memory map: 381M
- Rust in-memory map: 608M
- ज़्यादातर products इन संख्याओं तक पहुँचते ही नहीं हैं
- उदाहरण: 10,000 SaaS customers → 3 req/s, 100,000 DAU app → 30 req/s
- निष्कर्षतः ज़्यादातर शुरुआती products को डेटाबेस की ज़रूरत नहीं होती
- और ज़रूरत पड़े भी तो SQLite की single file से 9 करोड़ DAU तक संभाला जा सकता है
डेटाबेस कब ज़रूरी होता है
-
जब dataset RAM में समा न सके
- कई करोड़ records पर केवल index के लिए भी कई GB चाहिए होते हैं
- data paging की ज़रूरत पड़ती है, जिसे डेटाबेस अपने आप संभालता है
-
जब ID के अलावा दूसरे fields पर query चाहिए
- multi-condition search के लिए file scan या अतिरिक्त maps चाहिए होते हैं
- कई maps बनाए रखना दरअसल खुद का query engine implement करने जैसा है
-
जब join की ज़रूरत हो
- कई फ़ाइलें पढ़कर उन्हें जोड़ना पड़ता है, और SQL अधिक कुशल रहता है
-
जब कई processes एक साथ write करें
- हर instance का in-memory map अलग हो जाता है, जिससे consistency टूटती है
- ऐसे में external single source of truth चाहिए → यही डेटाबेस की भूमिका है
-
जब entities के बीच atomic write चाहिए
- order creation और inventory deduction दोनों के साथ success/failure की गारंटी चाहिए
- इसके लिए अलग transaction log implement करना पड़ेगा, जबकि DB इसे ACID से हल करता है
- जिन internal tools, side projects, शुरुआती products पर ये constraints नहीं हैं
- वे एक single server की RAM में आराम से चल सकते हैं
- JSONL files को बाद में डेटाबेस में आसानी से migrate किया जा सकता है
परिशिष्ट और code उपलब्धता
- Go, Bun, Rust server code शामिल है
- data seed और benchmark run script(
run_bench.sh) अलग से दिए गए हैं
- ZIP file में
go-server/, bun-server/, rust-server/, seed.ts शामिल हैं
- script तीन अलग sizes का data seed करती है, फिर wrk से load test चलाकर बंद हो जाती है
DB Pro से संबंधित जानकारी
-
DB Pro** Mac, Windows, Linux के लिए** एक database client है
- query, browsing, management features एक साथ देता है
- collaborative web platform और built-in AI support देता है
- latest version में Val Town की SQLite database connection support जोड़ी गई है
- v1.3.0 में database creation, multi-query editor, PlanetScale Vitess connection features जोड़े गए हैं
अभी कोई टिप्पणी नहीं है.