ข้ามไปยังเนื้อหา

Migrations และ Blobs

database IndexedDB ทุกตัวมี หมายเลขเวอร์ชัน เป็นจำนวนเต็ม เมื่อคุณเรียก indexedDB.open(name, version) ด้วยเวอร์ชันที่สูงกว่าที่จัดเก็บอยู่ในปัจจุบัน browser จะยิงเหตุการณ์ upgradeneeded ก่อน success นี่คือ สถานที่เดียว ที่คุณสามารถสร้างหรือลบ object store และ index ได้

const request = indexedDB.open('my-db', 2);
request.onupgradeneeded = (event) => {
const db = event.target.result;
const oldVersion = event.oldVersion; // 0 if brand-new, 1 if upgrading from v1
// ...create stores based on oldVersion
};

คุณสมบัติสำคัญคือ event.oldVersion: ค่านี้บอกให้คุณรู้ว่า database อยู่ที่เวอร์ชันใด ก่อน การเปิดครั้งนี้ database ที่สร้างใหม่จะรายงาน oldVersion === 0

แทนที่จะใช้ if/else เพียงอันเดียว ให้ใช้การตรวจสอบแบบน้อยกว่าต่อเนื่องกัน เพื่อให้ database ที่อยู่ในเวอร์ชันก่อนหน้าใด ๆ รัน upgrade ทั้งหมดที่ขาดหายไป:

request.onupgradeneeded = (event) => {
const db = event.target.result;
const { oldVersion } = event;
if (oldVersion < 1) {
// v0 → v1: create the initial 'users' store
db.createObjectStore('users', { keyPath: 'id' });
}
if (oldVersion < 2) {
// v1 → v2: add a 'files' store for binary data
db.createObjectStore('files', { keyPath: 'name' });
}
// if oldVersion < 3: future migration goes here
};

ผู้ใช้ที่อัปเกรดจาก v0 จะรัน ทั้งสอง บล็อก ผู้ใช้ที่อัปเกรดจาก v1 จะรันเฉพาะบล็อกที่สอง ผู้ใช้ที่อยู่บน v2 อยู่แล้วจะไม่รันบล็อกใดเลย

หนึ่งในข้อได้เปรียบที่ยิ่งใหญ่ที่สุดของ IndexedDB เหนือ Web Storage คือความสามารถในการจัดเก็บค่า JavaScript ที่ structured-cloneable — รวมถึง object Blob และ Fileโดยไม่ต้องทำ serialisation ใด ๆ

// Create a Blob in memory
const blob = new Blob(['Hello, binary world!'], { type: 'text/plain' });
// Store it directly — no JSON.stringify, no base64 encoding
const tx = db.transaction('files', 'readwrite');
tx.objectStore('files').put({ name: 'greeting.txt', data: blob });

เมื่อคุณอ่าน record กลับมา คุณจะได้รับ Blob จริง ๆ พร้อมกับ method ทั้งหมดที่ยังคงสมบูรณ์:

const getReq = db.transaction('files').objectStore('files').get('greeting.txt');
getReq.onsuccess = async () => {
const record = getReq.result;
const text = await record.data.text(); // Blob.prototype.text() returns a Promise
console.log(text); // 'Hello, binary world!'
};

เทคนิคเดียวกันนี้ใช้ได้กับ object File (ที่เป็น subclass ของ Blob), ArrayBuffer, ImageBitmap และประเภทอื่น ๆ ที่ structured-cloneable

ตัวอย่างด้านล่างเปิด demo-advanced-migrations ที่ เวอร์ชัน 2 หาก database ยังไม่มีอยู่ onupgradeneeded จะรันทั้งสองบล็อก migration (oldVersion เป็น 0) จากนั้นจะจัดเก็บ record ผู้ใช้และ Blob ดึง Blob กลับมา และอ่านเนื้อหาข้อความก่อนล้างข้อมูล

Browser Storage
แนวคิดรายละเอียด
event.oldVersionหมายเลขเวอร์ชันก่อนการเปิดครั้งนี้ 0 สำหรับ database ที่สร้างใหม่
การตรวจสอบ if (oldVersion < N) แบบต่อเนื่องรับประกันว่า migration แบบเพิ่มทีละขั้นจะไม่ถูกข้ามไป
Blob / File ใน IDBจัดเก็บและดึงกลับโดยไม่ต้อง serialisation ใด ๆ
Blob.prototype.text()คืนค่า Promise<string> — ใช้ await
ตัวเลือกBenefitCost
จัดเก็บ Blob โดยตรงใน IndexedDBสะดวก ไม่ต้อง serialise เป็น base64 หรือ JSON ประหยัด CPU และขนาดข้อมูลไฟล์ขนาดใหญ่กินโควต้าของ origin อย่างรวดเร็ว อาจเผลอเกิน storage budget โดยไม่รู้ตัว
อัปโหลดไฟล์ขึ้น server แล้วเก็บแค่ URL ใน IndexedDBควบคุมขนาด storage ฝั่ง client ได้ดีกว่า ไม่พึ่งพาโควต้าของ browserต้องพึ่ง network ตอนอ่านไฟล์ ใช้งาน offline ไม่ได้เต็มรูปแบบ
การตรวจสอบ if (oldVersion < N) แบบต่อเนื่องรองรับผู้ใช้ที่อัปเกรดจากเวอร์ชันไหนก็ได้อย่างถูกต้องต้องดูแล migration path เพิ่มขึ้นเรื่อยๆ ตามจำนวนเวอร์ชันที่ผ่านมา โค้ดยาวขึ้นตามเวลา
  • จัดเก็บ Blob ขนาดใหญ่โดยไม่ตรวจสอบโควต้าก่อน — อาจทำให้ origin เกิน storage budget แบบเงียบๆ ควรเช็ค navigator.storage.estimate() ก่อนเขียนไฟล์ขนาดใหญ่
  • เขียน migration ด้วย if/else เดี่ยวๆ แทนการตรวจสอบแบบต่อเนื่อง — ทำให้ผู้ใช้ที่อัปเกรดข้ามหลายเวอร์ชันพลาด migration บางขั้นตอนไป ควรใช้ if (oldVersion < N) เรียงต่อกันเสมอ
  • แปลง Blob เป็น base64 string ก่อนเก็บโดยไม่จำเป็น — IndexedDB รองรับ structured clone ของ Blob/File อยู่แล้ว การแปลงเป็น base64 เพิ่ม overhead ทั้ง CPU และขนาดข้อมูลโดยไม่มีประโยชน์

💡 ตัวอย่างจากของจริง

Figma — เก็บ asset และ thumbnail ของไฟล์ design เป็น Blob ใน local cache พร้อม migration schema แบบมีเวอร์ชันเมื่อโครงสร้างข้อมูล cache เปลี่ยนแปลงในแต่ละ release

Excalidraw — จัดเก็บรูปภาพที่ผู้ใช้ paste เข้ามาบน canvas เป็น Blob โดยตรงใน IndexedDB ทำให้ภาพยังอยู่ครบแม้ปิด browser แล้วเปิดใหม่ โดยไม่ต้อง encode เป็น base64

event.oldVersion มีค่าเป็นเท่าใด เมื่อผู้ใช้เปิดฐานข้อมูล IndexedDB เป็นครั้งแรกสุด (ยังไม่เคยมีมาก่อน)?
คุณเปิดฐานข้อมูลที่เวอร์ชัน 3 ผู้ใช้ปัจจุบันมีเวอร์ชัน 1 บล็อก migration ใดควรรันภายใน onupgradeneeded?
คุณควรจัดเก็บออบเจกต์ Blob ใน IndexedDB อย่างไร?
Blob.prototype.text() คืนค่าอะไร?