الطوابع الزمنية التي تمت مشاركتها

إذا كانت المجموعة تحتوي على مستندات ذات قيم مفهرسة تسلسلية، فإن 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]);
    });

بعد إجراء بعض البحث، حددت أن التطبيق سيتلقى ما بين 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

  1. افتح صفحة Cloud Firestore Composite Indexes في وحدة تحكم Firebase.

    انتقل إلى الفهارس المركبة

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

وحدة تحكم Google Cloud Platform

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

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

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

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

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

  5. لكل من هذه الفهارس، انقر فوق الزر ثم انقر فوق حذف .

Firebase CLI

  1. إذا لم تقم بإعداد Firebase CLI، فاتبع هذه التوجيهات لتثبيت CLI وتشغيل الأمر 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 Console، انتقل إلى صفحة قواعد البيانات .

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

  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 . على سبيل المثال، لدعم الاستعلامات أعلاه، أضف الفهارس التالية:

مجموعة الحقول مفهرسة نطاق الاستعلام
الادوات ، سعر العملة، ، الطابع الزمني مجموعة
الادوات ، تبادل ، طابع زمني مجموعة
الادوات ، ، نوع الأداة، ، الطابع الزمني مجموعة

رسائل خاطئة

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

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

ماذا بعد