और तेज़ SQLite की तलाश में
(avi.im)- serverless·edge वातावरण में कई SQLite instances साथ चलाने पर synchronous I/O wait tail latency को बढ़ा देता है, और Helsinki·Cambridge के शोधकर्ताओं ने इसे asynchronous I/O और storage separation से कम करने का प्रयोग किया
- Linux io_uring submission queue और completion queue के ज़रिए I/O request के दौरान भी application को दूसरे काम जारी रखने देता है, जिससे thread blocking कम करने की बुनियाद मिलती है
- SQLite में
sqlite3_step()चलते समय अगर ज़रूरी B-Tree page cache में न हो, तो POSIXread()जैसे synchronous I/O से disk पढ़ी जाती है, और I/O पूरा होने तक thread रुक जाता है - शोधकर्ताओं ने सिर्फ POSIX calls बदलने के बजाय Rust-आधारित rewrite project Limbo में VM और BTree को asynchronous execution model के अनुसार बदला
- benchmark में p999 tail latency अधिकतम 100 गुना कम हुई, लेकिन p90·p99 SQLite के लगभग समान रहे और multi-reader/writer evaluation आगे का काम है
SQLite को और तेज़ बनाने की कोशिश करने वाला शोध
- University of Helsinki और Cambridge के शोधकर्ताओं ने “Serverless Runtime / Database Co-Design With Asynchronous I/O” में SQLite पर asynchronous I/O और storage separation लागू करने के तरीके पर काम किया है
- यही पेपर Rust-आधारित SQLite rewrite project Limbo की नींव बना
- यह workshop paper है, इसलिए आकार में छोटा है, और इसका फोकस serverless और edge computing पर है
- मुख्य बात यह है कि SQLite अपने आप में पहले से तेज़ होने पर भी, multi-tenant वातावरण में tail latency को execution model बदलकर और कम किया जा सकता है
io_uring कैसे I/O wait घटाता है
- Linux kernel का io_uring एक asynchronous I/O interface देता है
- इसका नाम user space और kernel space के बीच साझा होने वाले ring buffer से आया है, जो दोनों के बीच buffer copy overhead को कम करता है
- application I/O request submit करने के बाद, OS के completion signal देने तक दूसरे काम साथ में कर सकता है
- इसका flow इस तरह काम करता है
io_uring_setup()system call से submission queue और completion queue नाम के दो memory areas सेट किए जाते हैं- application submission queue में I/O request डालता है और
io_uring_enter()से OS को processing शुरू करने के लिए बताता है read()औरwrite()की तरह thread को block किए बिना control वापस user space को दे दिया जाता है- application दूसरे काम करते हुए समय-समय पर completion queue को poll करके I/O completion जांचता है
SQLite query execution में synchronous I/O bottleneck
- SQLite application
sqlite3_open()से database file खोलता है, और इस दौरान POSIXopenजैसे low-level OS I/O calls होते हैं sqlite3_prepare()SELECT,INSERTजैसे SQL statements को bytecode instructions की sequence में बदलता हैsqlite3_step()bytecode instructions को तब तक चलाता है जब तक query पढ़ने के लिए row न बना दे या execution पूरा न हो जाए- अगर पढ़ने के लिए row है, तो
SQLITE_ROWलौटाता है - statement पूरा होने पर
SQLITE_DONEलौटाता है
- अगर पढ़ने के लिए row है, तो
- execution के दौरान backend pager को बुलाया जाता है, और tables व rows को दर्शाने वाले B-Tree को traverse किया जाता है
- अगर ज़रूरी B-Tree page SQLite page cache में न हो, तो disk access होता है
- SQLite POSIX
readजैसे synchronous I/O से page contents को disk से memory में पढ़ता है - इस दौरान
sqlite3_step()kernel thread को block कर देता है - I/O wait के दौरान भी concurrent काम करना हो, तो application को ज़्यादा threads इस्तेमाल करने पड़ते हैं
- SQLite POSIX
serverless·edge में SQL embed करने की वजह
- अगर serverless computing edge पर चल रही हो और database cloud वातावरण में हो, तो serverless function और cloud के बीच network round-trip cost लगती है
- data को edge पर साथ में रखना एक तरीका है, लेकिन बेहतर तरीका edge runtime के भीतर database को embed करना बताया गया है
- Cloudflare Workers पहले से इस तरह का model हासिल करते हैं, लेकिन वे KV interface expose करते हैं
- KV हर problem domain के लिए अच्छी तरह fit नहीं बैठता
- tabular data को KV model में map करने से developer experience खराब होता है
- serialization और deserialization cost भी आती है
- SQL ज़्यादा उपयुक्त हो सकता है, और SQLite एक embedded database होने के कारण serverless runtime में सीधे शामिल किया जा सकता है
SQLite को सीधे io_uring पर ले जाना आसान क्यों नहीं है
- SQLite पारंपरिक POSIX
read()औरwrite()पर आधारित synchronous I/O का उपयोग करता है - छोटे applications में यह बड़ी समस्या न हो, लेकिन एक server पर सैकड़ों SQLite databases चलें तो यह bottleneck बन सकता है
- जिन वातावरणों में server resource utilization को अधिकतम करना ज़रूरी हो, वहाँ synchronous I/O एक सीमा बन जाता है
- SQLite में concurrency और multi-tenancy से जुड़ी समस्याएँ हैं
- I/O synchronous और blocking होने से एक ही machine पर applications resources के लिए प्रतिस्पर्धा करते हैं
- नतीजतन latency बढ़ती है
- POSIX I/O calls को io_uring से सीधे बदलना आसान नहीं है
- blocking I/O का उपयोग करने वाले applications को io_uring के asynchronous I/O model के अनुसार redesign करना पड़ता है
- SQLite library को I/O चलने के दौरान application को control वापस दे पाने में सक्षम होना चाहिए
- शोधकर्ताओं ने SQLite के कुछ calls ही बदलने के बजाय, Rust में SQLite को rewrite करके io_uring इस्तेमाल करने का रास्ता चुना
Limbo का asynchronous execution model
- Limbo, SQLite का Rust में rewrite project है, और इसमें VM व BTree components को asynchronous I/O support करने के लिए बदला गया है
- synchronous bytecode instructions को asynchronous counterpart instructions से बदला गया
- उदाहरण के लिए
Nextinstruction cursor को आगे बढ़ाता है और ज़रूरत पड़ने पर अगला page लाता है- पुराने synchronous version में अगर disk I/O होता है, तो page पढ़कर caller को लौटाने तक block होता है
- asynchronous version में
NextAsyncsubmit होने के बाद तुरंत return हो जाता है - caller बाद में block कर सकता है या दूसरा काम कर सकता है
- asynchronous I/O blocking हटाता है और concurrency बेहतर करता है
- resource utilization और बढ़ाने के लिए query engine और storage engine को अलग करने वाला storage separation भी प्रस्तावित है
- इससे जुड़ी व्याख्या के लिए Disaggregated Storage - a brief introduction भी लिंक की गई है
benchmark results और बाकी खुले सवाल
- benchmark multi-tenant serverless runtime को simulate करता है
- हर tenant के पास अपना embedded database होता है
- tenants की संख्या 1 से 100 तक 10 के अंतर से बदली गई
- SQLite में हर tenant के लिए अलग thread इस्तेमाल किया गया, और हर thread पर query चलाकर मापा गया
- चलाई गई query
SELECT * FROM users LIMIT 100थी, जिसे 1000 बार दोहराया गया - Limbo ने भी वही प्रयोग किया, लेकिन Rust coroutines का उपयोग किया
- नतीजतन p999 पर tail latency अधिकतम 100 गुना कम हुई
- SQLite query latency threads बढ़ने के साथ धीरे-धीरे खराब नहीं हुई
- काम अभी जारी है, और paper में कुछ खुले सवाल बाकी हैं
- Future Work में कई readers और writers शामिल करने वाले अतिरिक्त benchmarks की बात की गई है
- फायदे खास तौर पर p999 के बाद ही साफ़ दिखते हैं
- p90 और p99 performance SQLite के लगभग समान हैं
- Limbo का code open source के रूप में उपलब्ध है
- Limbo इस समय आधिकारिक Turso project है, और परिचय लेख भी प्रकाशित है
अभी कोई टिप्पणी नहीं है.