ଆମେ ଏପର୍ଯ୍ୟନ୍ତ ଡାଟାବେସ୍ କାହିଁକି ବ୍ୟବହାର କରୁନାହୁଁ
ଆମ iPad ହାତଲେଖା ଆପ୍ ପାଇଁ Turso/libSQL ମୂଲ୍ୟାୟନ କରି, ସବୁକିଛି ମାପି, ଆମେ flat snapshot ଫାଇଲ୍ଗୁଡ଼ିକ ବାଛିଲୁ। ଏହି କାର୍ଯ୍ୟଭାରକୁ ଡାଟାବେସ୍ ଦରକାର ନାହିଁ—ଏବଂ ପ୍ରତ୍ୟେକ ପଦକ୍ଷେପ କେବଳ ପୂର୍ବରୁ ଥିବା ସମସ୍ୟା ପାଇଁ ଖର୍ଚ୍ଚ କରିବା ଉଚିତ।
Lulucat Notes iPad ପାଇଁ ଏକ handwriting app। ଗତ ସପ୍ତାହ ପର୍ଯ୍ୟନ୍ତ ଏଥିରେ ଗୋଟିଏ canvas ଥିଲା ଏବଂ ଦ୍ୱିତୀୟ note ବୋଲି କୌଣସି ଧାରଣା ନଥିଲା। ଏବେ ଆମେ ଏକ note library ଯୋଡ଼ିବାକୁ ଯାଉଥିଲୁ—ଅନେକ document, ପ୍ରତ୍ୟେକରେ ଅନେକ page—ଏବଂ ପ୍ରଥମ architectural ପ୍ରଶ୍ନ ଥିଲା storage।
ଡାଟାବେସ୍ ଏକ ସ୍ୱାଭାବିକ ଉତ୍ତର ଭଳି ଲାଗିଲା। Notes app ଗୁଡ଼ିକ structured data ସଞ୍ଚୟ କରେ। Structured data ଡାଟାବେସ୍ରେ ଯାଏ। ଆମେ Turso ଏବଂ ଏହାର Swift SDK ମୂଲ୍ୟାୟନ କଲୁ, iOS simulator ରେ ଏହାକୁ ଚଲାଇଲୁ, ବାସ୍ତବ stroke data ର benchmark କଲୁ, ଏବଂ ଶେଷରେ ଏହାକୁ ବ୍ୟବହାର ନକରିବାକୁ ବାଛିଲୁ।
ତାହାର ବଦଳରେ ଆମେ flat files ବାଛିଲୁ। ଆମେ କ’ଣ ପାଇଲୁ ଏବଂ ଏହି ନିଷ୍ପତ୍ତି କାହିଁକି ନେଲୁ, ତାହା ଏଠାରେ ଅଛି।

Gabriel Coxଙ୍କ ଫଟୋ Unsplashରେ। Unsplash ଲାଇସେନ୍ସ।
ଆପ୍ଟି ବାସ୍ତବରେ ଡାଟା ସହ କ’ଣ କରେ
ଏକ handwriting app ର data access pattern ସଂକୀର୍ଣ୍ଣ ଏବଂ ପୂର୍ବାନୁମାନଯୋଗ୍ୟ। ପଢ଼ିବାର ଅର୍ଥ ହେଉଛି ଗୋଟିଏ page ଖୋଲି, ତାହାର ପ୍ରତ୍ୟେକ element—ସମସ୍ତ stroke, ସମସ୍ତ image—ଏକାଥରେ memory ରେ load କରିବା। Canvas ସବୁକିଛି ଧରିଥାଏ; ଏହା କେବେ partial query ଚଲାଏ ନାହିଁ। ଲେଖିବାର ଅର୍ଥ ହେଉଛି ଗୋଟିଏ pen stroke ସମାପ୍ତ କରି page ରେ ଗୋଟିଏ element append କରିବା। ବିରଳ ସମୟରେ user ଗୋଟିଏ stroke ର କିଛି ଅଂଶ ମୋଛେ, selection କୁ ଘୁଞ୍ଚାଏ, କିମ୍ବା କିଛି delete କରେ, କିନ୍ତୁ ସେଗୁଡ଼ିକ ମଧ୍ୟ single-page, single-element operation।
Concurrent access ନାହିଁ। ଏକ ସମୟରେ ଜଣେ ଲୋକ ଗୋଟିଏ document ର ଗୋଟିଏ page ରେ ଲେଖନ୍ତି। Cross-document search ମଧ୍ୟ ନାହିଁ—note library କୁ ପ୍ରତ୍ୟେକ document ପାଇଁ କେବଳ title, timestamp, page count ଏବଂ cover thumbnail ଦରକାର; page content ପଢ଼ିବା ଆବଶ୍ୟକ ନୁହେଁ।
Queries, indexes ଏବଂ concurrency coordination ପାଇଁ ହିଁ ଡାଟାବେସ୍ ତିଆରି। ଆମ app ଏହି ତିନୋଟିରୁ କିଛି ମଧ୍ୟ ବ୍ୟବହାର କରେ ନାହିଁ।
Turso ମୂଲ୍ୟାୟନ
ଆମେ libsql-swift ମୂଲ୍ୟାୟନ କଲୁ, ଯାହା Turso ର libSQL engine ପାଇଁ official Swift SDK।
SDK କାମ କରେ। ଏହାର ସମସ୍ତ 9ଟି test case pass କରେ। ଆମେ ଏହାକୁ app ର ଏକ copy ଭିତରେ integrate କଲୁ, iOS simulator ପାଇଁ build କଲୁ, launch କଲୁ ଏବଂ app sandbox ରେ local database ସୃଷ୍ଟି କଲୁ। ଗୋଟିଏ transaction ରେ 100ଟି stroke, ପ୍ରତ୍ୟେକରେ 3400ଟି sampling point—ମୋଟ 4,080,000 bytes BLOB data—ଲେଖିଲୁ। ଆମ development Mac ରେ ଏଥିପାଇଁ ପ୍ରାୟ 0.019s ଲାଗିଲା।
PRAGMA wal_checkpoint(TRUNCATE) ଚଲାଇବା ପରେ WAL file ଶୂନ୍ୟକୁ ସଙ୍କୋଚିତ ହେଲା। ତା’ପରେ ଆମେ କେବଳ ମୁଖ୍ୟ .db file କୁ ଅନ୍ୟ ସ୍ଥାନକୁ copy କରି, ଏହାକୁ ଖୋଲି, ସମସ୍ତ data ପୁଣି ପଢ଼ିପାରିଲୁ। Engine ନିଜେ ଭରସାଯୋଗ୍ୟ।
SDK ର ଖର୍ଚ୍ଚ ଅଛି। CLibsql.xcframework ର ଆକାର 161 MB। Link କରିବା ପରେ ଆମ Debug simulator build ପ୍ରାୟ 1.9 MB ରୁ ପ୍ରାୟ 8.2 MB ହୋଇଗଲା। API synchronous ଏବଂ blocking, ଏବଂ ଏଥିରେ Swift Concurrency wrapper ନାହିଁ। କୌଣସି explicit close() method ନାହିଁ। Transaction.commit() throw କରେ ନାହିଁ—underlying C API void return କରେ। Repository ର README SDK କୁ “technical preview” ବୋଲି କହେ, ଏବଂ ଆମ evaluation ର ପ୍ରାୟ ଏକ ବର୍ଷ ପୂର୍ବରୁ, July 2025 ରେ, ସବୁଠାରୁ ନୂଆ commit ହୋଇଥିଲା।
Turso ecosystem ରେ ଏକ ଖାଲି ସ୍ଥାନ ଅଛି। ନୂଆ project ପାଇଁ Turso ଏବେ ନିଜର ନୂଆ “Turso Database” engine ଏବଂ “Turso Sync” protocol ସୁପାରିଶ କରେ। Turso Sync ର TypeScript, Python, Go ଏବଂ Rust ପାଇଁ client SDK ଅଛି। Swift ପାଇଁ ନାହିଁ। ପୁରୁଣା Embedded Replica mode libsql-swift ରେ ଅଛି, କିନ୍ତୁ ଏହାର Swift initializer ସମ୍ପୂର୍ଣ୍ଣ local-first mobile app ପାଇଁ ଦରକାରି offline parameter କୁ expose କରେ ନାହିଁ। ଆଜି libsql-swift ନେଲେ ଆମେ ଏକ local SQLite fork ପାଇବୁ, କିନ୍ତୁ Turso କୁ ଅଲଗା କରୁଥିବା synchronization capability ପାଇବୁ ନାହିଁ।
ଏବେ ଡାଟାବେସ୍ ଆମ ପାଇଁ କେତେ ଖର୍ଚ୍ଚାଳୁ
SDK ପରିପକ୍ୱ ହୋଇଥାନ୍ତା ମଧ୍ୟ, ଆମ କାର୍ଯ୍ୟଭାର ପାଇଁ କୌଣସି ଲାଭ ନଦେଇଥିବା ଖର୍ଚ୍ଚ ଆମକୁ ଦେବାକୁ ପଡ଼ିଥାନ୍ତା:
WAL sidecar management। ଚାଲୁଥିବା database -wal ଏବଂ -shm companion file ସୃଷ୍ଟି କରେ। ଗୋଟିଏ document copy କରିବାକୁ ପ୍ରଥମେ checkpoint କରିବା କିମ୍ବା ତିନୋଟି file କୁ atomic ଭାବରେ copy କରିବା ଦରକାର। Files କିମ୍ବା AirDrop କୁ .lnote package export କରିବା ଏବେ ଏକ pre-export step ଚାହିଁବ, ଯାହା user ଦେଖିପାରିବେ ନାହିଁ ଏବଂ developer ଭୁଲିପାରିବେ ନାହିଁ।
ଏକ adapter layer। Stroke ଗୁଡ଼ିକୁ BLOB ରେ serialize ଏବଂ ପୁଣି deserialize କରିବାକୁ ପଡ଼ିବ। Page element ଗୁଡ଼ିକର ଏକ ସ୍ୱାଭାବିକ array order ଅଛି, ଯାହାକୁ canvas ସିଧାସଳଖ render କରେ; database row ordering ଏବଂ z-index column ଯୋଡ଼ିଦେବ। ସମାନ data ର ଦୁଇଟି representation ମଧ୍ୟରେ ଏକ translation layer ଲେଖିବାକୁ ହେବ, ଏବଂ ପ୍ରତ୍ୟେକ schema change ସହ ଏହାକୁ maintain କରିବାକୁ ହେବ।
161 MB ର ଏକ dependency। ଯେଉଁ app ର Debug build 2 MB ରୁ କମ୍, ସେଠାରେ app ନିଜେ ଠାରୁ 80× ରୁ ବଡ଼ dependency ଏକ ଧ୍ୟାନ ଦେବାଯୋଗ୍ୟ ଖର୍ଚ୍ଚ—ବିଶେଷକରି ଯେତେବେଳେ ସେହି dependency କୁ “technical preview” କୁହାଯାଇଛି ଏବଂ ଏକ ବର୍ଷ ଧରି ନିଷ୍କ୍ରିୟ।
ଏହି ଖର୍ଚ୍ଚଗୁଡ଼ିକ କଳ୍ପିତ ନୁହେଁ। Dependency link ହେବା ମୁହୂର୍ତ୍ତରୁ ଏଗୁଡ଼ିକ ଆରମ୍ଭ ହୁଏ। ଏବଂ ଆମ app ବ୍ୟବହାର ନକରୁଥିବା capability—query, indexing, concurrent write—ପାଇଁ ଏଗୁଡ଼ିକ ଖର୍ଚ୍ଚ ହୁଏ।
ଆମେ ship କରିଥିବା ସମାଧାନ: snapshot file packages
ଏକ .lnote document ହେଉଛି ଏକ directory package:
Documents/Notes/<UUID>.lnote/
manifest.json # library cache: title, time, page count, cover
document.json # source of truth: document metadata + page order
pages/
<page-uuid>.content # one snapshot per page
assets/ # document-level shared resources
<asset-uuid>.jpg
thumbnails/
<page-uuid>.jpg # per-page thumbnail; first page doubles as cover
document.json document structure ପାଇଁ source of truth: ଏଥିରେ ତାହାର ID, title, timestamps ଏବଂ ପ୍ରତ୍ୟେକ page ର canvas size, timestamps ଓ element count ସହ ordered page list ରହେ। Page content file ଗୁଡ଼ିକ ସେହି quantized integer encoding ରେ element array ସଞ୍ଚୟ କରେ, ଯାହା app ପୂର୍ବରୁ ବ୍ୟବହାର କରୁଛି—0.1-point precision ରେ coordinates ଏବଂ radii, thousandths ରେ pressure, relative milliseconds ରେ timestamps।
Note library କେବଳ manifest.json ଏବଂ cover thumbnail ପଢ଼େ। ଏହା କେବେ document.json କିମ୍ବା କୌଣସି page content parse କରେ ନାହିଁ। ଗୋଟିଏ page ଖୋଲିଲେ ଗୋଟିଏ .content file load ହୁଏ। Stroke data କୁ ଛୁଉଥିବା ଏହା ହିଁ ଏକମାତ୍ର file read।
Pages write amplification ସମାଧାନ କରେ
ଏକ handwriting app ରେ page ର ଧାରଣା ଆଗରୁ ଅଛି—user ଯାହା ଭାବନ୍ତି, ଯାହା ମଧ୍ୟରେ swipe କରନ୍ତି। Page କୁ persistence ର unit କରିବାର ଅର୍ଥ auto-save କେବଳ ବଦଳିଥିବା page ଗୁଡ଼ିକୁ rewrite କରିବ।
Handwriting ଥିବା ଗୋଟିଏ page—ଧରନ୍ତୁ 1,000 ରୁ 2,000 stroke—ଆମ quantized format ରେ ପ୍ରାୟ 3–5 MB ହୁଏ। 3400 sampling point ଥିବା ଗୋଟିଏ 21-stroke recording quantize ହୋଇ ପ୍ରାୟ 55 KB ହୁଏ। Modern hardware ରେ ଗୋଟିଏ page snapshot flash storage ରେ ଲେଖିବାକୁ 10–20 ms ଲାଗେ। 0.5s debounce ସହ save user ପାଇଁ ଅଦୃଶ୍ୟ ରହେ।
Save cost ବର୍ତ୍ତମାନ page ରେ କେତେ ଲେଖାଯାଉଛି ତାହା ସହ ବଢ଼େ, document ରେ ମୋଟ କେତେ page ଅଛି ତାହା ସହ ନୁହେଁ। 200-page notebook ଏକ 2-page notebook ପରି ଠିକ୍ ସେହି ଗତିରେ save ହୁଏ, କାରଣ କେବଳ dirty page rewrite ହୁଏ।
ପ୍ରତ୍ୟେକ write atomic file operation ବ୍ୟବହାର କରେ—temporary file କୁ write କରି, ପରେ rename—ତେଣୁ save ମଝିରେ crash ହେଲେ truncated page ତିଆରି ହୋଇପାରେ ନାହିଁ। App background କୁ ଯିବା ସମୟରେ ସମସ୍ତ dirty page ତୁରନ୍ତ flush ହୁଏ, ଯାହା single canvas ସମୟରୁ ଥିବା behavior ସହ ମେଳ ଖାଏ।
Transactions ବିନା consistency
File package ରେ transaction ନାହିଁ, କିନ୍ତୁ ସେମାନଙ୍କର ସ୍ପଷ୍ଟ ownership rule ଅଛି, ଯାହା ସେହି ଉଦ୍ଦେଶ୍ୟ ପୂରଣ କରେ:
Resources before references। User ଯେତେବେଳେ ଗୋଟିଏ image insert କରନ୍ତି, asset file ତୁରନ୍ତ assets/ ରେ write ହୁଏ। Asset କୁ ID ଦ୍ୱାରା reference କରୁଥିବା page snapshot debounced auto-save ରେ ପରେ write ହୁଏ। କୌଣସି ମୁହୂର୍ତ୍ତରେ page ଏମିତି asset କୁ reference କରେ ନାହିଁ ଯାହା disk ରେ ନାହିଁ।
Source of truth wins। document.json ଏବଂ pages/ directory ହେଉଛି source of truth। manifest.json ଏକ cache। ଯଦି ଦୁଇଟି ମଧ୍ୟରେ ଅମେଳ ଥାଏ, ପରବର୍ତ୍ତୀ save source ସହ ମେଳ ଖାଇବା ପାଇଁ cache କୁ reconcile କରେ। Thumbnail derived, ଏବଂ ଯେକୌଣସି ସମୟରେ regenerate କରାଯାଇପାରେ।
Orphans over dangling references। Crash ର ସବୁଠାରୁ ଖରାପ ଫଳ ହେଉଛି orphan asset—assets/ ଭିତରେ ଥିବା ଏମିତି file ଯାହାକୁ କୌଣସି page reference କରେ ନାହିଁ। Document close ହେବାବେଳେ orphan ଗୁଡ଼ିକ clean up ହୁଏ। ଏହାର ବିପରୀତ—missing file କୁ reference କରୁଥିବା page—ହୋଇପାରେ ନାହିଁ, କାରଣ reference କରୁଥିବା page snapshot ପୂର୍ବରୁ asset write ହୁଏ।
ଏହି rule ଗୁଡ଼ିକ WAL checkpointing ଏବଂ transaction isolation ଠାରୁ ବୁଝିବାରେ ସହଜ, ଏବଂ app ର single-process, single-page access pattern ସହ ସମ୍ପୂର୍ଣ୍ଣ ମେଳ ଖାଏ।
Upgrade path ଲେଖା ହୋଇଛି
ଏବେ flat files ବାଛିବାର ଅର୍ଥ ସବୁଦିନ flat files ବାଛିବା ନୁହେଁ। Package structure ଏପରି ଭାବେ ତିଆରି ଯେ storage engine upgrade କଲେ package ଭିତରର ଜିନିଷ ବଦଳିବ, package ନିଜେ ନୁହେଁ।
Level 1: snapshot + append journal। ଯଦି write amplification କେବେ ଦୃଶ୍ୟମାନ ହୁଏ—ଧରନ୍ତୁ ହଜାର ହଜାର stroke ଥିବା page ରେ continuous writing କଲେ save delay ଅନୁଭବ ହୁଏ—ପ୍ରତ୍ୟେକ page file ଏକ snapshot ଏବଂ ଏକ append-only journal ରେ ଭାଗ ହେବ। ନୂଆ element ଗୁଡ଼ିକ [length][CRC][type][payload] frame ଭାବରେ append ହେବ। Journal replay କରିବାବେଳେ CRC ନମିଳିଥିବା frame discard ହେବ, ଯାହା crash safety ଦିଏ। Journal ଏକ threshold ଠାରୁ ବଡ଼ ହେଲେ କିମ୍ବା page close ହେଲେ, ଏହା ପୁଣି snapshot ରେ merge ହେବ। ଏହା ପ୍ରାୟ ~200 LOC, କୌଣସି external dependency ବିନା।
Page-level snapshot ପୂର୍ବରୁ cross-page write amplification ହଟାଇଦେଇଥିବାରୁ, ଏହି level ଦୀର୍ଘ ସମୟ ପର୍ଯ୍ୟନ୍ତ ଦରକାର ନହୋଇପାରେ। ପ୍ରତି 0.5s ରେ 5 MB page rewrite କରିବା flash write budget ଭିତରେ ସହଜରେ ରହେ।
Level 2: SQLite database। App କୁ ଯଦି କେବେ notes ମଧ୍ୟରେ full-text search, ପ୍ରତ୍ୟେକ element ର synchronization କିମ୍ବା cross-document indexing ଦରକାର ହୁଏ, SQLite ସଠିକ୍ tool ହେବ। ସେତେବେଳେ ସମ୍ଭାବ୍ୟ engine GRDB, ଯାହା mature, source-compiled Swift wrapper ଏବଂ ପ୍ରାୟ zero binary size overhead ଥାଏ। Turso ecosystem—ବିଶେଷକରି Swift ପାଇଁ Turso Sync—ଏକ ବାସ୍ତବ product need ହେଲେ ମାତ୍ର libsql-swift କୁ ପୁଣି ଭାବିବୁ।
Migration mechanical: ପ୍ରତ୍ୟେକ page ର element array ଏକ strokes / images table କୁ map ହେବ, ଏବଂ ପ୍ରତ୍ୟେକ stroke ପାଇଁ little-endian binary ରେ ପ୍ରତି sampling point 16 bytes ଥିବା ଗୋଟିଏ immutable BLOB ରହିବ। 100 stroke ର 0.019s benchmark ଏହି ପଦ୍ଧତି viable ବୋଲି ନିଶ୍ଚିତ କରେ। Ship ପୂର୍ବରୁ migration ପୁରୁଣା package ଏବଂ ନୂଆ database ମଧ୍ୟରେ element count ଓ asset count ମେଳାଉଛି କି ନାହିଁ verify କରିବ, ଏବଂ crash recovery, WAL bounds ଓ background flush ସମସ୍ତ test pass କରୁଛି କି ନାହିଁ ଯାଞ୍ଚିବ।
କେବେ ଖର୍ଚ୍ଚ କରିବା
ଏହି ନିଷ୍ପତ୍ତି ଡାଟାବେସ୍ ବିରୋଧରେ କୌଣସି ରାୟ ନୁହେଁ। SQLite concurrent writer, complex query ଏବଂ shared state ଉପରେ crash recovery ସମ୍ଭାଳେ—ଆମ app କୁ ଏବେ ଏହାର କିଛି ଦରକାର ନାହିଁ। ଯେ capability ଯେଉଁ ସମସ୍ୟା ସମାଧାନ କରେ, ସେ ସମସ୍ୟା ଆସିବା ପୂର୍ବରୁ ତାହା ପାଇଁ ଖର୍ଚ୍ଚ କରିବା ନେଟ୍ କ୍ଷତି।
ଡାଟାବେସ୍ର ଖର୍ଚ୍ଚ—dependency, WAL management, adapter layer, binary size—library link ହେବା ମୁହୂର୍ତ୍ତରୁ ଆରମ୍ଭ ହୁଏ। ଲାଭ ସେତେବେଳେ ଆରମ୍ଭ ହୁଏ ଯେତେବେଳେ app ରେ query ଚଲାଇବା, index maintain କରିବା କିମ୍ବା concurrent writer coordinate କରିବାକୁ ପଡ଼େ। ଏହି ପର୍ଯ୍ୟାୟରେ ଏହି ତିନୋଟିରୁ କୌଣସିଟି ନାହିଁ।
Storage evolution ର ପ୍ରତ୍ୟେକ ପଦକ୍ଷେପ କେବଳ ଆଗରୁ ଦେଖାଦେଇଥିବା ସମସ୍ୟା ପାଇଁ ଖର୍ଚ୍ଚ କରିବ। Page-level snapshot ଆଜିର ସମସ୍ୟା ପାଇଁ ଖର୍ଚ୍ଚ କରେ: ସମଗ୍ର file rewrite ନକରି multi-page document save କରିବା। Write amplification ଯଦି ମାପିହେବା ଯୋଗ୍ୟ ହୁଏ, append journal ସେହି ସମସ୍ୟା ପାଇଁ ଖର୍ଚ୍ଚ କରିବ। Search କିମ୍ବା sync product need ହେଲେ, database ସେହି ସମସ୍ୟା ପାଇଁ ଖର୍ଚ୍ଚ କରିବ।
Package structure, manifest ଏବଂ document schema କୌଣସି storage engine ସହ ବାନ୍ଧା ନୁହେଁ। Boundary ଗୁଡ଼ିକ ଠିକ୍ ସ୍ଥାନରେ ଥିବାରୁ switching cost କମ୍। ଯେତେବେଳେ ସତରେ database ଦରକାର ହେବ, ଆମେ ଏହାକୁ ଏକ ନିର୍ଦ୍ଦିଷ୍ଟ, ଆଗରୁ ମାପିଥିବା ସମସ୍ୟା ପାଇଁ ନେବୁ—କଳ୍ପିତ ସମସ୍ୟା ପାଇଁ ନୁହେଁ।