3 पॉइंट द्वारा GN⁺ 3 시간 전 | 2 टिप्पणियां | WhatsApp पर शेयर करें
  • SQLite स्थानीय स्टोरेज और embedded projects में व्यापक रूप से इस्तेमाल होने वाला self-contained RDBMS है, लेकिन backward compatibility के कारण यह सुरक्षा और performance के लिए नुकसानदेह पुराने default settings बनाए रखता है
  • डिफ़ॉल्ट रूप से यह foreign key constraints को enforce नहीं करता और ROWID को दोबारा इस्तेमाल कर सकता है, जिससे data integrity की समस्या पैदा हो सकती है जहाँ हटाए गए user की posts उसी ID पाने वाले किसी दूसरे user से जुड़ जाती हैं
  • सामान्य tables घोषित columns से अलग data types भी स्टोर कर लेते हैं, और STRICT tables इसे रोकते हैं, लेकिन हर table पर अलग से strict लिखना पड़ता है और यह custom type names इस्तेमाल करने वाले तरीकों से टकराता है
  • concurrent writes में तुरंत आने वाला SQLITE_BUSY, बंद पड़ा WAL, और conservative synchronization settings को क्रमशः busy_timeout, journal_mode, synchronous pragma से सुधारा जा सकता है
  • अगर PRAGMA edition = 2026 जैसी year-based editions के जरिए सुरक्षित default settings के bundle चुने जा सकें, तो SQLite बिना पुराने behavior को तोड़े अपने defaults को आगे भी बेहतर बना सकता है

SQLite की ताकत और पुराने defaults

  • SQLite embedded projects के लिए database और local data storage का लगभग industry standard है, और कुछ server software जैसे lobste.rs भी SQLite पर चल रहे हैं
  • पारंपरिक RDBMS की तरह अलग process के रूप में चलने के बजाय यह library-form RDBMS है, इसलिए software को self-contained रखा जा सकता है
  • सामान्य file format की तरह custom serializer और parser लिखने की ज़रूरत नहीं पड़ती, इसलिए यह RDBMS और file-based storage दोनों के फायदे देता है
  • लेकिन data integrity, concurrency, और performance को प्रभावित करने वाले कई defaults अनुपयुक्त बने हुए हैं

foreign keys को enforce न करने से पैदा होने वाली integrity problems

  • foreign keys database consistency बनाए रखने और broken references रोकने का मुख्य तरीका हैं, लेकिन SQLite इन्हें डिफ़ॉल्ट रूप से enforce नहीं करता
  • भले ही posts.user_id को users.id refer करने के लिए declare किया गया हो, default setting में यह निम्न behavior को नहीं रोकता
    • valid user ID के बिना post बनाना
    • उस user की posts छोड़े रखते हुए user को delete करना
  • INTEGER PRIMARY KEY table के ROWID का alias बन जाता है, और SQLite का ROWID allocation algorithm कुछ स्थितियों में IDs को reuse करता है
    • अगर Bob ने ID 1 से post लिखी और बाद में सिर्फ account delete हो गया, तब भी foreign key error नहीं आता
    • बाद में Alice को वही reused ID 1 मिल जाए, तो Bob की पुरानी posts Alice से जुड़ जाती हैं
    • query सामान्य रूप से सफल हो जाती है, इसलिए यह साधारण broken reference की तुलना में पकड़ना और मुश्किल होता है
  • हर connection पर नीचे वाला pragma चालू करने से foreign keys enforce किए जा सकते हैं
PRAGMA foreign_keys = ON;
  • अगर यह शुरुआत से लागू होता, तो posts छोड़कर user delete करने की कोशिश FOREIGN KEY constraint failed error के साथ रुक जाती

घोषित type से अलग data types की अनुमति देने वाली सामान्य tables

  • SQLite values NULL, INTEGER, REAL, TEXT, BLOB में से एक होती हैं, लेकिन सामान्य tables में column declaration स्टोर होने वाले type को सख्ती से सीमित नहीं करती, बल्कि type affinity तय करती है
  • INTEGER affinity वाले column में नीचे वाले storage rules लागू होते हैं
    • ऐसा TEXT जिसे integer की तरह समझा जा सके, INTEGER में convert हो जाता है
    • ऐसा TEXT जिसे floating-point number की तरह समझा जा सके, double-precision REAL में convert हो जाता है
    • बाकी values अपनी मूल type में ही store होती हैं
  • दूसरी affinities के लिए भी अलग conversion rules हैं
    • BLOB affinity value को ज्यों का त्यों store करती है
    • TEXT affinity BLOB, TEXT, NULL को वैसे ही रखती है और numbers को TEXT में convert करती है
    • REAL affinity INTEGER affinity की तरह काम करती है, लेकिन integers को REAL में बदल देती है
  • इसी वजह से duration_sec INTEGER column में 'Way too long, I mean come on' जैसी non-numeric string भी store की जा सकती है
  • वास्तविक projects में boolean के लिए numeric 1, 0 की जगह string '1', '0' लिखे जाने के मामले भी मिले, जिनके कारण data cleanup और debugging करनी पड़ी

STRICT tables और custom types का टकराव

  • STRICT tables में अगर column में गलत data type डालें, तो cannot store TEXT value in INTEGER column जैसी type error आती है
CREATE TABLE music (
  id INTEGER PRIMARY KEY,
  name TEXT,
  duration_sec INTEGER
) strict;
  • सभी tables को globally strict बनाने के लिए कोई pragma नहीं है, इसलिए हर CREATE TABLE statement में strict जोड़ना पड़ता है

  • SQLite का रुख है कि flexible typing उपयोगी है, लेकिन STRICT tables में भी ANY data type लिखकर हर तरह की values रखने वाला column बनाया जा सकता है

    • यानी किन columns में arbitrary types चलेंगे और किनमें specific type enforce होगा, यह साफ़ तौर पर अलग किया जा सकता है
  • ज़्यादा व्यावहारिक compatibility problem यह है कि STRICT tables सिर्फ types enforce नहीं करतीं, बल्कि type specifier interpretation rules भी बदल देती हैं

  • सामान्य tables type names को कैसे interpret करती हैं

    • सामान्य SQLite tables declared type string के आधार पर affinity तय करती हैं
    • अगर "INT" शामिल है, तो उसे INTEGER माना जाता है
    • अगर "CHAR", "CLOB", "TEXT" में से कुछ शामिल है, तो वह TEXT बनता है
    • अगर "BLOB" शामिल है या type छोड़ा गया है, तो उसे BLOB माना जाता है
    • अगर "REAL", "FLOA", "DOUB" में से कुछ शामिल है, तो वह REAL बनता है
    • अगर कोई शर्त लागू न हो, तो उसे NUMERIC माना जाता है
    • loose typing और इन rules को मिलाकर columns पर DATETIME, KEY_VALUE_SET, COLOR जैसे custom type names लगाए जा सकते हैं
    • database connectors या wrappers type name देखकर serialization और deserialization अपने-आप कर सकते हैं
    • भले ही अलग processing feature न हो, type name अपने आप में column के expected data को document करता है
    • अगर STRICT को उसी रूप में default बना दिया जाए, तो यह उपयोग का तरीका छोड़ना पड़ेगा, इसलिए explicit custom type aliases की ज़रूरत होगी
    CREATE TYPE KEY_VALUE_SET = TEXT;
    
    • अगर strict table में KEY_VALUE_SET जैसा alias इस्तेमाल किया जा सके, तो schema में यह भी दर्ज किया जा सकेगा कि TEXT column में application द्वारा parse किया जाने वाला कौन-सा expected data pattern है
    • custom types से CHECK constraints जोड़ने की सुविधा भी उपयोगी होगी
    • SQL:1999 standard में constraints तक समर्थित type alias syntax CREATE DOMAIN पहले से मौजूद है, इसलिए SQLite इसे इसी standard statement के रूप में implement कर सकता है

concurrent writes और SQLITE_BUSY

  • SQLite कई concurrent reads की अनुमति देता है, लेकिन writes एक समय में सिर्फ एक ही संभालता है
  • default setting में जब दो processes एक साथ write lock लेने की कोशिश करते हैं, तो उनमें से एक बिना इंतज़ार किए तुरंत SQLITE_BUSY error पाता है
    • disk I/O करने वाली write मूल रूप से धीमी हो सकती है, इसलिए कुछ समय lock release होने का इंतज़ार करना ज़्यादा स्वाभाविक है
    • ऐसे मामले भी रहे हैं जहाँ production system बीच-बीच में रुक जाता था और manually retry loop लिखना पड़ा
  • नीचे वाली setting अधिकतम 5 seconds तक lock लेने की retry करती है, और उसके बाद ही SQLITE_BUSY लौटाती है
PRAGMA busy_timeout = 5000;
  • सामान्य संचालन में सभी writes को एक ही process, और संभव हो तो एक ही thread में समेटना बेहतर है; concurrent writes तेज़ नहीं हो सकतीं
  • फिर भी concurrent writes की ज़रूरत वाले वैध operational cases मौजूद हैं
    • sqlite3 command-line tool से database को interactively साफ़-सुधारना
    • अलग frontend के बिना rare admin tasks के लिए scripts चलाना
  • ऐसी operations default settings की वजह से अनपेक्षित SQLITE_BUSY पैदा कर सकती हैं और चल रहे software को रोक सकती हैं

WAL और performance defaults

  • SQLite सही configuration के साथ इतना तेज़ हो सकता है कि PostgreSQL या MySQL जैसे बड़े servers के कुछ काम भी संभाल सके, लेकिन इसका default performance अच्छा नहीं है
  • server environment की detailed tuning के लिए Optimizing SQLite for servers देखा जा सकता है
  • सबसे महत्वपूर्ण default problem यह है कि Write-Ahead Log, यानी WAL बंद रहता है
PRAGMA journal_mode = WAL;
  • WAL अधिकांश स्थितियों में write speed को काफी बढ़ाता है
  • नीचे वाली setting को साथ में इस्तेमाल करने पर data corruption का जोखिम बढ़ाए बिना disk sync की संख्या बहुत कम की जा सकती है
PRAGMA synchronous = NORMAL;

year-based SQLite editions

  • अगर मौजूदा defaults को अभी सीधे बदल दिया जाए, तो पुराना software टूट सकता है और users आगे के SQLite upgrades से डरने लगेंगे
  • backward compatibility बनाए रखते हुए बेहतर defaults चुनने के लिए एक super pragma जोड़ा जा सकता है
PRAGMA edition = 2026;
  • 2026 edition कम-से-कम नीचे वाली settings के alias की तरह काम करेगा
PRAGMA foreign_keys = ON;
PRAGMA busy_timeout = 5000;
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
  • इस edition में नए tables के लिए strict mode भी default के रूप में लागू होगा
  • यह Rust editions की तरह है, जहाँ पुराने code का behavior बचाए रखते हुए नए default behavior को explicit तरीके से चुना जाता है
  • JavaScript के "use strict"; जैसे single mode के विपरीत, year-based editions समय के साथ sensible default bundles बदलने की सुविधा देती हैं
  • उदाहरण के लिए अगर Hctree की WAL2 2034 में main branch में आ जाए, तो भविष्य का PRAGMA edition = 2034 इस तरह configure किया जा सकता है कि वह journal_mode = WAL2 चुने
  • edition system ऐसा समझौता बन सकता है जिसमें पुराने programs का default behavior टूटे बिना SQLite अपने safety और performance defaults को आगे बढ़ा सके

2 टिप्पणियां

 
tomskang 2 시간 전

सिद्धांत रूप में यह अच्छा लगता है, लेकिन compatibility conflicts और overhead को देखते हुए मुझे लगता है कि इससे नुकसान ही होगा।
SQLite तो ऐसा DBMS है जो embedded environments, जैसे Aegis, में चलने के लिए पैदा हुआ था।
void** को धड़ाधड़ इस्तेमाल करना और __weak__ घोषित करने के बाद functions को override करना—यही embedded दुनिया है। ऐसी दुनिया में इसे जानबूझकर क्यों लाना? यही ख्याल आता है।

 
GN⁺ 3 시간 전
Lobste.rs की रायें
  • कल यह खबर देखकर कि lobste.rs ने SQLite पर स्विच किया है (https://lobste.rs/s/ko1ji1/lobste_rs_is_now_running_on_sqlite), मैंने यह पोस्ट लिखी
    उस समय की चर्चा पढ़ते हुए SQLite के defaults की छोटी-छोटी असुविधाएँ याद आईं, इसलिए उन्हें ब्लॉग पोस्ट में समेट दिया

    • मैंने भी वह चर्चा पढ़ी और उसी निष्कर्ष पर पहुँचा
      अगर क्षमता हो तो यह पोस्ट SQLite forum पर भी साझा करने लायक है: https://sqlite.org/forum/forum
  • SQL में CREATE TYPE का उपयोग composite type बनाने के लिए होता है, और Postgres इसमें range, enum, array, यहाँ तक कि काफ़ी जटिल base type भी support करता है
    जो फ़ीचर चाहिए वह CREATE DOMAIN के ज़्यादा क़रीब है। इससे base concrete type पर newtype बनाया जा सकता है, और default value तथा constraints भी तय किए जा सकते हैं, जिससे कई tables में एक ही CHECK constraint दोहराने वाली schema काफ़ी संक्षिप्त हो जाती है। उदाहरण के लिए, create domain mything as text; या create domain mything as integer not null check (VALUE < 5); जैसा लिखा जा सकता है

    • पहले यह जाँच लेना चाहिए था कि क्या पहले से type alias feature मौजूद है। मैं पोस्ट में इससे जुड़ी बात जोड़ दूँगा
  • यह काफ़ी ख़तरनाक trap भी कवर किया जाना चाहिए कि सिर्फ busy_timeout से तुरंत आने वाली SQLITE_BUSY error नहीं रुकती
    अगर read-only query से transaction शुरू करने के बाद lock को upgrade करने के लिए write query चलाई जाए और उसी समय कोई दूसरी write operation मौजूद हो, तो busy_timeout setting की परवाह किए बिना वह तुरंत fail हो जाती है। इसका समाधान begin immediate का उपयोग करना है

    • मुझे पता है कि यह असल bug नहीं है, लेकिन सच कहूँ तो यह लगभग bug जैसा लगने वाला behavior है
  • यह भी ध्यान में रखना चाहिए कि प्रस्तावित edition से बँधी कुछ settings, SQLite model में connection properties हैं, जबकि बाकी database file या individual table की properties हैं
    PRAGMA journal_mode = WAL database file में बना रहता है, लेकिन PRAGMA foreign_keys = ON हर connection पर अलग से सेट करना पड़ता है। STRICT individual table की property है, इसलिए एक ही database में strict tables और non-strict tables को मिलाकर रखा जा सकता है। इसलिए तय करना होगा कि इस unified PRAGMA को database file की property माना जाए या connection की। यह भी जानने की जिज्ञासा है कि क्या कोई built-in mechanism है जो पुरानी library को ऐसी file बदलकर खराब करने से चेतावनी दे, जिसमें ऐसे features शामिल हों जिन्हें वह पहचानती नहीं

  • Edition approach मूल रूप से SQLite developers के backward compatibility बनाए रखने के लक्ष्य और आधुनिक recommended settings चाहने वाले users के लिए कम confusing defaults के बीच एक अच्छा compromise लगता है

  • हाल में मैं RocksDB में काफ़ी दिलचस्पी ले रहा हूँ। यह भी SQLite की तरह embedded database library है, लेकिन मेरे हिसाब से इसमें SQL न होना ही एक बड़ा फ़ायदा है
    हर चीज़ को SQL में व्यक्त करना पड़े, यह बंधन database के साथ काम करने में सिर्फ़ रुकावट पैदा करता है

    • RocksDB एक key-value database है और SQLite एक relational database, इसलिए मेरा मानना है कि दोनों अलग उद्देश्यों के लिए बने अलग concepts हैं
      lobste.rs जैसे SQL इस्तेमाल करने वाले system को RocksDB या किसी और key-value database पर कैसे ले जाया जाएगा, यह कल्पना करना मुश्किल है
  • मैं इस बात से सहमत नहीं हूँ कि busy_timeout सेट करना ही समाधान है। SQLite, अगर single write thread रखकर आपस में असंबंधित logical transactions को batch किया जाए, तो बेहद शानदार performance देता है
    इसका एक ठोस तरीका https://andersmurphy.com/2025/12/… में समझाया गया है

    • सामान्य workload में write operations को एक तक सीमित रखना और ऐसा ढाँचा न बनाना बेहतर है जहाँ कई write operations लगातार आपस में compete करें
      लेकिन maintenance के लिए database को सीधे modify करना, या ऐसे छिटपुट admin tasks को script से चलाना भी आम बात है जिनके लिए अलग management UI बनाना ज़रूरी नहीं होता। ऐसे में सिर्फ़ bad luck से execution timing टकरा जाने पर server या application का random तरीके से रुक जाना अच्छी बात नहीं है