หากคอลเลกชันมีเอกสารที่มีค่าการจัดทำดัชนีตามลำดับ Cloud Firestore จะจำกัดอัตราการเขียนไว้ที่ 500 การเขียนต่อวินาที หน้านี้อธิบายวิธีการแบ่งส่วนฟิลด์เอกสารเพื่อเอาชนะขีดจำกัดนี้ ขั้นแรก มากำหนดสิ่งที่เราหมายถึงโดย "ฟิลด์ที่จัดทำดัชนีตามลำดับ" และชี้แจงว่าเมื่อใดที่ขีดจำกัดนี้มีผล
ฟิลด์ที่จัดทำดัชนีตามลำดับ
"ฟิลด์ที่จัดทำดัชนีตามลำดับ" หมายถึงคอลเลกชันของเอกสารใด ๆ ที่มีฟิลด์ที่จัดทำดัชนีเพิ่มขึ้นหรือลดลงซ้ำซาก ในหลายกรณี นี่หมายถึงฟิลด์ timestamp
แต่ค่าฟิลด์ที่เพิ่มขึ้นหรือลดลงแบบซ้ำซากสามารถกระตุ้นให้เกิดขีดจำกัดการเขียนที่ 500 การเขียนต่อวินาที
ตัวอย่างเช่น ขีดจำกัดจะมีผลกับคอลเลกชันของเอกสาร user
ที่มี userid
ในช่องที่จัดทำดัชนีไว้ หากแอปกำหนดค่า userid
ดังนี้:
-
1281, 1282, 1283, 1284, 1285, ...
ในทางกลับกัน ไม่ใช่ทุกช่องการ timestamp
จะทริกเกอร์ขีดจำกัดนี้ หากฟิลด์การประทับ timestamp
ติดตามค่าที่แจกแจงแบบสุ่ม ขีดจำกัดการเขียนจะไม่มีผล ค่าที่แท้จริงของฟิลด์ก็ไม่สำคัญเช่นกัน เพียงแต่ว่าฟิลด์นั้นเพิ่มขึ้นหรือลดลงอย่างซ้ำซากจำเจ ตัวอย่างเช่น ชุดค่าฟิลด์ที่เพิ่มขึ้นแบบซ้ำซากจำเจทั้งสองชุดต่อไปนี้จะทริกเกอร์ขีดจำกัดการเขียน:
-
100000, 100001, 100002, 100003, ...
-
0, 1, 2, 3, ...
การแบ่งเขตข้อมูลการประทับเวลา
สมมติว่าแอปของคุณใช้ช่องการ timestamp
ที่เพิ่มขึ้นซ้ำซากจำเจ หากแอปของคุณไม่ได้ใช้ช่องการ timestamp
ในการค้นหาใดๆ คุณสามารถลบขีดจำกัดการเขียน 500 รายการต่อวินาทีออกได้โดยไม่สร้างดัชนีช่องการประทับเวลา หากคุณต้องการฟิลด์ timestamp
สำหรับการสืบค้นของคุณ คุณสามารถแก้ไขขีดจำกัดได้โดยใช้ การประทับเวลาแบบแบ่งส่วน :
- เพิ่มช่อง
shard
ข้างช่องtimestamp
ใช้ค่าที่แตกต่างกัน1..n
สำหรับshard
ชาร์ด สิ่งนี้จะเพิ่มขีดจำกัดการเขียนสำหรับคอลเลกชันเป็น500*n
แต่คุณต้องรวมn
แบบสอบถาม - อัปเดตตรรกะการเขียนของคุณเพื่อ สุ่ม กำหนดค่า
shard
ให้กับแต่ละเอกสาร - อัปเดตคำค้นหาของคุณเพื่อรวมชุดผลลัพธ์ที่แบ่งส่วน
- ปิดใช้งานดัชนีช่องเดียวสำหรับทั้งช่อง
shard
และช่องการtimestamp
ลบดัชนีผสมที่มีอยู่ซึ่งมีฟิลด์timestamp
- สร้างดัชนีผสมใหม่เพื่อรองรับการสืบค้นที่อัปเดตของคุณ ลำดับของช่องในดัชนีมีความสำคัญ และช่องชา
shard
ต้องอยู่ก่อนช่องการtimestamp
ดัชนีใดๆ ที่มีฟิลด์timestamp
จะต้องมีshard
ชาร์ดด้วย
คุณควรใช้การประทับเวลาที่แบ่งกลุ่มเฉพาะในกรณีการใช้งานที่มีอัตราการเขียนแบบยั่งยืนมากกว่า 500 การเขียนต่อวินาที มิฉะนั้น นี่ถือเป็นการเพิ่มประสิทธิภาพก่อนกำหนด การแบ่งฟิลด์ timestamp
จะลบข้อจำกัดการเขียน 500 รายการต่อวินาที แต่ไม่จำเป็นต้องรวมการสืบค้นฝั่งไคลเอ็นต์
ตัวอย่างต่อไปนี้แสดงวิธีการแบ่งเขตข้อมูล timestamp
และวิธีการสืบค้นชุดผลลัพธ์ที่แบ่งส่วน
ตัวอย่างโมเดลข้อมูลและการสืบค้น
ตามตัวอย่าง ลองนึกภาพแอปสำหรับการวิเคราะห์เครื่องมือทางการเงินแบบเกือบจะเรียลไทม์ เช่น สกุลเงิน หุ้นสามัญ และ ETF แอพนี้เขียนเอกสารไปยังคอลเลกชั่น instruments
ดังนี้:
โหนด js
async function insertData() { const instruments = [ { symbol: 'AAA', price: { currency: 'USD', micros: 34790000 }, exchange: 'EXCHG1', instrumentType: 'commonstock', timestamp: Timestamp.fromMillis( Date.parse('2019-01-01T13:45:23.010Z')) }, { symbol: 'BBB', price: { currency: 'JPY', micros: 64272000000 }, exchange: 'EXCHG2', instrumentType: 'commonstock', timestamp: Timestamp.fromMillis( Date.parse('2019-01-01T13:45:23.101Z')) }, { symbol: 'Index1 ETF', price: { currency: 'USD', micros: 473000000 }, exchange: 'EXCHG1', instrumentType: 'etf', timestamp: Timestamp.fromMillis( Date.parse('2019-01-01T13:45:23.001Z')) } ]; const batch = fs.batch(); for (const inst of instruments) { const ref = fs.collection('instruments').doc(); batch.set(ref, inst); } await batch.commit(); }
แอปนี้เรียกใช้คำสั่งและคำสั่งต่อไปนี้ตามฟิลด์ timestamp
:
โหนด js
function createQuery(fieldName, fieldOperator, fieldValue, limit = 5) { return fs.collection('instruments') .where(fieldName, fieldOperator, fieldValue) .orderBy('timestamp', 'desc') .limit(limit) .get(); } function queryCommonStock() { return createQuery('instrumentType', '==', 'commonstock'); } function queryExchange1Instruments() { return createQuery('exchange', '==', 'EXCHG1'); } function queryUSDInstruments() { return createQuery('price.currency', '==', 'USD'); }
insertData() .then(() => { const commonStock = queryCommonStock() .then( (docs) => { console.log('--- queryCommonStock: '); docs.forEach((doc) => { console.log(`doc = ${util.inspect(doc.data(), {depth: 4})}`); }); } ); const exchange1Instruments = queryExchange1Instruments() .then( (docs) => { console.log('--- queryExchange1Instruments: '); docs.forEach((doc) => { console.log(`doc = ${util.inspect(doc.data(), {depth: 4})}`); }); } ); const usdInstruments = queryUSDInstruments() .then( (docs) => { console.log('--- queryUSDInstruments: '); docs.forEach((doc) => { console.log(`doc = ${util.inspect(doc.data(), {depth: 4})}`); }); } ); return Promise.all([commonStock, exchange1Instruments, usdInstruments]); });
หลังจากการค้นคว้าข้อมูล คุณพบว่าแอปจะได้รับการอัพเดตอุปกรณ์ระหว่าง 1,000 ถึง 1,500 รายการต่อวินาที ซึ่งเกินกว่าการเขียน 500 รายการต่อวินาทีที่อนุญาตสำหรับคอลเลกชันที่มีเอกสารที่มีฟิลด์การประทับเวลาจัดทำดัชนี ในการเพิ่มปริมาณงานเขียน คุณต้องมีค่าส่วนแบ่ง 3 ค่า MAX_INSTRUMENT_UPDATES/500 = 3
ตัวอย่างนี้ใช้ค่าชาร์ด x
, y
และ z
คุณยังสามารถใช้ตัวเลขหรืออักขระอื่นๆ สำหรับค่าชาร์ดของคุณได้
การเพิ่มฟิลด์ชาร์ด
เพิ่มช่อง shard
ลงในเอกสารของคุณ ตั้งค่า shard
ส่วนแบ่งเป็นค่า x
, y
หรือ z
ซึ่งจะเพิ่มขีดจำกัดการเขียนในคอลเลกชันเป็น 1,500 การเขียนต่อวินาที
โหนด js
// Define our 'K' shard values const shards = ['x', 'y', 'z']; // Define a function to help 'chunk' our shards for use in queries. // When using the 'in' query filter there is a max number of values that can be // included in the value. If our number of shards is higher than that limit // break down the shards into the fewest possible number of chunks. function shardChunks() { const chunks = []; let start = 0; while (start < shards.length) { const elements = Math.min(MAX_IN_VALUES, shards.length - start); const end = start + elements; chunks.push(shards.slice(start, end)); start = end; } return chunks; } // Add a convenience function to select a random shard function randomShard() { return shards[Math.floor(Math.random() * Math.floor(shards.length))]; }
async function insertData() { const instruments = [ { shard: randomShard(), // add the new shard field to the document symbol: 'AAA', price: { currency: 'USD', micros: 34790000 }, exchange: 'EXCHG1', instrumentType: 'commonstock', timestamp: Timestamp.fromMillis( Date.parse('2019-01-01T13:45:23.010Z')) }, { shard: randomShard(), // add the new shard field to the document symbol: 'BBB', price: { currency: 'JPY', micros: 64272000000 }, exchange: 'EXCHG2', instrumentType: 'commonstock', timestamp: Timestamp.fromMillis( Date.parse('2019-01-01T13:45:23.101Z')) }, { shard: randomShard(), // add the new shard field to the document symbol: 'Index1 ETF', price: { currency: 'USD', micros: 473000000 }, exchange: 'EXCHG1', instrumentType: 'etf', timestamp: Timestamp.fromMillis( Date.parse('2019-01-01T13:45:23.001Z')) } ]; const batch = fs.batch(); for (const inst of instruments) { const ref = fs.collection('instruments').doc(); batch.set(ref, inst); } await batch.commit(); }
กำลังสอบถามการประทับเวลาที่ชาร์ด
การเพิ่ม shard
ส่วนแบ่งข้อมูลกำหนดให้คุณต้องอัปเดตคำค้นหาของคุณเพื่อรวมผลลัพธ์ที่แบ่งส่วน:
โหนด js
function createQuery(fieldName, fieldOperator, fieldValue, limit = 5) { // For each shard value, map it to a new query which adds an additional // where clause specifying the shard value. return Promise.all(shardChunks().map(shardChunk => { return fs.collection('instruments') .where('shard', 'in', shardChunk) // new shard condition .where(fieldName, fieldOperator, fieldValue) .orderBy('timestamp', 'desc') .limit(limit) .get(); })) // Now that we have a promise of multiple possible query results, we need // to merge the results from all of the queries into a single result set. .then((snapshots) => { // Create a new container for 'all' results const docs = []; snapshots.forEach((querySnapshot) => { querySnapshot.forEach((doc) => { // append each document to the new all container docs.push(doc); }); }); if (snapshots.length === 1) { // if only a single query was returned skip manual sorting as it is // taken care of by the backend. return docs; } else { // When multiple query results are returned we need to sort the // results after they have been concatenated. // // since we're wanting the `limit` newest values, sort the array // descending and take the first `limit` values. By returning negated // values we can easily get a descending value. docs.sort((a, b) => { const aT = a.data().timestamp; const bT = b.data().timestamp; const secondsDiff = aT.seconds - bT.seconds; if (secondsDiff === 0) { return -(aT.nanoseconds - bT.nanoseconds); } else { return -secondsDiff; } }); return docs.slice(0, limit); } }); } function queryCommonStock() { return createQuery('instrumentType', '==', 'commonstock'); } function queryExchange1Instruments() { return createQuery('exchange', '==', 'EXCHG1'); } function queryUSDInstruments() { return createQuery('price.currency', '==', 'USD'); }
insertData() .then(() => { const commonStock = queryCommonStock() .then( (docs) => { console.log('--- queryCommonStock: '); docs.forEach((doc) => { console.log(`doc = ${util.inspect(doc.data(), {depth: 4})}`); }); } ); const exchange1Instruments = queryExchange1Instruments() .then( (docs) => { console.log('--- queryExchange1Instruments: '); docs.forEach((doc) => { console.log(`doc = ${util.inspect(doc.data(), {depth: 4})}`); }); } ); const usdInstruments = queryUSDInstruments() .then( (docs) => { console.log('--- queryUSDInstruments: '); docs.forEach((doc) => { console.log(`doc = ${util.inspect(doc.data(), {depth: 4})}`); }); } ); return Promise.all([commonStock, exchange1Instruments, usdInstruments]); });
อัปเดตคำจำกัดความของดัชนี
หากต้องการลบข้อจำกัดการเขียน 500 รายการต่อวินาที ให้ลบดัชนีช่องเดียวและดัชนีรวมที่มีอยู่ซึ่งใช้ช่องการ timestamp
ลบคำจำกัดความดัชนีผสม
คอนโซล Firebase
เปิดหน้า ดัชนีคอมโพสิตของ Cloud Firestore ในคอนโซล Firebase
สำหรับแต่ละดัชนีที่มีฟิลด์
timestamp
ให้คลิกปุ่ม แล้วคลิก Delete
คอนโซล GCP
ในคอนโซล Google Cloud Platform ให้ไปที่หน้า ฐานข้อมูล
เลือกฐานข้อมูลที่ต้องการจากรายการฐานข้อมูล
ในเมนูนำทาง คลิก ดัชนี จากนั้นคลิกแท็บ คอมโพสิต
ใช้ฟิลด์ตัว กรอง เพื่อค้นหาคำจำกัดความของดัชนีที่มีฟิลด์
timestamp
สำหรับแต่ละดัชนีเหล่านี้ ให้คลิกปุ่ม
แล้วคลิก Delete
Firebase CLI
- หากคุณยังไม่ได้ตั้งค่า Firebase CLI ให้ทำตามคำแนะนำเหล่านี้เพื่อติดตั้ง CLI และเรียกใช้คำสั่ง
firebase init
ในระหว่างคำสั่งinit
ตรวจสอบให้แน่ใจว่าได้เลือกFirestore: Deploy rules and create indexes for Firestore
- ในระหว่างการตั้งค่า Firebase CLI จะดาวน์โหลดคำจำกัดความดัชนีที่มีอยู่ของคุณไปยังไฟล์ชื่อ
firestore.indexes.json
ตามค่าเริ่มต้น ลบคำจำกัดความดัชนีใดๆ ที่มีฟิลด์
timestamp
ตัวอย่างเช่น:{ "indexes": [ // Delete composite index definition that contain the timestamp field { "collectionGroup": "instruments", "queryScope": "COLLECTION", "fields": [ { "fieldPath": "exchange", "order": "ASCENDING" }, { "fieldPath": "timestamp", "order": "DESCENDING" } ] }, { "collectionGroup": "instruments", "queryScope": "COLLECTION", "fields": [ { "fieldPath": "instrumentType", "order": "ASCENDING" }, { "fieldPath": "timestamp", "order": "DESCENDING" } ] }, { "collectionGroup": "instruments", "queryScope": "COLLECTION", "fields": [ { "fieldPath": "price.currency", "order": "ASCENDING" }, { "fieldPath": "timestamp", "order": "DESCENDING" } ] }, ] }
ปรับใช้คำจำกัดความดัชนีที่อัปเดตของคุณ:
firebase deploy --only firestore:indexes
อัปเดตข้อกำหนดดัชนีเขตข้อมูลเดี่ยว
คอนโซล Firebase
เปิดหน้า ดัชนีฟิลด์เดี่ยวของ Cloud Firestore ในคอนโซล Firebase
คลิก เพิ่มการยกเว้น
สำหรับ รหัสคอลเลก ชัน ให้ป้อน
instruments
สำหรับ Field path ให้ป้อนtimestamp
ภายใต้ ขอบเขตการค้นหา เลือกทั้ง คอลเลกชัน และ กลุ่มคอลเลกชัน
คลิก ถัดไป
สลับการตั้งค่าดัชนีทั้งหมดเป็น Disabled คลิก บันทึก
ทำซ้ำขั้นตอนเดียวกันสำหรับช่อง
shard
คอนโซล GCP
ในคอนโซล Google Cloud Platform ให้ไปที่หน้า ฐานข้อมูล
เลือกฐานข้อมูลที่ต้องการจากรายการฐานข้อมูล
ในเมนูนำทาง คลิก ดัชนี จากนั้นคลิกแท็บเขต ข้อมูลเดี่ยว
คลิกแท็บ เขตข้อมูลเดี่ยว
คลิก เพิ่มการยกเว้น
สำหรับ รหัสคอลเลก ชัน ให้ป้อน
instruments
สำหรับ Field path ให้ป้อนtimestamp
ภายใต้ ขอบเขตการค้นหา เลือกทั้ง คอลเลกชัน และ กลุ่มคอลเลกชัน
คลิก ถัดไป
สลับการตั้งค่าดัชนีทั้งหมดเป็น Disabled คลิก บันทึก
ทำซ้ำขั้นตอนเดียวกันสำหรับช่อง
shard
Firebase CLI
เพิ่มข้อมูลต่อไปนี้ลงในส่วน
fieldOverrides
ของไฟล์คำจำกัดความดัชนีของคุณ:{ "fieldOverrides": [ // Disable single-field indexing for the timestamp field { "collectionGroup": "instruments", "fieldPath": "timestamp", "indexes": [] }, ] }
ปรับใช้คำจำกัดความดัชนีที่อัปเดตของคุณ:
firebase deploy --only firestore:indexes
สร้างดัชนีคอมโพสิตใหม่
หลังจากลบดัชนีก่อนหน้าทั้งหมดที่มี timestamp
แล้ว ให้กำหนดดัชนีใหม่ที่แอปของคุณต้องการ ดัชนีใดๆ ที่มีฟิลด์ timestamp
จะต้องมี shard
ชาร์ดด้วย ตัวอย่างเช่น เพื่อสนับสนุนการสืบค้นข้างต้น ให้เพิ่มดัชนีต่อไปนี้:
ของสะสม | ฟิลด์ที่จัดทำดัชนีแล้ว | ขอบเขตการค้นหา |
---|---|---|
เครื่องมือ | shard, price.currency, เวลาประทับ | ของสะสม |
เครื่องมือ | shard, การแลกเปลี่ยน , การประทับเวลา | ของสะสม |
เครื่องมือ | shard, ประเภทเครื่องดนตรี, การประทับเวลา | ของสะสม |
ข้อความแสดงข้อผิดพลาด
คุณสามารถสร้างดัชนีเหล่านี้ได้โดยการเรียกใช้แบบสอบถามที่อัปเดต
การค้นหาแต่ละรายการส่งคืนข้อความแสดงข้อผิดพลาดพร้อมลิงก์เพื่อสร้างดัชนีที่จำเป็นในคอนโซล Firebase
Firebase CLI
เพิ่มดัชนีต่อไปนี้ลงในไฟล์คำจำกัดความดัชนีของคุณ:
{ "indexes": [ // New indexes for sharded timestamps { "collectionGroup": "instruments", "queryScope": "COLLECTION", "fields": [ { "fieldPath": "shard", "order": "DESCENDING" }, { "fieldPath": "exchange", "order": "ASCENDING" }, { "fieldPath": "timestamp", "order": "DESCENDING" } ] }, { "collectionGroup": "instruments", "queryScope": "COLLECTION", "fields": [ { "fieldPath": "shard", "order": "DESCENDING" }, { "fieldPath": "instrumentType", "order": "ASCENDING" }, { "fieldPath": "timestamp", "order": "DESCENDING" } ] }, { "collectionGroup": "instruments", "queryScope": "COLLECTION", "fields": [ { "fieldPath": "shard", "order": "DESCENDING" }, { "fieldPath": "price.currency", "order": "ASCENDING" }, { "fieldPath": "timestamp", "order": "DESCENDING" } ] }, ] }
ปรับใช้คำจำกัดความดัชนีที่อัปเดตของคุณ:
firebase deploy --only firestore:indexes
ทำความเข้าใจการเขียนสำหรับฟิลด์ที่จัดทำดัชนีตามลำดับขีดจำกัด
ขีดจำกัดของอัตราการเขียนสำหรับช่องที่จัดทำดัชนีตามลำดับมาจากวิธีที่ Cloud Firestore เก็บค่าดัชนีและปรับขนาดการเขียนดัชนี สำหรับการเขียนดัชนีแต่ละครั้ง Cloud Firestore จะกำหนดรายการคีย์-ค่าซึ่งจะเชื่อมชื่อเอกสารและค่าของแต่ละช่องที่จัดทำดัชนีเข้าด้วยกัน Cloud Firestore จัดระเบียบรายการดัชนีเหล่านี้ออกเป็นกลุ่มข้อมูลที่เรียกว่า แท็บเล็ต เซิร์ฟเวอร์ Cloud Firestore แต่ละเซิร์ฟเวอร์มีแท็บเล็ตตั้งแต่หนึ่งเครื่องขึ้นไป เมื่อภาระการเขียนไปยังแท็บเล็ตใดแท็บเล็ตหนึ่งสูงเกินไป Cloud Firestore จะปรับขนาดในแนวนอนโดยแยกแท็บเล็ตออกเป็นแท็บเล็ตขนาดเล็กและกระจายแท็บเล็ตใหม่ไปยังเซิร์ฟเวอร์ Cloud Firestore ต่างๆ
Cloud Firestore วางรายการดัชนีปิดตามพจนานุกรมบนแท็บเล็ตเดียวกัน หากค่าดัชนีในแท็บเล็ตอยู่ใกล้กันเกินไป เช่น สำหรับช่องการประทับเวลา Cloud Firestore จะไม่สามารถแยกแท็บเล็ตออกเป็นแท็บเล็ตที่มีขนาดเล็กลงได้อย่างมีประสิทธิภาพ สิ่งนี้จะสร้างฮอตสปอตที่แท็บเล็ตตัวเดียวได้รับปริมาณข้อมูลมากเกินไป และการดำเนินการอ่านและเขียนไปยังฮอตสปอตจะช้าลง
การแบ่งช่องการประทับเวลาทำให้ Cloud Firestore สามารถแบ่งภาระงานบนแท็บเล็ตหลายเครื่องได้อย่างมีประสิทธิภาพ แม้ว่าค่าของช่องการประทับเวลาอาจอยู่ใกล้กัน แต่ชาร์ดและค่าดัชนีที่ต่อกันจะทำให้ Cloud Firestore มีพื้นที่เพียงพอระหว่างรายการดัชนีเพื่อแยกรายการออกเป็นหลายแท็บเล็ต
อะไรต่อไป
- อ่าน แนวทางปฏิบัติที่ดีที่สุดสำหรับการออกแบบตามขนาด
- สำหรับกรณีที่มีอัตราการเขียนสูงในเอกสารเดียว โปรดดู ตัวนับที่รบกวน
- ดู ขีดจำกัดมาตรฐานสำหรับ Cloud Firestore