การประทับเวลาแบบแบ่งส่วน

หากคอลเลกชันมีเอกสารที่มีค่าการจัดทำดัชนีตามลำดับ 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 และวิธีการสืบค้นชุดผลลัพธ์ที่แบ่งส่วน

ตัวอย่างโมเดลข้อมูลและการสืบค้น

ตามตัวอย่าง ลองนึกภาพแอปสำหรับการวิเคราะห์เครื่องมือทางการเงินแบบเกือบจะเรียลไทม์ เช่น สกุลเงิน หุ้นสามัญ และ 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

  1. เปิดหน้า ดัชนีคอมโพสิตของ Cloud Firestore ในคอนโซล Firebase

    ไปที่ดัชนีคอมโพสิต

  2. สำหรับแต่ละดัชนีที่มีฟิลด์ timestamp ให้คลิกปุ่ม แล้วคลิก Delete

คอนโซล GCP

  1. ในคอนโซล Google Cloud Platform ให้ไปที่หน้า ฐานข้อมูล

    ไปที่ฐานข้อมูล

  2. เลือกฐานข้อมูลที่ต้องการจากรายการฐานข้อมูล

  3. ในเมนูนำทาง คลิก ดัชนี จากนั้นคลิกแท็บ คอมโพสิต

  4. ใช้ฟิลด์ตัว กรอง เพื่อค้นหาคำจำกัดความของดัชนีที่มีฟิลด์ timestamp

  5. สำหรับแต่ละดัชนีเหล่านี้ ให้คลิกปุ่ม แล้วคลิก Delete

Firebase CLI

  1. หากคุณยังไม่ได้ตั้งค่า Firebase CLI ให้ทำตามคำแนะนำเหล่านี้เพื่อติดตั้ง CLI และเรียกใช้คำสั่ง firebase init ในระหว่างคำสั่ง init ตรวจสอบให้แน่ใจว่าได้เลือก Firestore: Deploy rules and create indexes for Firestore
  2. ในระหว่างการตั้งค่า Firebase CLI จะดาวน์โหลดคำจำกัดความดัชนีที่มีอยู่ของคุณไปยังไฟล์ชื่อ 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 สำหรับ Field path ให้ป้อน timestamp

  4. ภายใต้ ขอบเขตการค้นหา เลือกทั้ง คอลเลกชัน และ กลุ่มคอลเลกชัน

  5. คลิก ถัดไป

  6. สลับการตั้งค่าดัชนีทั้งหมดเป็น Disabled คลิก บันทึก

  7. ทำซ้ำขั้นตอนเดียวกันสำหรับช่อง shard

คอนโซล GCP

  1. ในคอนโซล Google Cloud Platform ให้ไปที่หน้า ฐานข้อมูล

    ไปที่ฐานข้อมูล

  2. เลือกฐานข้อมูลที่ต้องการจากรายการฐานข้อมูล

  3. ในเมนูนำทาง คลิก ดัชนี จากนั้นคลิกแท็บเขต ข้อมูลเดี่ยว

  4. คลิกแท็บ เขตข้อมูลเดี่ยว

  5. คลิก เพิ่มการยกเว้น

  6. สำหรับ รหัสคอลเลก ชัน ให้ป้อน instruments สำหรับ Field path ให้ป้อน timestamp

  7. ภายใต้ ขอบเขตการค้นหา เลือกทั้ง คอลเลกชัน และ กลุ่มคอลเลกชัน

  8. คลิก ถัดไป

  9. สลับการตั้งค่าดัชนีทั้งหมดเป็น Disabled คลิก บันทึก

  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 ชาร์ดด้วย ตัวอย่างเช่น เพื่อสนับสนุนการสืบค้นข้างต้น ให้เพิ่มดัชนีต่อไปนี้:

ของสะสม ฟิลด์ที่จัดทำดัชนีแล้ว ขอบเขตการค้นหา
เครื่องมือ shard, price.currency, เวลาประทับ ของสะสม
เครื่องมือ shard, การแลกเปลี่ยน , การประทับเวลา ของสะสม
เครื่องมือ 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 มีพื้นที่เพียงพอระหว่างรายการดัชนีเพื่อแยกรายการออกเป็นหลายแท็บเล็ต

อะไรต่อไป