إذا كانت المجموعة تحتوي على مستندات ذات قيم مفهرسة تسلسلية، فإن 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
وكيفية الاستعلام عن مجموعة نتائج مقسمة.
نموذج البيانات والاستعلامات
على سبيل المثال، تخيل تطبيقًا للتحليل في الوقت الفعلي تقريبًا للأدوات المالية مثل العملات والأسهم العادية وصناديق الاستثمار المتداولة. يقوم هذا التطبيق بكتابة المستندات إلى مجموعة instruments
مثل:
Node.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
:
Node.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]); });
بعد إجراء بعض البحث، حددت أن التطبيق سيتلقى ما بين 1000 و1500 تحديث للأداة في الثانية. وهذا يتجاوز 500 عملية كتابة في الثانية المسموح بها للمجموعات التي تحتوي على مستندات ذات حقول طابع زمني مفهرسة. لزيادة إنتاجية الكتابة، تحتاج إلى 3 قيم للجزء، MAX_INSTRUMENT_UPDATES/500 = 3
. يستخدم هذا المثال قيم الجزء x
و y
و z
. يمكنك أيضًا استخدام الأرقام أو الأحرف الأخرى لقيم الجزء الخاص بك.
إضافة حقل شارد
أضف حقل shard
إلى مستنداتك. قم بتعيين حقل shard
على القيم x
أو y
أو z
مما يرفع حد الكتابة في المجموعة إلى 1500 عملية كتابة في الثانية.
Node.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
أن تقوم بتحديث استعلاماتك لتجميع النتائج المقسمة:
Node.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 Composite Indexes في وحدة تحكم Firebase.
لكل فهرس يحتوي على حقل
timestamp
، انقر فوق الزر ثم انقر فوق حذف .
وحدة تحكم Google Cloud Platform
في Google Cloud Platform Console، انتقل إلى صفحة قواعد البيانات .
قم باختيار قاعدة البيانات المطلوبة من قائمة قواعد البيانات.
في قائمة التنقل، انقر فوق الفهارس ، ثم انقر فوق علامة التبويب مركب .
استخدم حقل التصفية للبحث عن تعريفات الفهرس التي تحتوي على حقل
timestamp
.لكل من هذه الفهارس، انقر فوق الزر
ثم انقر فوق حذف .
Firebase CLI
- إذا لم تقم بإعداد Firebase CLI، فاتبع هذه التوجيهات لتثبيت CLI وتشغيل الأمر
firebase init
. أثناء أمرinit
، تأكد من تحديدFirestore: Deploy rules and create indexes for Firestore
. - أثناء الإعداد، تقوم واجهة سطر أوامر Firebase بتنزيل تعريفات الفهرس الموجودة لديك إلى ملف يسمى، بشكل افتراضي،
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
. بالنسبة لمسار الحقل ، أدخلtimestamp
.ضمن نطاق الاستعلام ، حدد المجموعة ومجموعة المجموعة .
انقر فوق {التالي
قم بتبديل جميع إعدادات الفهرس إلى معطل . انقر فوق حفظ .
كرر نفس الخطوات لحقل
shard
.
وحدة تحكم Google Cloud Platform
في Google Cloud Platform Console، انتقل إلى صفحة قواعد البيانات .
قم باختيار قاعدة البيانات المطلوبة من قائمة قواعد البيانات.
في قائمة التنقل، انقر فوق الفهارس ، ثم انقر فوق علامة التبويب حقل واحد .
انقر فوق علامة التبويب حقل واحد .
انقر فوق إضافة استثناء .
بالنسبة لمعرف المجموعة ، أدخل
instruments
. بالنسبة لمسار الحقل ، أدخلtimestamp
.ضمن نطاق الاستعلام ، حدد المجموعة ومجموعة المجموعة .
انقر فوق {التالي
قم بتبديل جميع إعدادات الفهرس إلى معطل . انقر فوق حفظ .
كرر نفس الخطوات لحقل
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
. على سبيل المثال، لدعم الاستعلامات أعلاه، أضف الفهارس التالية:
مجموعة | الحقول مفهرسة | نطاق الاستعلام |
---|---|---|
الادوات | ، سعر العملة، ، الطابع الزمني | مجموعة |
الادوات | ، تبادل ، طابع زمني | مجموعة |
الادوات | ، ، نوع الأداة، ، الطابع الزمني | مجموعة |
رسائل خاطئة
يمكنك إنشاء هذه الفهارس عن طريق تشغيل الاستعلامات المحدثة.
يعرض كل استعلام رسالة خطأ تحتوي على رابط لإنشاء الفهرس المطلوب في وحدة تحكم 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