Lulucat

ਅਸੀਂ ਹਾਲੇ ਤੱਕ ਡਾਟਾਬੇਸ ਕਿਉਂ ਨਹੀਂ ਵਰਤਦੇ

Gaoge ZhangGaoge Zhang

ਸਾਡੀ iPad ਹੱਥ-ਲਿਖਤ ਐਪ ਲਈ Turso/libSQL ਦਾ ਮੁਲਾਂਕਣ ਕਰਕੇ, ਹਰ ਚੀਜ਼ ਮਾਪਣ ਤੋਂ ਬਾਅਦ ਅਸੀਂ ਫਲੈਟ snapshot ਫਾਈਲਾਂ ਚੁਣੀਆਂ। ਇਸ ਕਾਰਜ-ਭਾਰ ਨੂੰ ਡਾਟਾਬੇਸ ਦੀ ਲੋੜ ਨਹੀਂ—ਅਤੇ ਹਰ ਕਦਮ ਨੂੰ ਸਿਰਫ਼ ਉਨ੍ਹਾਂ ਸਮੱਸਿਆਵਾਂ ਲਈ ਭੁਗਤਾਨ ਕਰਨਾ ਚਾਹੀਦਾ ਹੈ ਜੋ ਪਹਿਲਾਂ ਹੀ ਮੌਜੂਦ ਹਨ।

Lulucat Notes iPad ਲਈ ਇੱਕ handwriting app ਹੈ। ਪਿਛਲੇ ਹਫ਼ਤੇ ਤੱਕ ਇਸ ਵਿੱਚ ਇੱਕ canvas ਸੀ ਅਤੇ ਦੂਜੇ note ਦੀ ਕੋਈ ਧਾਰਣਾ ਨਹੀਂ ਸੀ। ਅਸੀਂ ਇੱਕ note library ਜੋੜਨ ਵਾਲੇ ਸਾਂ—ਕਈ documents, ਹਰ ਇੱਕ ਵਿੱਚ ਕਈ pages—ਅਤੇ ਪਹਿਲਾ architectural ਸਵਾਲ storage ਦਾ ਸੀ।

ਡਾਟਾਬੇਸ ਸਪੱਸ਼ਟ ਜਵਾਬ ਜਾਪਦਾ ਸੀ। Notes apps structured data ਸੰਭਾਲਦੀਆਂ ਹਨ। Structured data ਡਾਟਾਬੇਸ ਵਿੱਚ ਜਾਂਦਾ ਹੈ। ਅਸੀਂ Turso ਅਤੇ ਇਸ ਦੇ Swift SDK ਦਾ ਮੁਲਾਂਕਣ ਕੀਤਾ, ਇਸ ਨੂੰ iOS simulator ਉੱਤੇ ਚਲਾਇਆ, ਅਸਲ stroke data ਨੂੰ benchmark ਕੀਤਾ ਅਤੇ ਫਿਰ ਇਸ ਨੂੰ ਨਾ ਵਰਤਣ ਦਾ ਫ਼ੈਸਲਾ ਕੀਤਾ।

ਅਸੀਂ ਇਸ ਦੀ ਥਾਂ flat files ਚੁਣੀਆਂ। ਸਾਨੂੰ ਕੀ ਮਿਲਿਆ ਅਤੇ ਅਸੀਂ ਇਹ ਫ਼ੈਸਲਾ ਕਿਉਂ ਕੀਤਾ, ਉਹ ਇੱਥੇ ਹੈ।

ਲੱਕੜੀ ਦੀ ਮੇਜ਼ ਉੱਤੇ ਹੱਥ-ਲਿਖਤ ਨੋਟਾਂ ਅਤੇ ਕਲਮ ਵਾਲੀ ਖੁੱਲ੍ਹੀ ਨੋਟਬੁੱਕ।

Gabriel Cox ਦੀ ਤਸਵੀਰ Unsplash ਉੱਤੇ। Unsplash ਲਾਇਸੈਂਸ

ਐਪ ਅਸਲ ਵਿੱਚ data ਨਾਲ ਕੀ ਕਰਦੀ ਹੈ

ਇੱਕ ਹੱਥ-ਲਿਖਤ ਐਪ ਦਾ data access pattern ਸੀਮਤ ਅਤੇ ਅਨੁਮਾਨਯੋਗ ਹੁੰਦਾ ਹੈ। ਪੜ੍ਹਨ ਦਾ ਮਤਲਬ ਹੈ ਇੱਕ page ਖੋਲ੍ਹਣਾ ਅਤੇ ਉਸ ਦੇ ਹਰ element—ਸਾਰੇ strokes, ਸਾਰੀਆਂ images—ਨੂੰ ਇੱਕੋ ਵਾਰ memory ਵਿੱਚ load ਕਰਨਾ। Canvas ਸਭ ਕੁਝ ਰੱਖਦਾ ਹੈ; ਇਹ ਕਦੇ partial query ਨਹੀਂ ਚਲਾਉਂਦਾ। ਲਿਖਣ ਦਾ ਮਤਲਬ ਹੈ pen stroke ਮੁਕਾ ਕੇ page ਵਿੱਚ ਇੱਕ element append ਕਰਨਾ। ਕਦੇ-ਕਦਾਈਂ user ਕਿਸੇ stroke ਦਾ ਹਿੱਸਾ ਮਿਟਾਉਂਦਾ ਹੈ, selection ਹਿਲਾਉਂਦਾ ਹੈ ਜਾਂ ਕੁਝ delete ਕਰਦਾ ਹੈ, ਪਰ ਇਹ ਵੀ single-page, single-element operations ਹਨ।

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 cases pass ਹੁੰਦੇ ਹਨ। ਅਸੀਂ ਇਸ ਨੂੰ app ਦੀ ਇੱਕ copy ਵਿੱਚ integrate ਕੀਤਾ, iOS simulator ਲਈ build ਕੀਤਾ, launch ਕੀਤਾ ਅਤੇ app sandbox ਵਿੱਚ local database ਬਣਾਇਆ। ਇੱਕ transaction ਵਿੱਚ 100 strokes ਲਿਖੇ, ਹਰ ਇੱਕ ਵਿੱਚ 3400 sampling points—ਕੁੱਲ 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 wrappers ਨਹੀਂ ਹਨ। ਕੋਈ explicit close() method ਨਹੀਂ ਹੈ। Transaction.commit() throw ਨਹੀਂ ਕਰਦਾ—ਅੰਦਰਲਾ C API void return ਕਰਦਾ ਹੈ। Repository ਦਾ README SDK ਨੂੰ “technical preview” ਕਹਿੰਦਾ ਹੈ, ਅਤੇ ਸਭ ਤੋਂ ਨਵਾਂ commit ਸਾਡੇ ਮੁਲਾਂਕਣ ਤੋਂ ਲਗਭਗ ਇੱਕ ਸਾਲ ਪਹਿਲਾਂ, July 2025 ਵਿੱਚ ਹੋਇਆ ਸੀ।

Turso ecosystem ਵਿੱਚ ਇੱਕ ਖਾਲੀ ਥਾਂ ਹੈ। ਨਵੇਂ projects ਲਈ Turso ਹੁਣ ਆਪਣਾ ਨਵਾਂ “Turso Database” engine ਅਤੇ “Turso Sync” protocol ਸੁਝਾਉਂਦਾ ਹੈ। Turso Sync ਕੋਲ TypeScript, Python, Go ਅਤੇ Rust ਲਈ client SDKs ਹਨ। Swift ਲਈ ਨਹੀਂ। ਪੁਰਾਣਾ Embedded Replica mode libsql-swift ਵਿੱਚ ਮੌਜੂਦ ਹੈ, ਪਰ ਇਸ ਦਾ Swift initializer ਪੂਰੀ ਤਰ੍ਹਾਂ local-first mobile app ਲਈ ਲੋੜੀਂਦਾ offline parameter expose ਨਹੀਂ ਕਰਦਾ। ਅੱਜ libsql-swift ਅਪਣਾਉਣ ਨਾਲ ਸਾਨੂੰ local SQLite fork ਤਾਂ ਮਿਲੇਗਾ, ਪਰ ਉਹ synchronization capabilities ਨਹੀਂ ਮਿਲਣਗੀਆਂ ਜੋ Turso ਨੂੰ ਵੱਖਰਾ ਬਣਾਉਂਦੀਆਂ ਹਨ।

ਇਸ ਵੇਲੇ ਡਾਟਾਬੇਸ ਸਾਡੇ ਲਈ ਕੀ ਮਹਿੰਗਾ ਪਵੇਗਾ

ਭਾਵੇਂ SDK mature ਹੁੰਦਾ, ਸਾਨੂੰ ਫਿਰ ਵੀ ਉਹ ਲਾਗਤਾਂ ਦੇਣੀਆਂ ਪੈਂਦੀਆਂ ਜੋ ਸਾਡੇ ਕਾਰਜ-ਭਾਰ ਲਈ ਕੁਝ ਨਹੀਂ ਖਰੀਦਦੀਆਂ:

WAL sidecar management। ਚੱਲਦਾ database -wal ਅਤੇ -shm companion files ਬਣਾਉਂਦਾ ਹੈ। ਇੱਕ document copy ਕਰਨ ਲਈ ਜਾਂ ਤਾਂ ਪਹਿਲਾਂ checkpoint ਕਰਨਾ ਪੈਂਦਾ ਹੈ ਜਾਂ ਤਿੰਨੋਂ files ਨੂੰ atomic ਤਰੀਕੇ ਨਾਲ copy ਕਰਨਾ ਪੈਂਦਾ ਹੈ। Files ਜਾਂ AirDrop ਨੂੰ .lnote package export ਕਰਨ ਲਈ ਹੁਣ ਇੱਕ pre-export step ਚਾਹੀਦਾ ਹੋਵੇਗਾ, ਜਿਸ ਨੂੰ user ਦੇਖ ਨਹੀਂ ਸਕਦਾ ਅਤੇ developer ਭੁੱਲ ਨਹੀਂ ਸਕਦਾ।

ਇੱਕ adapter layer। Strokes ਨੂੰ BLOBs ਵਿੱਚ serialize ਅਤੇ ਵਾਪਸ deserialize ਕਰਨਾ ਪਵੇਗਾ। Page elements ਦਾ ਇੱਕ natural array order ਹੈ ਜਿਸ ਨੂੰ canvas ਸਿੱਧਾ render ਕਰਦਾ ਹੈ; database row ordering ਅਤੇ z-index columns ਲਿਆਵੇਗਾ। ਇੱਕੋ data ਦੀਆਂ ਦੋ representations ਵਿਚਕਾਰ translation layer ਲਿਖਣੀ ਪਵੇਗੀ ਅਤੇ ਹਰ schema change ਨਾਲ ਉਸ ਨੂੰ maintain ਕਰਨਾ ਪਵੇਗਾ।

161 MB ਦੀ dependency। ਜਿਸ app ਦਾ Debug build 2 MB ਤੋਂ ਘੱਟ ਹੈ, ਉਸ ਲਈ app ਤੋਂ 80× ਵੱਡੀ dependency ਇੱਕ ਧਿਆਨ ਦੇਣ ਯੋਗ ਲਾਗਤ ਹੈ—ਖ਼ਾਸ ਕਰਕੇ ਜਦੋਂ ਉਸ ਨੂੰ “technical preview” ਕਿਹਾ ਗਿਆ ਹੋਵੇ ਅਤੇ ਇੱਕ ਸਾਲ ਤੋਂ ਕੋਈ activity ਨਾ ਹੋਵੇ।

ਇਹ ਲਾਗਤਾਂ ਕਲਪਨਾਤਮਕ ਨਹੀਂ ਹਨ। Dependency link ਹੋਣ ਦੇ ਪਲ ਤੋਂ ਇਹ ਸ਼ੁਰੂ ਹੋ ਜਾਂਦੀਆਂ ਹਨ। ਅਤੇ ਇਹ ਸਾਨੂੰ ਉਹ capabilities—querying, indexing, concurrent writes—ਖਰੀਦ ਕੇ ਦਿੰਦੀਆਂ ਹਨ ਜੋ ਸਾਡੀ app ਵਰਤਦੀ ਹੀ ਨਹੀਂ।

ਅਸੀਂ 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 ਅਤੇ pages ਦੀ ordered list ਹੁੰਦੀ ਹੈ, ਜਿਸ ਵਿੱਚ ਹਰ page ਦਾ canvas size, timestamps ਅਤੇ element count ਹੁੰਦਾ ਹੈ। Page content files ਉਹੀ quantized integer encoding ਵਿੱਚ element array ਸੰਭਾਲਦੀਆਂ ਹਨ ਜੋ app ਪਹਿਲਾਂ ਹੀ ਵਰਤਦੀ ਹੈ—coordinates ਅਤੇ radii 0.1-point precision ਉੱਤੇ, pressure thousandths ਵਿੱਚ ਅਤੇ timestamps relative milliseconds ਵਿੱਚ।

Note library ਸਿਰਫ਼ manifest.json ਅਤੇ cover thumbnails ਪੜ੍ਹਦੀ ਹੈ। ਇਹ ਕਦੇ document.json ਜਾਂ ਕਿਸੇ page content ਨੂੰ parse ਨਹੀਂ ਕਰਦੀ। Page ਖੋਲ੍ਹਣ ਉੱਤੇ ਇੱਕ .content file load ਹੁੰਦੀ ਹੈ। Stroke data ਨੂੰ ਛੁਹਿਣ ਵਾਲੀ ਇਹੀ ਇਕੱਲੀ file read ਹੈ।

Pages write amplification ਦਾ ਹੱਲ ਕਰਦੇ ਹਨ

Handwriting app ਵਿੱਚ page ਦੀ ਧਾਰਣਾ ਪਹਿਲਾਂ ਹੀ ਹੁੰਦੀ ਹੈ—ਇਹ ਉਹ unit ਹੈ ਜਿਸ ਬਾਰੇ user ਸੋਚਦੇ ਹਨ ਅਤੇ ਜਿਸ ਦੇ ਵਿਚਕਾਰ ਉਹ swipe ਕਰਦੇ ਹਨ। Page ਨੂੰ persistence ਦੀ unit ਬਣਾਉਣ ਦਾ ਮਤਲਬ ਹੈ ਕਿ auto-save ਸਿਰਫ਼ ਬਦਲੇ ਹੋਏ pages ਨੂੰ rewrite ਕਰੇ।

Handwriting ਦਾ ਇੱਕ page—ਮੰਨ ਲਓ 1,000 ਤੋਂ 2,000 strokes—ਸਾਡੇ quantized format ਵਿੱਚ ਲਗਭਗ 3–5 MB ਲੈਂਦਾ ਹੈ। 3400 sampling points ਵਾਲੀ ਇੱਕ 21-stroke recording quantize ਹੋ ਕੇ ਲਗਭਗ 55 KB ਬਣਦੀ ਹੈ। Modern hardware ਉੱਤੇ ਇੱਕ page snapshot ਨੂੰ flash storage ਵਿੱਚ ਲਿਖਣ ਲਈ 10–20 ms ਲੱਗਦੇ ਹਨ। 0.5s debounce ਨਾਲ saves user ਨੂੰ ਦਿਖਾਈ ਨਹੀਂ ਦਿੰਦੇ।

Save cost ਮੌਜੂਦਾ page ਉੱਤੇ ਲਿਖਤ ਦੀ ਮਾਤਰਾ ਨਾਲ ਵਧਦੀ ਹੈ, document ਦੇ ਕੁੱਲ pages ਦੀ ਗਿਣਤੀ ਨਾਲ ਨਹੀਂ। ਇੱਕ 200-page notebook ਬਿਲਕੁਲ 2-page notebook ਜਿੰਨੀ ਤੇਜ਼ save ਹੁੰਦੀ ਹੈ, ਕਿਉਂਕਿ ਸਿਰਫ਼ dirty page rewrite ਹੁੰਦਾ ਹੈ।

ਹਰ write atomic file operations ਵਰਤਦੀ ਹੈ—temporary file ਵਿੱਚ write ਕਰਨਾ, ਫਿਰ rename—ਇਸ ਲਈ save ਦੇ ਵਿਚਕਾਰ crash ਹੋਣ ਨਾਲ truncated page ਨਹੀਂ ਬਣ ਸਕਦਾ। Background ਵਿੱਚ ਜਾਣ ਉੱਤੇ ਸਾਰੇ dirty pages ਤੁਰੰਤ flush ਹੁੰਦੇ ਹਨ, ਜੋ single canvas ਵੇਲੇ app ਦੇ behavior ਨਾਲ ਮਿਲਦਾ ਹੈ।

Transactions ਤੋਂ ਬਿਨਾਂ consistency

File packages ਵਿੱਚ transactions ਨਹੀਂ ਹੁੰਦੀਆਂ, ਪਰ ownership ਦੇ ਸਪੱਸ਼ਟ rules ਹਨ ਜੋ ਉਹੀ ਕੰਮ ਕਰਦੇ ਹਨ:

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 cache ਨੂੰ source ਨਾਲ ਮਿਲਾਉਂਦਾ ਹੈ। Thumbnails derived ਹਨ ਅਤੇ ਕਿਸੇ ਵੀ ਵੇਲੇ regenerate ਕੀਤੇ ਜਾ ਸਕਦੇ ਹਨ।

Orphans over dangling references। Crash ਦਾ ਸਭ ਤੋਂ ਮਾੜਾ ਨਤੀਜਾ orphan asset ਹੈ—assets/ ਵਿੱਚ ਅਜਿਹੀ file ਜਿਸ ਨੂੰ ਕੋਈ page reference ਨਹੀਂ ਕਰਦਾ। Document close ਹੋਣ ਉੱਤੇ orphans clean up ਕੀਤੇ ਜਾਂਦੇ ਹਨ। ਉਲਟ ਹਾਲਤ—ਕਿਸੇ missing file ਨੂੰ reference ਕਰਦਾ page—ਹੋ ਨਹੀਂ ਸਕਦੀ, ਕਿਉਂਕਿ reference ਕਰਨ ਵਾਲੇ page snapshot ਤੋਂ ਪਹਿਲਾਂ assets write ਹੁੰਦੇ ਹਨ।

ਇਹ rules 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 ਕਦੇ ਮਹਿਸੂਸ ਹੋਣ ਲੱਗੇ—ਮੰਨ ਲਓ ਹਜ਼ਾਰਾਂ strokes ਵਾਲੇ page ਉੱਤੇ ਲਗਾਤਾਰ ਲਿਖਣ ਨਾਲ save delay ਨਜ਼ਰ ਆਵੇ—ਤਾਂ ਹਰ page file ਇੱਕ snapshot ਅਤੇ append-only journal ਵਿੱਚ ਵੰਡ ਜਾਵੇਗੀ। ਨਵੇਂ elements [length][CRC][type][payload] frames ਵਜੋਂ append ਹੋਣਗੇ। Journal replay ਕਰਦੇ ਸਮੇਂ ਜਿਸ frame ਦਾ CRC match ਨਾ ਕਰੇ, ਉਹ discard ਹੋ ਜਾਵੇਗਾ ਅਤੇ crash safety ਮਿਲੇਗੀ। Journal threshold ਤੋਂ ਵੱਡੀ ਹੋਵੇ ਜਾਂ page close ਹੋਵੇ, ਤਾਂ ਇਹ ਮੁੜ snapshot ਵਿੱਚ merge ਹੋ ਜਾਵੇਗੀ। ਇਹ ਲਗਭਗ ~200 LOC ਹੈ ਅਤੇ ਕੋਈ external dependency ਨਹੀਂ।

ਕਿਉਂਕਿ page-level snapshots cross-page write amplification ਪਹਿਲਾਂ ਹੀ ਖਤਮ ਕਰ ਦਿੰਦੇ ਹਨ, ਇਹ level ਲੰਬੇ ਸਮੇਂ ਤੱਕ ਲੋੜੀਂਦਾ ਨਾ ਹੋਵੇ। ਹਰ 0.5s ਵਿੱਚ 5 MB page rewrite ਕਰਨਾ flash write budgets ਦੇ ਅੰਦਰ ਆਸਾਨੀ ਨਾਲ ਰਹਿੰਦਾ ਹੈ।

Level 2: SQLite database। ਜੇ app ਨੂੰ ਕਦੇ notes ਵਿੱਚ full-text search, ਹਰ element ਦੀ synchronization ਜਾਂ cross-document indexing ਚਾਹੀਦੀ ਹੋਵੇ, ਤਾਂ SQLite ਸਹੀ tool ਬਣੇਗਾ। ਉਸ ਵੇਲੇ ਸੰਭਾਵੀ engine GRDB ਹੋਵੇਗਾ, ਜੋ mature, source-compiled Swift wrapper ਹੈ ਅਤੇ binary size overhead ਲਗਭਗ zero ਹੈ। libsql-swift ਉੱਤੇ ਤਦ ਹੀ ਮੁੜ ਵਿਚਾਰ ਹੋਵੇਗਾ ਜੇ Turso ecosystem—ਖ਼ਾਸ ਕਰਕੇ Swift ਲਈ Turso Sync—ਇੱਕ ਅਸਲ product need ਬਣੇ।

Migration mechanical ਹੈ: ਹਰ page ਦਾ element array ਇੱਕ strokes / images table ਵਿੱਚ map ਹੋਵੇਗਾ, ਅਤੇ ਹਰ stroke ਲਈ little-endian binary ਵਿੱਚ ਪ੍ਰਤੀ sampling point 16 bytes ਵਾਲਾ ਇੱਕ immutable BLOB ਹੋਵੇਗਾ। 100 strokes ਦਾ 0.019s benchmark ਦੱਸਦਾ ਹੈ ਕਿ ਇਹ approach viable ਹੈ। Ship ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ migration verify ਕਰੇਗੀ ਕਿ ਪੁਰਾਣੇ package ਅਤੇ ਨਵੇਂ database ਵਿੱਚ element counts ਅਤੇ asset counts ਮਿਲਦੇ ਹਨ, ਅਤੇ crash recovery, WAL bounds ਅਤੇ background flush ਸਾਰੇ pass ਕਰਦੇ ਹਨ।

ਕਦੋਂ ਭੁਗਤਾਨ ਕਰਨਾ ਹੈ

ਇਹ ਫ਼ੈਸਲਾ ਡਾਟਾਬੇਸਾਂ ਬਾਰੇ ਕੋਈ ਫ਼ੈਸਲਾ-ਕੁੰਨ ਰਾਏ ਨਹੀਂ ਹੈ। SQLite concurrent writers, complex queries ਅਤੇ shared state ਉੱਤੇ crash recovery ਸੰਭਾਲਦਾ ਹੈ—ਸਾਡੀ app ਨੂੰ ਇਸ ਵੇਲੇ ਇਨ੍ਹਾਂ ਵਿੱਚੋਂ ਕੁਝ ਨਹੀਂ ਚਾਹੀਦਾ। ਜਿਨ੍ਹਾਂ ਸਮੱਸਿਆਵਾਂ ਨੂੰ capabilities ਹੱਲ ਕਰਦੀਆਂ ਹਨ, ਉਹ ਆਉਣ ਤੋਂ ਪਹਿਲਾਂ ਉਨ੍ਹਾਂ capabilities ਲਈ ਭੁਗਤਾਨ ਕਰਨਾ net loss ਹੈ।

ਡਾਟਾਬੇਸ ਦੀਆਂ ਲਾਗਤਾਂ—dependency, WAL management, adapter layer ਅਤੇ binary size—library link ਹੋਣ ਦੇ ਪਲ ਤੋਂ ਸ਼ੁਰੂ ਹੋ ਜਾਂਦੀਆਂ ਹਨ। ਲਾਭ ਉਸ ਵੇਲੇ ਸ਼ੁਰੂ ਹੁੰਦੇ ਹਨ ਜਦੋਂ app ਕੋਲ ਚਲਾਉਣ ਲਈ queries, ਸੰਭਾਲਣ ਲਈ indexes ਜਾਂ coordinate ਕਰਨ ਲਈ concurrent writers ਹੋਣ। ਇਸ ਪੜਾਅ ਉੱਤੇ ਇਨ੍ਹਾਂ ਤਿੰਨਾਂ ਵਿੱਚੋਂ ਕੋਈ ਨਹੀਂ ਹੈ।

ਸਾਡੀ storage evolution ਦਾ ਹਰ ਕਦਮ ਸਿਰਫ਼ ਉਨ੍ਹਾਂ ਸਮੱਸਿਆਵਾਂ ਲਈ ਭੁਗਤਾਨ ਕਰੇਗਾ ਜੋ ਪਹਿਲਾਂ ਹੀ ਸਾਹਮਣੇ ਆ ਚੁੱਕੀਆਂ ਹਨ। Page-level snapshots ਅੱਜ ਦੀ ਸਮੱਸਿਆ ਲਈ ਭੁਗਤਾਨ ਕਰਦੇ ਹਨ: ਪੂਰੀ file rewrite ਕੀਤੇ ਬਿਨਾਂ multi-page documents save ਕਰਨਾ। ਜੇ write amplification ਮਾਪਣਯੋਗ ਬਣੇ, ਤਾਂ append journal ਉਸ ਸਮੱਸਿਆ ਲਈ ਭੁਗਤਾਨ ਕਰੇਗਾ। ਜੇ search ਜਾਂ sync product need ਬਣੇ, ਤਾਂ database ਉਸ ਸਮੱਸਿਆ ਲਈ ਭੁਗਤਾਨ ਕਰੇਗਾ।

Package structure, manifest ਅਤੇ document schema ਕਿਸੇ storage engine ਨਾਲ ਬੱਝੇ ਨਹੀਂ ਹਨ। Boundaries ਸਹੀ ਥਾਂ ਉੱਤੇ ਹੋਣ ਕਰਕੇ switching costs ਘੱਟ ਹਨ। ਜਿਸ ਦਿਨ ਸਾਨੂੰ database ਦੀ ਸੱਚਮੁੱਚ ਲੋੜ ਪਵੇਗੀ, ਅਸੀਂ ਇਸ ਨੂੰ ਇੱਕ ਖ਼ਾਸ, ਪਹਿਲਾਂ ਹੀ ਮਾਪੀ ਹੋਈ ਸਮੱਸਿਆ ਲਈ ਅਪਣਾਵਾਂਗੇ—ਕਿਸੇ ਕਲਪਨਾਤਮਕ ਸਮੱਸਿਆ ਲਈ ਨਹੀਂ।