• 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 में न हो, तो POSIX read() जैसे 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 खोलता है, और इस दौरान POSIX open जैसे 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 लौटाता है
  • 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 इस्तेमाल करने पड़ते हैं

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 से बदला गया
  • उदाहरण के लिए Next instruction cursor को आगे बढ़ाता है और ज़रूरत पड़ने पर अगला page लाता है
    • पुराने synchronous version में अगर disk I/O होता है, तो page पढ़कर caller को लौटाने तक block होता है
    • asynchronous version में NextAsync submit होने के बाद तुरंत 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 है, और परिचय लेख भी प्रकाशित है

अभी कोई टिप्पणी नहीं है.

अभी कोई टिप्पणी नहीं है.