الطوابع الزمنية المقسّمة إلى أجزاء

إذا كانت إحدى المجموعات تحتوي على مستندات ذات قيم مفهرسة تسلسلية، تحد 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 لطلبات البحث، يمكنك تجنُّب الحدّ من خلال باستخدام طوابع زمنية مجزّأة:

  1. أضِف حقل shard بجانب الحقل timestamp. استخدام 1..n المميزة للحقل shard. وهذا يرفع مستوى كتابة محددة للمجموعة إلى 500*n، ولكن يجب تجميع n طلب بحث.
  2. تعديل منطق الكتابة إلى تحديد قيمة shard لكل عنصر بشكل عشوائي جلسة المراجعة.
  3. عدِّل طلبات البحث لتجميع مجموعات النتائج المجزّأة.
  4. إيقاف فهارس الحقل الواحد لكل من الحقل shard وtimestamp . حذف الفهارس المركبة الحالية التي تحتوي على timestamp .
  5. إنشاء فهارس مركبة جديدة لدعم طلبات البحث المحدّثة. ترتيب الحقول في الفهرس مهمة، ويجب أن يأتي الحقل 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]);
    });

بعد إجراء بعض البحث، تحدد أن التطبيق سيتلقى ما بين 1,000 و1,500 تحديث أداة في الثانية. يتجاوز هذا 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

  1. افتح صفحة مؤشرات Cloud Firestore المركبة في وحدة تحكُّم Firebase.

    الانتقال إلى المؤشرات المركّبة

  2. لكل فهرس يحتوي على الحقل timestamp، انقر على زر وانقر على حذف.

وحدة تحكُّم Google Cloud Platform

  1. في وحدة تحكّم Google Cloud Platform، انتقِل إلى صفحة قواعد البيانات.

    الانتقال إلى قواعد البيانات

  2. حدد قاعدة البيانات المطلوبة من قائمة قواعد البيانات.

  3. في قائمة التنقل، انقر على الفهارس، ثم انقر على علامة التبويب مركّب.

  4. استخدِم الحقل فلتر للبحث عن تعريفات الفهرس التي تحتوي على الحقل "timestamp".

  5. لكل مؤشر من هذه المؤشرات، انقر على وانقر على حذف.

Firebase CLI

  1. في حال عدم إعداد واجهة سطر الأوامر في Firebase، اتّبع هذه التوجيهات لتثبيت في واجهة سطر الأوامر وتنفيذ الأمر firebase init. أثناء الأمر init، اجعل تأكَّد من اختيار Firestore: Deploy rules and create indexes for Firestore.
  2. أثناء الإعداد، ينزِّل واجهة سطر الأوامر في Firebase تعريفات الفهرس الحالية إلى ملف باسم، firestore.indexes.json بشكل تلقائي
  3. أزِل أي تعريفات فهرس تحتوي على الحقل 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"
          }
        ]
      },
     ]
    }
    
  4. انشر تعريفات الفهرس المعدَّلة:

    firebase deploy --only firestore:indexes
    

تعديل تعريفات فهرس الحقل الفردي

وحدة تحكُّم Firebase

  1. افتح صفحة مؤشرات الحقل الفردي في Cloud Firestore في قسم "وحدة تحكُّم Firebase".

    الانتقال إلى فهارس الحقل الواحد

  2. انقر على إضافة استثناء.

  3. بالنسبة إلى معرّف المجموعة، أدخِل instruments. بالنسبة إلى مسار الحقل، أدخِل timestamp.

  4. ضمن نطاق طلب البحث، اختَر كلاً من المجموعة مجموعة المجموعات:

  5. انقر على التالي.

  6. بدِّل جميع إعدادات الفهرس إلى غير مفعَّل. انقر على حفظ.

  7. كرِّر الخطوات نفسها للحقل shard.

وحدة تحكُّم Google Cloud Platform

  1. في وحدة تحكّم Google Cloud Platform، انتقِل إلى صفحة قواعد البيانات.

    الانتقال إلى قواعد البيانات

  2. حدد قاعدة البيانات المطلوبة من قائمة قواعد البيانات.

  3. في قائمة التنقل، انقر على الفهارس، ثم انقر على علامة التبويب حقل واحد.

  4. انقر على علامة التبويب حقل واحد.

  5. انقر على إضافة استثناء.

  6. بالنسبة إلى معرّف المجموعة، أدخِل instruments. بالنسبة إلى مسار الحقل، أدخِل timestamp.

  7. ضمن نطاق طلب البحث، اختَر كلاً من المجموعة مجموعة المجموعات:

  8. انقر على التالي.

  9. بدِّل جميع إعدادات الفهرس إلى غير مفعَّل. انقر على حفظ.

  10. كرِّر الخطوات نفسها للحقل shard.

Firebase CLI

  1. أضِف ما يلي إلى القسم fieldOverrides في تعريفات الفهرس. الملف:

    {
     "fieldOverrides": [
       // Disable single-field indexing for the timestamp field
       {
         "collectionGroup": "instruments",
         "fieldPath": "timestamp",
         "indexes": []
       },
     ]
    }
    
  2. انشر تعريفات الفهرس المعدَّلة:

    firebase deploy --only firestore:indexes
    

إنشاء فهارس مركبة جديدة

بعد إزالة جميع الفهارس السابقة التي تحتوي على timestamp، وتحدد الفهارس الجديدة التي يتطلبها تطبيقك. أي فهرس يحتوي على يجب أن يحتوي الحقل timestamp أيضًا على الحقل shard. على سبيل المثال، لدعم الاستعلامات أعلاه، أضف الفهارس التالية:

التجميع الحقول التي تمت فهرستها نطاق طلب البحث
آلات موسيقية جزء ، عملة price.currency ، طابع زمني واحد () التجميع
آلات موسيقية جزء ، التباد�� ، ��ل��ابع ��ل��مني ���� التجميع
آلات موسيقية جزء ، نوع الجهاز ، الطابع الزمني التجميع

رسائل الخطأ

يمكنك إنشاء هذه الفهارس عن طريق تشغيل الاستعلامات المحدثة.

يعرض كل طلب بحث رسالة خطأ بها رابط لإنشاء الفهرس في "وحدة تحكُّم Firebase".

Firebase CLI

  1. أضِف الفهارس التالية إلى ملف تعريف الفهرس:

     {
       "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"
             }
           ]
         },
       ]
     }
    
  2. انشر تعريفات الفهرس المعدَّلة:

    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 بين إدخالات الفهرس لتقسيم الإدخالات بين أجهزة لوحية متعددة.

الخطوات التالية