Lulucat

మేము ఇంకా డేటాబేస్‌ను ఎందుకు ఉపయోగించడం లేదు

Gaoge ZhangGaoge Zhang

మా iPad చేతిరాత యాప్ కోసం Turso/libSQLను అంచనా వేసి, అన్నింటినీ కొలిచి, ఫ్లాట్ స్నాప్‌షాట్ ఫైళ్లను ఎంచుకున్నాం. ఈ వర్క్‌లోడ్‌కు డేటాబేస్ అవసరం లేదు; ప్రతి దశ ఇప్పటికే ఉన్న సమస్యలకే ఖర్చు చేయాలి.

Lulucat Notes అనేది iPad కోసం చేతిరాత యాప్. గత వారం వరకు అందులో ఒకే కేన్వాస్ ఉండేది; రెండో నోట్ అనే భావన కూడా లేదు. ఇప్పుడు బహుళ డాక్యుమెంట్లు, ప్రతి డాక్యుమెంట్‌లో బహుళ పేజీలతో నోట్ లైబ్రరీని జోడించబోతున్నాం. మొదటి ఆర్కిటెక్చర్ ప్రశ్న స్టోరేజ్ గురించే.

డేటాబేస్ స్పష్టమైన సమాధానంలా అనిపించింది. నోట్ యాప్‌లు నిర్మిత డేటాను నిల్వ చేస్తాయి. నిర్మిత డేటా డేటాబేస్‌లోనే ఉంటుంది. మేము Turso మరియు దాని Swift SDKను అంచనా వేసి, iOS సిమ్యులేటర్‌లో నడిపి, నిజమైన స్ట్రోక్ డేటాతో బెంచ్‌మార్క్ చేసి, చివరికి దాన్ని ఉపయోగించకూడదని నిర్ణయించుకున్నాం.

దానికి బదులుగా ఫ్లాట్ ఫైళ్లను ఎంచుకున్నాం. ఫలితాలు, కారణాలు ఇవి.

చెక్క బల్లపై తెరిచి ఉన్న నోట్‌బుక్‌లో చేతిరాత నోట్లు, పక్కన పెన్ ఉన్నాయి.

Gabriel Cox తీసిన ఫోటో, Unsplashలో ఉంది. Unsplash License.

యాప్ డేటాతో నిజంగా ఏమి చేస్తుంది

చేతిరాత యాప్‌కి డేటా యాక్సెస్ విధానం పరిమితమైనది, ముందుగా ఊహించగలిగినది. చదవడం అంటే ఒక పేజీని తెరిచి, దానిలోని ప్రతి ఎలిమెంట్‌ను — అన్ని స్ట్రోక్‌లు, అన్ని ఇమేజ్‌లు — ఒకేసారి మెమరీలోకి లోడ్ చేయడం. కేన్వాస్ అన్నింటినీ ఉంచుతుంది; అది ఎప్పుడూ పాక్షిక క్వెరీని నడపదు. రాయడం అంటే పెన్ స్ట్రోక్ పూర్తయినప్పుడు ఒక ఎలిమెంట్‌ను పేజీకి జోడించడం. అరుదైన సందర్భాల్లో యూజర్ స్ట్రోక్‌లో కొంత భాగాన్ని చెరిపేయవచ్చు, సెలెక్షన్‌ను కదిలించవచ్చు లేదా ఏదైనా తొలగించవచ్చు. అయినా అవన్నీ ఒకే పేజీ, ఒకే ఎలిమెంట్‌పై జరిగే ఆపరేషన్లే.

కాంకరెంట్ యాక్సెస్ లేదు. ఒకేసారి ఒక వ్యక్తి ఒక డాక్యుమెంట్‌లోని ఒక పేజీపై రాస్తాడు. డాక్యుమెంట్ల మధ్య శోధన కూడా లేదు — ప్రతి డాక్యుమెంట్‌కు నోట్ లైబ్రరీకి టైటిల్, టైమ్‌స్టాంప్, పేజీ కౌంట్, కవర్ థంబ్‌నెయిల్ మాత్రమే అవసరం; పేజీ కంటెంట్ చదవాల్సిన అవసరం లేదు.

డేటాబేస్‌లు క్వెరీలు, ఇండెక్స్‌లు, కాంకరెన్సీ సమన్వయం కోసం నిర్మించబడతాయి. మా యాప్ ఈ మూడింటినీ ఉపయోగించదు.

Turso మూల్యాంకనం

Turso యొక్క libSQL ఇంజిన్‌కు అధికారిక Swift SDK అయిన libsql-swiftను మేము అంచనా వేశాం.

SDK పనిచేస్తుంది. దాని 9 టెస్ట్ కేసులన్నీ పాస్ అయ్యాయి. దాన్ని యాప్ కాపీలో ఇంటిగ్రేట్ చేసి, iOS సిమ్యులేటర్ కోసం బిల్డ్ చేసి, లాంచ్ చేసి, యాప్ సాండ్‌బాక్స్‌లో లోకల్ డేటాబేస్ సృష్టించాం. ఒకే ట్రాన్సాక్షన్‌లో, ఒక్కో దాంట్లో 3,400 శాంప్లింగ్ పాయింట్లు ఉన్న 100 స్ట్రోక్‌లను రాశాం — 4,080,000 బైట్ల BLOB డేటా. మా డెవలప్‌మెంట్ Macలో దీనికి సుమారు 0.019s పట్టింది.

PRAGMA wal_checkpoint(TRUNCATE) నడిపిన తర్వాత WAL ఫైల్ సున్నాకి తగ్గింది. మెయిన్ .db ఫైల్‌ను మాత్రమే వేరే చోటికి కాపీ చేసి, దాన్ని తెరిచి, మొత్తం డేటాను తిరిగి చదవగలిగాం. ఇంజిన్‌లోనే సమస్య లేదు.

SDKకు ఖర్చులు ఉన్నాయి. CLibsql.xcframework 161 MB ఉంటుంది. లింక్ చేసిన తర్వాత మా Debug సిమ్యులేటర్ బిల్డ్ దాదాపు 1.9 MB నుంచి దాదాపు 8.2 MBకి పెరిగింది. API సింక్రోనస్‌గా, బ్లాకింగ్‌గా ఉంది; Swift Concurrency ర్యాపర్లు లేవు. స్పష్టమైన close() మెథడ్ లేదు. Transaction.commit() త్రో చేయదు — దిగువ C API voidని రిటర్న్ చేస్తుంది. రిపోజిటరీ README SDKను technical previewగా పేర్కొంటుంది. మా మూల్యాంకనానికి దాదాపు ఒక సంవత్సరం ముందు, జూలై 2025లో చివరి కమిట్ జరిగింది.

Turso ఎకోసిస్టమ్‌లో ఒక ఖాళీ ఉంది. కొత్త ప్రాజెక్టుల కోసం కొత్త Turso Database ఇంజిన్, Turso Sync ప్రోటోకాల్‌ను ఉపయోగించమని Turso ఇప్పుడు సూచిస్తోంది. Turso Syncకు TypeScript, Python, Go, Rust కోసం క్లయింట్ SDKలు ఉన్నాయి; Swift కోసం లేదు. పాత Embedded Replica మోడ్ libsql-swiftలో ఉంది. అయితే పూర్తిగా లోకల్-ఫస్ట్ మొబైల్ యాప్‌కు అవసరమైన offline పారామీటర్‌ను దాని Swift ఇనిషియలైజర్ బయటపెట్టదు. ఈరోజు libsql-swiftను తీసుకుంటే లోకల్ SQLite ఫోర్క్ వస్తుంది; Tursoను ప్రత్యేకంగా నిలబెట్టే సింక్రనైజేషన్ సామర్థ్యాలు రావు.

ఇప్పుడే డేటాబేస్ మనకు కలిగించే ఖర్చు

SDK పూర్తిగా మెచ్యూర్ అయినా, మా వర్క్‌లోడ్‌కు ఉపయోగం లేని సామర్థ్యాల కోసం ఖర్చు చేయాల్సి వస్తుంది:

WAL సైడ్‌కార్ నిర్వహణ. నడుస్తున్న డేటాబేస్ -wal, -shm సహచర ఫైళ్లను సృష్టిస్తుంది. డాక్యుమెంట్‌ను కాపీ చేయాలంటే ముందుగా చెక్‌పాయింట్ చేయాలి లేదా మూడు ఫైళ్లనూ అటామిక్‌గా కాపీ చేయాలి. .lnote ప్యాకేజీని Files లేదా AirDropకు ఎక్స్‌పోర్ట్ చేయడానికి యూజర్ చూడలేని, డెవలపర్ మరచిపోలేని ప్రీ-ఎక్స్‌పోర్ట్ దశ అవసరమవుతుంది.

అడాప్టర్ లేయర్. స్ట్రోక్‌లను BLOBలుగా సీరియలైజ్ చేసి, తిరిగి డీరియలైజ్ చేయాలి. పేజీ ఎలిమెంట్‌లకు సహజమైన అర్రే క్రమం ఉంది; కేన్వాస్ దానిని నేరుగా రెండర్ చేస్తుంది. డేటాబేస్ వరుస క్రమం, z-index కాలమ్‌లను తీసుకొస్తుంది. ఒకే డేటా యొక్క రెండు ప్రతినిధ్యాల మధ్య అనువాద లేయర్ రాసి, ప్రతి స్కీమా మార్పుతో దాన్ని నిర్వహించాలి.

161 MB డిపెండెన్సీ. Debug బిల్డ్ 2 MB కంటే తక్కువగా ఉన్న యాప్‌కు, యాప్ కంటే 80× కంటే పెద్ద డిపెండెన్సీ గమనించాల్సిన ఖర్చే — ముఖ్యంగా అది technical previewగా లేబుల్ చేయబడి, ఒక సంవత్సరం నుంచి యాక్టివిటీ లేకపోతే.

ఈ ఖర్చులు ఊహాజనితమైనవి కావు. డిపెండెన్సీ లింక్ చేసిన క్షణం నుంచే అవి మొదలవుతాయి. ప్రతిఫలంగా వచ్చే సామర్థ్యాలు — క్వెరీలు, ఇండెక్సింగ్, కాంకరెంట్ రైట్స్ — మా యాప్ ఉపయోగించదు.

మేము అందించిన పరిష్కారం: స్నాప్‌షాట్ ఫైల్ ప్యాకేజీలు

ఒక .lnote డాక్యుమెంట్ ఒక డైరెక్టరీ ప్యాకేజీ:

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 డాక్యుమెంట్ నిర్మాణానికి source of truth: దాని ID, టైటిల్, టైమ్‌స్టాంప్‌లు, క్రమబద్ధమైన పేజీల జాబితా ఇందులో ఉంటాయి. ప్రతి పేజీకి సంబంధించిన కేన్వాస్ పరిమాణం, టైమ్‌స్టాంప్‌లు, ఎలిమెంట్ కౌంట్ కూడా ఇందులో ఉంటాయి. పేజీ కంటెంట్ ఫైళ్లు యాప్ ఇప్పటికే ఉపయోగిస్తున్న అదే క్వాంటైజ్డ్ ఇంటీజర్ ఎన్‌కోడింగ్‌లో ఎలిమెంట్ అర్రేను నిల్వ చేస్తాయి — కోఆర్డినేట్‌లు, రేడియస్‌లు 0.1-పాయింట్ ఖచ్చితత్వంతో, ప్రెజర్ వెయ్యిలో భాగాలుగా, టైమ్‌స్టాంప్‌లు రిలేటివ్ మిల్లీసెకండ్లలో.

నోట్ లైబ్రరీ manifest.json, కవర్ థంబ్‌నెయిల్‌లను మాత్రమే చదువుతుంది. అది document.jsonను లేదా ఏ పేజీ కంటెంట్‌ను గానీ పార్స్ చేయదు. పేజీని తెరిచినప్పుడు ఒక .content ఫైల్ మాత్రమే లోడ్ అవుతుంది. స్ట్రోక్ డేటాను తాకే ఏకైక ఫైల్ రీడ్ అదే.

పేజీలు రైట్ యాంప్లిఫికేషన్‌ను పరిష్కరిస్తాయి

చేతిరాత యాప్‌లో పేజీ అనే భావన ముందే ఉంది — యూజర్ ఆలోచించే యూనిట్ అదే, యూజర్ ఒక పేజీ నుంచి మరొకదానికి స్వైప్ చేసేది అదే. పేజీని పర్సిస్టెన్స్ యూనిట్‌గా చేస్తే ఆటో-సేవ్ మారిన పేజీలను మాత్రమే తిరిగి రాస్తుంది.

ఒక చేతిరాత పేజీ — ఉదాహరణకు, 1,000 నుంచి 2,000 స్ట్రోక్‌లు — మా క్వాంటైజ్డ్ ఫార్మాట్‌లో సుమారు 3–5 MB ఉంటుంది. 3,400 శాంప్లింగ్ పాయింట్లు ఉన్న 21-స్ట్రోక్ రికార్డింగ్ క్వాంటైజ్ చేసిన తర్వాత సుమారు 55 KB అవుతుంది. ఆధునిక హార్డ్‌వేర్‌లో ఒక పేజీ స్నాప్‌షాట్‌ను ఫ్లాష్ స్టోరేజ్‌కు రాయడానికి 10–20 ms పడుతుంది. 0.5s డీబౌన్స్‌తో సేవ్‌లు యూజర్‌కు కనిపించవు.

సేవ్ ఖర్చు డాక్యుమెంట్‌లోని మొత్తం పేజీలపై కాదు, ప్రస్తుత పేజీలోని రాత పరిమాణంపై ఆధారపడుతుంది. 200-పేజీల నోట్‌బుక్ 2-పేజీల నోట్‌బుక్‌లాగే వేగంగా సేవ్ అవుతుంది, ఎందుకంటే డర్టీ పేజీ మాత్రమే తిరిగి రాయబడుతుంది.

ప్రతి రైట్ అటామిక్ ఫైల్ ఆపరేషన్లను ఉపయోగిస్తుంది — ముందుగా టెంపరరీ ఫైల్‌కు రాసి, తర్వాత రీనేమ్ చేయడం — కాబట్టి సేవ్ మధ్యలో క్రాష్ అయినా ట్రంకేటెడ్ పేజీ తయారు కాదు. యాప్ బ్యాక్‌గ్రౌండ్‌లోకి వెళ్లగానే అన్ని డర్టీ పేజీలను వెంటనే ఫ్లష్ చేస్తుంది. సింగిల్ కేన్వాస్ ఉన్నప్పటి ప్రవర్తన ఇదే.

ట్రాన్సాక్షన్లు లేకుండా కన్‌సిస్టెన్సీ

ఫైల్ ప్యాకేజీలకు ట్రాన్సాక్షన్లు ఉండవు, కానీ అదే ప్రయోజనాన్ని అందించే స్పష్టమైన ఓనర్‌షిప్ నియమాలు ఉన్నాయి:

రిఫరెన్స్‌లకు ముందు రిసోర్సులు. యూజర్ ఇమేజ్‌ను ఇన్‌సర్ట్ చేసినప్పుడు, ఆ అసెట్ ఫైల్ వెంటనే assets/లో రాయబడుతుంది. అసెట్ IDని రిఫరెన్స్ చేసే పేజీ స్నాప్‌షాట్ డీబౌన్స్‌డ్ ఆటో-సేవ్ ద్వారా తర్వాత రాయబడుతుంది. డిస్క్‌లో లేని అసెట్‌ను పేజీ రిఫరెన్స్ చేసే పరిస్థితి ఉండదు.

Source of truth గెలుస్తుంది. document.json, pages/ డైరెక్టరీ source of truth; manifest.json కాష్. అవి సరిపోలకపోతే తదుపరి సేవ్ కాష్‌ను sourceకు సరిపోయేలా సరిచేస్తుంది. థంబ్‌నెయిల్‌లు డెరివ్డ్ డేటా; వాటిని ఎప్పుడైనా మళ్లీ రూపొందించవచ్చు.

డ్యాంగ్లింగ్ రిఫరెన్స్‌ల కంటే ఆర్ఫన్‌లు. క్రాష్ తర్వాత జరిగే చెత్త ఫలితం ఒక ఆర్ఫన్ అసెట్ — assets/లో ఏ పేజీ రిఫరెన్స్ చేయని ఫైల్. డాక్యుమెంట్ మూసినప్పుడు ఆర్ఫన్‌లు శుభ్రం చేయబడతాయి. దీనికి విరుద్ధంగా, పేజీ డిస్క్‌లో లేని ఫైల్‌ను రిఫరెన్స్ చేయదు, ఎందుకంటే రిఫరెన్స్ చేసే పేజీ స్నాప్‌షాట్‌కు ముందే అసెట్ రాయబడుతుంది.

ఈ నియమాలు WAL చెక్‌పాయింటింగ్, ట్రాన్సాక్షన్ ఐసోలేషన్ కంటే ఆలోచించడానికి సులభమైనవి; యాప్ సింగిల్-ప్రాసెస్, సింగిల్-పేజీ యాక్సెస్ విధానానికి అవి సరిగ్గా సరిపోతాయి.

అప్‌గ్రేడ్ మార్గం స్పష్టంగా ఉంది

ఇప్పుడు ఫ్లాట్ ఫైళ్లను ఎంచుకోవడం శాశ్వత నిర్ణయం కాదు. ప్యాకేజీ మారకుండా, దాని లోపల ఉన్నదాన్ని మార్చి స్టోరేజ్ ఇంజిన్‌ను అప్‌గ్రేడ్ చేయడానికి ప్యాకేజీ నిర్మాణం రూపొందించబడింది.

లెవెల్ 1: స్నాప్‌షాట్ + అప్పెండ్ జర్నల్. రైట్ యాంప్లిఫికేషన్ ఎప్పుడైనా గమనించదగ్గదిగా మారితే — ఉదాహరణకు, వేల స్ట్రోక్‌లున్న పేజీలో నిరంతరం రాయడం వల్ల సేవ్ ఆలస్యం స్పష్టంగా కనిపిస్తే — ప్రతి పేజీ ఫైల్ స్నాప్‌షాట్, అప్పెండ్-ఓన్లీ జర్నల్‌గా విడిపోతుంది. కొత్త ఎలిమెంట్‌లు [length][CRC][type][payload] ఫ్రేమ్‌లుగా అప్పెండ్ అవుతాయి. జర్నల్‌ను రీప్లే చేసేటప్పుడు CRC సరిపోని ఫ్రేమ్‌లు తొలగించబడతాయి; దాంతో క్రాష్ సేఫ్టీ లభిస్తుంది. జర్నల్ థ్రెషోల్డ్ దాటినా లేదా పేజీ మూసినా, అది తిరిగి స్నాప్‌షాట్‌లో విలీనం అవుతుంది. బాహ్య డిపెండెన్సీలు లేకుండా ఇది సుమారు ~200 LOC మాత్రమే.

పేజీ-స్థాయి స్నాప్‌షాట్‌లు ఇప్పటికే క్రాస్-పేజీ రైట్ యాంప్లిఫికేషన్‌ను తొలగించాయి కాబట్టి, ఈ లెవెల్ చాలా కాలం అవసరం కాకపోవచ్చు. 5 MB పేజీని ప్రతి 0.5sకు ఒకసారి తిరిగి రాయడం ఫ్లాష్ రైట్ బడ్జెట్‌లోనే ఉంటుంది.

లెవెల్ 2: SQLite డేటాబేస్. యాప్‌కు నోట్స్ అంతటా ఫుల్-టెక్స్ట్ సెర్చ్, ప్రతి ఎలిమెంట్‌కు సింక్రనైజేషన్ లేదా డాక్యుమెంట్ల మధ్య ఇండెక్సింగ్ అవసరమైతే SQLite సరైన సాధనం. ఆ సమయంలో GRDBనే ఉపయోగించే అవకాశం ఉంది. ఇది మెచ్యూర్, సోర్స్-కంపైల్డ్ Swift ర్యాపర్; బైనరీ సైజ్ ఓవర్‌హెడ్ దాదాపు సున్నా. Turso ఎకోసిస్టమ్‌లో — ముఖ్యంగా Turso Sync for Swift — నిజమైన ప్రోడక్ట్ అవసరంగా మారితేనే libsql-swiftను మళ్లీ పరిశీలిస్తాం.

మైగ్రేషన్ మార్గం యాంత్రికమే: ప్రతి పేజీ ఎలిమెంట్ అర్రే strokes / images టేబుల్‌కు మ్యాప్ అవుతుంది; ప్రతి స్ట్రోక్‌కు ఒక ఇమ్మ్యూటబుల్ BLOB, ప్రతి శాంప్లింగ్ పాయింట్‌కు little-endian binaryలో 16 bytes. 100 స్ట్రోక్‌లపై చేసిన 0.019s బెంచ్‌మార్క్ ఈ విధానం సాధ్యమేనని నిర్ధారిస్తుంది. విడుదలకు ముందు మైగ్రేషన్ పాత ప్యాకేజీ, కొత్త డేటాబేస్‌లో ఎలిమెంట్ కౌంట్‌లు, అసెట్ కౌంట్‌లు సరిపోతున్నాయా అని తనిఖీ చేస్తుంది; క్రాష్ రికవరీ, WAL బౌండ్స్, బ్యాక్‌గ్రౌండ్ ఫ్లష్ అన్నీ పాస్ అవుతున్నాయా అని కూడా పరీక్షిస్తుంది.

ఎప్పుడు ఖర్చు చేయాలి

ఈ ఎంపిక యాప్‌కి ప్రస్తుతం ఉన్న అవసరాల ఆధారంగా జరిగింది. SQLite షేర్డ్ స్టేట్‌లో కాంకరెంట్ రైటర్లు, క్లిష్టమైన క్వెరీలు, క్రాష్ రికవరీని నిర్వహిస్తుంది; మా యాప్‌కు వీటిలో ఏదీ ప్రస్తుతం అవసరం లేదు. ఈ సామర్థ్యాల కోసం, వాటిని అవసరపరిచే సమస్యలు కనిపించకముందే ఖర్చు చేయడం, ప్రస్తుతం సమానమైన ప్రయోజనం లేని అదనపు వ్యయాన్ని సృష్టిస్తుంది.

డేటాబేస్ ఖర్చులు — డిపెండెన్సీ, WAL నిర్వహణ, అడాప్టర్ లేయర్, బైనరీ సైజ్ — లైబ్రరీ లింక్ చేసిన క్షణం నుంచే మొదలవుతాయి. యాప్‌కు అమలు చేయాల్సిన క్వెరీలు, నిర్వహించాల్సిన ఇండెక్స్‌లు లేదా సమన్వయం చేయాల్సిన కాంకరెంట్ రైటర్లు ఉన్నప్పుడే ప్రయోజనాలు మొదలవుతాయి. ఇప్పుడు ఈ మూడింటిలో ఏదీ లేదు.

మా స్టోరేజ్ పరిణామంలోని ప్రతి దశ ఇప్పటికే కనిపించిన సమస్యలకే ఖర్చు చేస్తుంది. పేజీ-స్థాయి స్నాప్‌షాట్‌లు ఈరోజు ఉన్న సమస్యను పరిష్కరిస్తాయి: మల్టీ-పేజీ డాక్యుమెంట్లను మొత్తం ఫైల్‌ను తిరిగి రాయకుండా సేవ్ చేయడం. రైట్ యాంప్లిఫికేషన్ కొలవగలిగే స్థాయికి వస్తే, అప్పెండ్ జర్నల్ ఆ సమస్యకు పరిష్కారం అవుతుంది. సెర్చ్ లేదా సింక్ ప్రోడక్ట్ అవసరమైతే, డేటాబేస్ ఆ సమస్యకు పరిష్కారం అవుతుంది.

ప్యాకేజీ నిర్మాణం, manifest, డాక్యుమెంట్ స్కీమా ఏ స్టోరేజ్ ఇంజిన్‌తోనూ బంధించబడలేదు. సరిహద్దులు సరైన చోట ఉన్నందున మార్పిడి ఖర్చు తక్కువ. డేటాబేస్ నిజంగా అవసరమైన రోజు, ఊహాజనిత సమస్య కోసం కాదు; ఇప్పటికే కొలిచిన ఒక నిర్దిష్ట సమస్య కోసం దాన్ని తీసుకుంటాం.