SQLite Deep Dive (Part-5) — Freelist နဲ့ Space Management

Data တွေကို DELETE လုပ်လိုက်တာနဲ့ Database file ကြီးက သေးသွားမယ်လို့ ထင်နေတတ်ကြပါတယ်။ တကယ်တမ်းကတော့ လုံးဝ မသေးသွားပါဘူး။ ဘာကြောင့် ဒီလိုဖြစ်ရသလဲဆိုတာနဲ့ SQLite က နောက်ကွယ်မှာ ဘာတွေ တကယ်လုပ်နေသလဲဆိုတာကို နားလည်သွားရင် Delete-heavy workload တွေကို အရင်လို မြင်တော့မှာ မဟုတ်ပါဘူး။

SQLite Deep Dive.png

“ပျောက်သွားပြီ” ဆိုတာနဲ့ ပတ်သက်တဲ့ ပြဿနာ

DELETE FROM users WHERE id = 42 လို့ run လိုက်တဲ့အချိန်မှာ .db file ထဲမှာ တစ်ခုခုတော့ ပြောင်းလဲသွားပါတယ်။ ဒါပေမဲ့ SQLite ဟာ File ကို သေးအောင် မလုပ်သလို၊ Data အလယ်မှာ အပေါက်ဖောက်တာမျိုးလည်း မလုပ်ပါဘူး။ အဲဒီ Row ကို ကိုယ်စားပြုခဲ့တဲ့ Byte တွေဟာ Disk ပေါ်က အရင်နေရာမှာပဲ ဆက်ရှိနေပါသေးတယ်။ ပြောင်းလဲသွားတာကတော့ SQLite ဟာ အတွင်းပိုင်းမှာ မှတ်ချက်တစ်ခုပဲ ရေးလိုက်တာပါ ။“ဒီနေရာဟာ အခု Reuse လုပ်လို့ ရပြီ” ဆိုတာပါပဲ။ အဲဒီမှတ်ချက်ဟာ Freelist ထဲမှာ နေထိုင်ပါတယ်။


Database ကို Page များဖြင့် ဖွဲ့စည်းခြင်း

SQLite Database File တစ်ခုဆိုတာ Row တွေ တစ်ကြောင်းပြီးတစ်ကြောင်း စီလျှောက်ထားတဲ့ Stream တစ်ခု မဟုတ်ပါဘူး။ တကယ်တမ်းကတော့ အရွယ်အစားတူညီတဲ့ Fixed-size Pages တွေရဲ့ Sequence တစ်ခုပဲ ဖြစ်ပါတယ်။ ပုံမှန်အားဖြင့် Page တစ်ခုကို 4096 bytes ရှိတတ်ပြီး လိုအပ်သလို Configure လုပ်နိုင်ပါတယ်။

Database ထဲမှာရှိတဲ့ Data တိုင်း၊ Index တိုင်းနဲ့ အတွင်းပိုင်း bookkeeping တိုင်းဟာ ဒီ Page တွေပေါ်မှာပဲ နေထိုင်ကြတာပါ။ အနှစ်သာရအားဖြင့် Database ဆိုတာ ဒီ Page တွေကို စနစ်တကျ စီမံထားတဲ့ Collection တစ်ခုပါပဲ။

SQLite ဟာ အသစ်တစ်ခုခုကို သိမ်းဆည်းဖို့ လိုအပ်လာတဲ့အခါ File ရဲ့ အဆုံးမှာ Byte တွေကို တစ်စက်ချင်း လိုက်ကပ်နေတာမျိုး မဟုတ်ဘဲ Page တစ်ခုစာ နေရာကို တစ်ခါတည်း Allocate လုပ်လိုက်ပါတယ်။ အလားတူပဲ တစ်ခုခုကို ဖယ်ရှားဖို့ မလိုတော့တဲ့အခါမှာလည်း File ကို Truncate ပြီး သေးအောင် မလုပ်ပါဘူး။ အဲဒီအစား Page တစ်ခုကို Release လုပ်လိုက်ပြီး အဲဒီ Page ဟာ Freelist ပေါ်ကို ရောက်သွားပါတယ်။

ဒါဟာ Notebook တစ်အုပ်ထဲက စာမျက်နှာတစ်ခုကို ဖြတ်မျဉ်းသားလိုက်တာနဲ့ အလွန်ဆင်တူပါတယ်။ Notebook ကြီးက ပိုတိုသွားတာ မဟုတ်ဘဲ အဲဒီစာမျက်နှာကို “ဗလာ၊ နောက်တစ်ခါ ပြန်ရေးလို့ရပြီ” လို့ Mark လုပ်ထားလိုက်ရုံပါပဲ။


Freelist - SQLite ရဲ့ Recycling System

Freelist ဆိုတာ SQLite က အသုံးမလိုတော့လို့ Free ဖြစ်သွားပြီး နောက်တစ်ကြိမ် ပြန်လည်အသုံးပြု (Reuse) လို့ ရနေတဲ့ Page တွေကို စနစ်တကျ မှတ်တမ်းတင်ထားတဲ့ Internal Mechanism တစ်ခု ဖြစ်ပါတယ်။ သူဟာ အခြေခံအားဖြင့် နှစ်ပိုင်းနဲ့ ဖွဲ့စည်းထားတာပါ။ Trunk pages နဲ့ Leaf pages တို့ပဲ ဖြစ်ပါတယ်။

Page တစ်ခုဟာ အသုံးမလိုတော့လို့ Free ဖြစ်သွားတဲ့အခါ သူ့ကို Freelist ထဲကို ထည့်လိုက်ပါတယ်။ ထူးခြားတာက Freelist ကိုယ်တိုင်ဟာလည်း Database File ထဲမှာပဲ ရှိနေတာပါ။ SQLite ဟာ Metadata တွေ သိမ်းဖို့အတွက် သီးခြား File တစ်ခုမှ ထပ်မသုံးပါဘူး။ Database ရဲ့ အစ (Page 1 ရဲ့ Header) မှာတင် Freelist ရဲ့ ပထမဆုံး Trunk Page ကို ညွှန်ပြတဲ့ Pointer တစ်ခုနဲ့ လက်ရှိ Free ဖြစ်နေတဲ့ Page အရေအတွက် စုစုပေါင်းကို အတိအကျ မှတ်သားထားပါတယ်။

Trunk Page တစ်ခုမှာ အဓိက အစိတ်အပိုင်း နှစ်ခု ပါဝင်ပါတယ်-

  1. Trunk Pointer: နောက်ထပ် Trunk Page တစ်ခုကို ချိတ်ဆက်ပေးတဲ့ Pointer။
  2. Leaf Pointers: အမှန်တကယ် Free ဖြစ်သွားပြီး ပြန်သုံးဖို့ စောင့်နေတဲ့ Leaf Page တွေကို ညွှန်ပြထားတဲ့ Pointer List။

Leaf Page တွေဆိုတာကတော့ အသုံးဝင်တဲ့ Data တွေ မရှိတော့ဘဲ Reclaim ခံရဖို့ စောင့်ဆိုင်းနေတဲ့ နေရာလွတ်တွေပဲ ဖြစ်ပါတယ်။

SQLite ဟာ Page အသစ်တစ်ခု လိုအပ်လာတဲ့အခါ (ဥပမာ- Row အသစ်တစ်ခု ထည့်ဖို့ နေရာလိုလာတဲ့အခါ) File ကို ချက်ချင်း ကြီးချဲ့ဖို့ မကြိုးစားပါဘူး။ အရင်ဆုံး Freelist ကို စစ်ဆေးပါတယ်။ Freelist ထဲမှာ အားနေတဲ့ Page ရှိနေရင် အဲဒီထဲက တစ်ခုကို ယူတယ်၊ Reset လုပ်တယ်၊ ပြီးတော့ အသစ်အနေနဲ့ ပြန်သုံးလိုက်ပါတယ်။ Freelist ထဲမှာ နေရာလွတ် လုံးဝမရှိတော့မှသာ SQLite ဟာ File ရဲ့ အဆုံးမှာ Page အသစ်တစ်ခုကို ထပ်တိုး (Append) လုပ်တာပါ။

ဒီလို ဒီဇိုင်းဆွဲထားရခြင်းဟာ SQLite ရဲ့ မူလရည်ရွယ်ချက်ဖြစ်တဲ့ Reliability နဲ့ အမြဲဆက်စပ်နေပါတယ်။ Fragmentation ကို ရှောင်ရှားနိုင်သလို၊ မလိုအပ်တဲ့ I/O လုပ်ဆောင်ချက်တွေကိုလည်း လျှော့ချပေးပါတယ်။ အရေးအကြီးဆုံးကတော့ Database File တစ်ခုတည်းနဲ့တင် အရာရာ ပြီးပြည့်စုံနေစေတဲ့ Self-contained Format ကို ထိန်းသိမ်းထားနိုင်ခြင်းပဲ ဖြစ်ပါတယ်။


File အရွယ်အစား ဘာကြောင့် မလျော့သွားတာလဲ? (Storage Behavior)

ဒါဟာ Developer တော်တော်များများ မျှော်လင့်မထားဘဲ ထိမိတတ်တဲ့ အချက်ဖြစ်ပါတယ်။

ဥပမာအားဖြင့် Database ဟာ 500 MB အထိ ကြီးထွားလာတယ်ဆိုပါစို့။ Row တွေရဲ့ 60% ကို ဖျက်ပစ်မယ့် Cleanup Job တစ်ခုကို Run လိုက်တယ်။ ပြီးတဲ့အခါ စိတ်လှုပ်ရှားစွာနဲ့ File Size ကို စစ်ကြည့်လိုက်တဲ့အခါ 500 MB ကနေ လုံးဝ ယုတ်လျော့မသွားတာကို တွေ့ရပါလိမ့်မယ်။

ဒါဟာ SQLite ရဲ့ မှန်ကန်တဲ့ Behavior သာ ဖြစ်ပါတယ်။ Freed ဖြစ်သွားတဲ့ Page တွေဟာ အခုအချိန်မှာ Freelist ပေါ်ကို ရောက်ရှိနေပြီး Data အသစ်တွေ ထပ်ဝင်လာရင် Reclaim ဖို့ စောင့်ဆိုင်းနေတာပါ။ SQLite ရဲ့ အမြင်ကနေ ကြည့်ရင်တော့ အလုပ်ပြီးသွားပြီ။ Data တွေ ပျောက်သွားပြီ၊ နေရာလွတ်ကိုလည်း စာရင်းမှတ်ပြီးပြီ၊ နောက်တစ်ခါ INSERT လုပ်ရင် ဒီနေရာတွေကိုပဲ ပြန်သုံးတော့မှာပါ။

ဒါပေမဲ့ Disk Usage ကို စောင့်ကြည့်နေရတာမျိုး၊ Database File ကို တစ်နေရာရာကို Transfer ဖို့လိုအပ်နေတာမျိုး၊ ဒါမှမဟုတ် တကယ်ကို Space ပြန်ရချင်လို့ Data ဖျက်ခဲ့တာမျိုး ဆိုရင်တော့ ဒီအပြုအမူဟာ ပြဿနာတစ်ခု ဖြစ်လာနိုင်ပါတယ်။ ဒီနေရာမှာပဲ VACUUM ဆိုတာ ဝင်လာရခြင်း ဖြစ်ပါတယ်။


VACUUM - Space ပြန်လည်ရယူခြင်း

VACUUM ဆိုတာ SQLite ရဲ့ Full Defragmentation Operation တစ်ခုပါ။ ဒါကို Run လိုက်တဲ့အခါ SQLite ဟာ Database တစ်ခုလုံးကို File အသစ်တစ်ခုထဲမှာ ပြန်လည်ရေးသားပါတယ်။ လက်ရှိ သုံးနေဆဲ (Live) ဖြစ်တဲ့ Page တွေကိုသာ Copy ကူးယူတာ ဖြစ်တဲ့အတွက် Freelist တွေ မပါတော့ဘူး၊ နေရာလွတ်တွေ မရှိတော့ဘူး။ ပြီးတဲ့အခါမှာတော့ ဖိုင်အဟောင်းကို ဖိုင်အသစ်နဲ့ အစားထိုးလိုက်ပါတယ်။

PRAGMA auto_vacuum = FULL;

ရလဒ်ကတော့ Live Data ပမာဏကိုသာ တကယ်ဖော်ပြနေတဲ့ သေးငယ်တဲ့ File Size တစ်ခုကို ရရှိလာတာပါပဲ။ ဒါပေမဲ့ သတိထားဖို့က VACUUM ဟာ Run နေစဉ်အတွင်းမှာ Database ရဲ့ Second Full Copy တစ်ခုစာ သိမ်းဆည်းဖို့ Disk Space အပို လိုအပ်ပါတယ်။ 2 GB Database ဆိုရင် ယာယီအားဖြင့် 2 GB အပို လိုအပ်မှာ ဖြစ်သလို၊ Run နေစဉ်မှာလည်း Database တစ်ခုလုံးကို Lock ချထားမှာ ဖြစ်ပါတယ်။


AUTO_VACUUM : ကွဲပြားသော Trade-Off တစ်ခု

Full VACUUM ရဲ့ ကြာမြင့်နိုင်တဲ့ Downtime ကို မခံနိုင်တဲ့ အခြေအနေမျိုးအတွက် SQLite ဟာ AUTO_VACUUM ဆိုတဲ့ Feature ကို ပေးထားပါတယ်။ ဒါကို Enable လုပ်ထားတဲ့အခါ SQLite ဟာ File ရဲ့ အဆုံးက Page တွေကို အလယ်က Freed Gap တွေထဲ ရောက်သွားအောင် အလိုအလျောက် ရွှေ့ပေးပါတယ်။ ပြီးတဲ့အခါမှာတော့ File ကို Truncate လိုက်တဲ့အတွက် Live Data ပမာဏနဲ့ File Size ကို အမြဲတမ်း Sync ဖြစ်နေအောင် ထိန်းညှိပေးပါတယ်။

PRAGMA auto_vacuum = FULL;

ဒါဟာ အလွန်အဆင်ပြေတဲ့ ပြောင်းလဲမှုလို့ ထင်ရပေမဲ့ တကယ်တမ်းမှာတော့ Trade-off ရှိပါတယ်။ DELETE Operation တိုင်းမှာ Page တွေကို Shuffle လုပ်ဖို့ I/O အပိုတွေ လိုအပ်လာတာကြောင့်ပါ။ အထူးသဖြင့် Write-heavy ဖြစ်တဲ့ Workload တွေမှာ ဒီ Overhead ဟာ စုပုံလာပြီး Performance ကို ထိခိုက်စေနိုင်ပါတယ်။ ဒါပေမဲ့ File Size ဟာ အမြဲတမ်း အရေးပါနေတဲ့ Mobile App တွေ ဒါမှမဟုတ် Storage ကန့်သတ်ချက်ရှိတဲ့ Embedded System တွေအတွက်တော့ ဒါဟာ အမှန်ကန်ဆုံး ရွေးချယ်မှု ဖြစ်နိုင်ပါတယ်။

နောက်ထပ် စိတ်ဝင်စားစရာကောင်းတဲ့ Option တစ်ခုကတော့ INCREMENTAL Mode ပဲ ဖြစ်ပါတယ်။

PRAGMA auto_vacuum = INCREMENTAL;

ဒါဟာ Full နဲ့ Auto ကြားက အလယ်လမ်းတစ်ခုလို့ ပြောလို့ရပါတယ်။ ဒီ Mode မှာ SQLite ဟာ ဘယ် Page တွေ Free ဖြစ်နေသလဲဆိုတာကို Track လုပ်ထားပေမဲ့ ချက်ချင်းတော့ Free မလုပ်ပေးသေးပါဘူး။ Cleanup လုပ်ငန်းစဉ်ကို သင်ကိုယ်တိုင်က Controlled Increment တွေနဲ့ အောက်ပါ Command သုံးပြီး Trigger လုပ်ပေးရတာပါ။

PRAGMA incremental_vacuum(N);

ဒီနေရာမှာ N ဆိုတာ Free လုပ်မယ့် Page အရေအတွက်ပဲ ဖြစ်ပါတယ်။ ဒီနည်းလမ်းရဲ့ အားသာချက်ကတော့ I/O Cost ကို အချိန်တစ်ခုအတွင်း ဖြန့်ကြဲပစ်နိုင်တာပါပဲ။ Maintenance အလုပ်တွေကို Burst လေးတွေနဲ့ ခံနိုင်ရည်ရှိပေမဲ့ Full VACUUM ကြောင့် အရာအားလုံး Block ဖြစ်သွားတာမျိုးကို မလိုလားတဲ့ Application တွေအတွက် အလွန်အသုံးဝင်တဲ့ Pattern တစ်ခု ဖြစ်ပါတယ်။


Freelist ကို နားလည်ဖို့ Mental Model

ဒါတွေအားလုံးကို ပိုပြီး ပေါ်ပေါ်လွင်လွင် ဖြစ်သွားစေမယ့် Mental Model တစ်ခု ရှိပါတယ်။

Database File ကြီးကို Warehouse ကြီးတစ်ခုလို စဉ်းစားကြည့်ပါ။ အထဲက Shelf တစ်ခုစီဟာ Page တစ်ခုစီ ဖြစ်ပါတယ်။ ကုန်ပစ္စည်း (Data) တွေ ရောက်လာတဲ့အခါမှာ အဲဒီ Shelf တွေပေါ်မှာ တင်ပြီး ဖြည့်ပါတယ်။ ကုန်ပစ္စည်းတွေ ပြန်ထွက်သွားတဲ့အခါ (Delete လုပ်တဲ့အခါ) မှာတော့ Shelf ကြီးကို ဖြုတ်ချပစ်တာမျိုး မလုပ်ပါဘူး။ အဲဒီအစား “ပြန်သုံးလို့ ရနိုင်ပြီ” ဆိုတဲ့ Sticky Note တစ်ခု ကပ်လိုက်ပြီး Warehouse ရဲ့ အရှေ့ဘက်က List ထဲကို အဲဒီ Shelf နံပါတ် ထည့်လိုက်ရုံပါပဲ။

ကုန်ပစ္စည်းအသစ်တွေ ထပ်ရောက်လာတဲ့အခါမှာ List ကို အရင်စစ်ပါတယ်။ အားနေတဲ့ Shelf ရှိနေရင် အဲဒါကိုပဲ ယူသုံးပါတယ်။ List ထဲမှာ ဘာမှမရှိတော့မှသာ Warehouse ရဲ့ အနောက်ဘက်မှာ Shelf အသစ်တစ်ခုကို ထပ်မံ တည်ဆောက်တာ ဖြစ်ပါတယ်။

ဒီ Model နဲ့ ကြည့်ရင် Vacuum အမျိုးမျိုးကို အခုလို မြင်နိုင်ပါတယ်-


ဒီ Design ရဲ့ နောက်ကွယ်က ရည်ရွယ်ချက်က ဘာဖြစ်မလဲ?

“Delete လုပ်တိုင်းမှာ File ကို ဘာလို့ ချက်ချင်း မချုံ့ပေးတာလဲ” လို့ မေးစရာ ရှိလာနိုင်ပါတယ်။

အဖြေကတော့ SQLite ရဲ့ Core Priority တွေဖြစ်တဲ့ Reliability နဲ့ Performance ဆီကိုပဲ ပြန်သွားပါတယ်။ လက်တွေ့ကမ္ဘာရဲ့ ကြမ်းတမ်းတဲ့ အခြေအနေတွေအောက်မှာ အမှားအယွင်းမရှိ အလုပ်လုပ်နိုင်ဖို့အတွက် ဒီလို ဒီဇိုင်းဆွဲထားတာ ဖြစ်ပါတယ်။

Operation တစ်ခု လုပ်ဆောင်နေစဉ်အတွင်းမှာ File ကို Shrink လုပ်တာဟာ မလိုအပ်ဘဲ Complexity တွေကို ပေါင်းထည့်လိုက်သလိုပါပဲ။ Shrink လုပ်နေတုန်း တစ်ဝက်တစ်ပျက်မှာ Fail ဖြစ်သွားရင် ဘာဖြစ်မလဲ? Truncation လုပ်နေတုန်း Power Cut ဖြစ်သွားရင်ရော? ဒါတွေကို ဖြေရှင်းဖို့ ကြိုးစားတာထက် Freed Page တွေကို In-place Freelist အနေနဲ့ပဲ ထားရှိတာက ပိုပြီး စိတ်ချရပါတယ်။ ဒါဟာ Database State ကို အမြဲတမ်း Consistent ဖြစ်နေစေပါတယ်။ Cleanup လုပ်ငန်းစဉ်ဆိုတာလည်း Header ထဲက Freelist Pointer လေးတစ်ခုကို Update လုပ်လိုက်ရုံပါပဲ။ ကျွန်တော်တို့ကိုယ်တိုင် အတိအလင်းမတောင်းဆိုသရွေ့ SQLite ဟာ Data တွေကို ဟိုရွှေ့ဒီရွှေ့ မလုပ်ပါဘူး။

ဒါဟာ Part-0 မှာ ကျွန်တော်တို့ ပြောခဲ့တဲ့ ACID Guarantee တွေနဲ့ တစ်သားတည်းကျနေတဲ့ Engineering Philosophy ပါပဲ။ System တစ်ခုဟာ orrectness ကို အရင်ဦးစားပေးရပါမယ်၊ Optimization ဆိုတာကတော့ ဒုတိယအဆင့်သာ ဖြစ်ပါတယ်။


Practical Takeaways

ဒီ Series ကို ဆက်လက်ဖတ်ရှုသွားတဲ့အခါမှာ SQLite ရဲ့ Space Management နဲ့ ပတ်သက်ပြီး အောက်ပါအချက်တွေကို အမြဲသတိပြုမိဖို့ လိုအပ်ပါတယ်။

ပထမဆုံးအနေနဲ့ Data တွေကို Delete လုပ်လိုက်တာနဲ့ Disk Space တွေ ချက်ချင်းပြန်ရမယ်လို့ လုံးဝ မယူဆပါနဲ့။ တကယ်လို့ Disk Usage ဟာ ကျွန်တော်တို့ရဲ့ Application အတွက် အလွန်အရေးကြီးတယ်ဆိုရင် PRAGMA freelist_count; ကို အသုံးပြုပြီး Freelist Size ဘယ်လောက်ရှိနေပြီလဲဆိုတာကို အမြဲ Monitor လုပ်ပေးသင့်ပါတယ်။

ဒုတိယအချက်ကတော့ VACUUM ရဲ့ အားသာချက်နဲ့ အားနည်းချက်ပါ။ သူဟာ Space ပြန်ရဖို့အတွက် အလွန်စွမ်းဆောင်ရည်ထက်မြက်ပေမဲ့ အလုပ်လုပ်နေစဉ်အတွင်းမှာ Database တစ်ခုလုံးကို Lock ချထားတဲ့ (Blocking) သဘောရှိပါတယ်။ ဒါကြောင့် User အသုံးနည်းတဲ့ Off-peak ကာလတွေမှာပဲ Schedule လုပ်ပြီး Run သင့်ပါတယ်။ တကယ်လို့ Live Database ကို အနှောင့်အယှက်မပေးဘဲ Clean ဖြစ်နေတဲ့ Copy တစ်ခုပဲ သီးသန့်လိုချင်တာဆိုရင်တော့ VACUUM INTO ကို အသုံးပြုဖို့ အကြံပြုလိုပါတယ်။

နောက်ထပ်သတိပြုရမှာက AUTO_VACUUM ပါ။ သူဟာ အလိုအလျောက် Space ချုံ့ပေးနိုင်ပေမဲ့ Write Cost (I/O Overhead) သိသိသာသာ ရှိပါတယ်။ ဒါကြောင့် သူ့ကို Enable မလုပ်ခင်မှာ ကျွန်တော်တို့ရဲ့ Workload အခြေအနေကို အရင်ဆုံး Measure လုပ်ကြည့်ဖို့ လိုအပ်ပါတယ်။

နောက်ဆုံးအနေနဲ့ အရေးကြီးဆုံးအချက်ကတော့ Freelist ဆိုတာ Database Corruption ဖြစ်နေတာ မဟုတ်ပါဘူး။ အသစ်စက်စက် Restore လုပ်ထားတဲ့ Database တွေ ဒါမှမဟုတ် Data အမြောက်အမြား ဖျက်ထားတဲ့ Database တွေမှာ Freelist ကြီးနေတာဟာ ပုံမှန်ပါပဲ။ ဒါဟာ SQLite ကို ဒီဇိုင်းဆွဲထားတဲ့အတိုင်း အတိအကျ အလုပ်လုပ်နေတာသာ ဖြစ်ပါတယ်။


For The Part-6 (The B-Tree Engine)

Part-6 မှာတော့ Structure အရ အလွန်စိတ်ဝင်စားဖို့ကောင်းတဲ့ အပိုင်းကို ကျွန်တော်တို့ ရောက်ရှိလာပါပြီ။

ရှေ့မှာ ပြောခဲ့တဲ့ Freelist က “ဘယ်နေရာမှာ Space ရနိုင်တယ်” ဆိုတာကိုပဲ လမ်းညွှန်ပေးနိုင်တာပါ။ ဒါပေမဲ့ အဲဒီ Page တွေထဲမှာ Data တွေကို တကယ်တမ်း ဘယ်လို Organize လုပ်တယ်၊ Row တစ်ခုချင်းစီကို ဘယ်လို Navigate လုပ်ပြီး ဘယ်လိုမျိုး စနစ်တကျ Store ဖြစ်သွားတယ်ဆိုတာကို ဆုံးဖြတ်ပေးတာကတော့ B-Tree ပဲ ဖြစ်ပါတယ်။

ဘယ်တော့မဆို SQLite ကို Run ခဲ့တဲ့ Query တိုင်းရဲ့ အောက်ခြေမှာ တိတ်တဆိတ် အလုပ်လုပ်ပေးနေတဲ့ တကယ့် Machinery Engine ကြီးကတော့ ဒါပါပဲ။ Database တစ်ခုရဲ့ မြန်ဆန်မှုနဲ့ တည်ငြိမ်မှုဟာ ဒီ B-Tree ပေါ်မှာ ဘယ်လောက်အထိ မှီတည်နေသလဲဆိုတာကို နောက်တစ်ပတ်မှာ အသေးစိတ် ဆက်လက်လေ့လာသွားကြပါမယ်။