Dấu thời gian bị chia nhỏ

Nếu bộ sưu tập chứa các tài liệu có giá trị được lập chỉ mục tuần tự, Cloud Firestore giới hạn tốc độ ghi ở mức 500 lần ghi mỗi giây. Trang này mô tả cách phân chia trường tài liệu để vượt qua giới hạn này. Trước tiên, hãy xác định ý nghĩa của "các trường được lập chỉ mục tuần tự" và làm rõ khi nào giới hạn này được áp dụng.

Các trường được lập chỉ mục tuần tự

"Các trường được lập chỉ mục tuần tự" nghĩa là bất kỳ tập hợp tài liệu nào chứa trường được lập chỉ mục tăng hoặc giảm đơn điệu. Trong nhiều trường hợp, điều này có nghĩa là trường timestamp , nhưng bất kỳ giá trị trường tăng hoặc giảm đơn điệu nào cũng có thể kích hoạt giới hạn ghi là 500 lần ghi mỗi giây.

Ví dụ: giới hạn áp dụng cho tập hợp tài liệu useruserid trường được lập chỉ mục nếu ứng dụng chỉ định các giá trị userid như sau:

  • 1281, 1282, 1283, 1284, 1285, ...

Mặt khác, không phải tất cả các trường timestamp đều kích hoạt giới hạn này. Nếu trường timestamp theo dõi các giá trị được phân phối ngẫu nhiên thì giới hạn ghi sẽ không được áp dụng. Giá trị thực tế của trường cũng không thành vấn đề, chỉ có điều trường tăng hoặc giảm đơn điệu. Ví dụ: cả hai tập hợp giá trị trường tăng đều đều sau đây đều kích hoạt giới hạn ghi:

  • 100000, 100001, 100002, 100003, ...
  • 0, 1, 2, 3, ...

Phân chia trường dấu thời gian

Giả sử ứng dụng của bạn sử dụng trường timestamp tăng dần đều. Nếu ứng dụng của bạn không sử dụng trường timestamp trong bất kỳ truy vấn nào, bạn có thể xóa giới hạn 500 lần ghi mỗi giây bằng cách không lập chỉ mục trường dấu thời gian. Nếu bạn thực sự yêu cầu trường timestamp cho các truy vấn của mình, bạn có thể vượt qua giới hạn bằng cách sử dụng dấu thời gian được chia nhỏ :

  1. Thêm trường shard cùng với trường timestamp . Sử dụng các giá trị riêng biệt 1..n cho trường shard . Điều này tăng giới hạn ghi cho bộ sưu tập lên 500*n , nhưng bạn phải tổng hợp n truy vấn.
  2. Cập nhật logic ghi của bạn để gán ngẫu nhiên giá trị shard cho mỗi tài liệu.
  3. Cập nhật các truy vấn của bạn để tổng hợp các tập hợp kết quả được phân chia.
  4. Tắt chỉ mục một trường cho cả trường shard và trường timestamp . Xóa các chỉ mục tổng hợp hiện có có chứa trường timestamp .
  5. Tạo các chỉ mục tổng hợp mới để hỗ trợ các truy vấn cập nhật của bạn. Thứ tự của các trường trong chỉ mục rất quan trọng và trường shard đoạn phải đứng trước trường timestamp . Bất kỳ chỉ mục nào bao gồm trường timestamp cũng phải bao gồm trường shard .

Bạn chỉ nên triển khai dấu thời gian được phân chia trong các trường hợp sử dụng có tốc độ ghi liên tục trên 500 lần ghi mỗi giây. Nếu không thì đây là một sự tối ưu hóa chưa hoàn thiện. Việc phân chia trường timestamp sẽ loại bỏ hạn chế 500 lần ghi mỗi giây nhưng phải đánh đổi việc cần tổng hợp truy vấn phía máy khách.

Các ví dụ sau đây cho thấy cách phân chia trường timestamp và cách truy vấn tập kết quả được phân chia.

Mô hình dữ liệu mẫu và truy vấn

Ví dụ: hãy tưởng tượng một ứng dụng để phân tích các công cụ tài chính gần như theo thời gian thực như tiền tệ, cổ phiếu phổ thông và quỹ ETF. Ứng dụng này ghi tài liệu vào bộ sưu tập instruments như sau:

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();
}

Ứng dụng này chạy các truy vấn và đơn đặt hàng sau theo trường 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]);
    });

Sau một số nghiên cứu, bạn xác định rằng ứng dụng sẽ nhận được từ 1.000 đến 1.500 cập nhật công cụ mỗi giây. Con số này vượt quá 500 lần ghi mỗi giây được phép đối với các bộ sưu tập chứa tài liệu có trường dấu thời gian được lập chỉ mục. Để tăng thông lượng ghi, bạn cần 3 giá trị phân đoạn, MAX_INSTRUMENT_UPDATES/500 = 3 . Ví dụ này sử dụng các giá trị phân đoạn x , yz . Bạn cũng có thể sử dụng số hoặc ký tự khác cho giá trị phân đoạn của mình.

Thêm trường phân đoạn

Thêm trường shard vào tài liệu của bạn. Đặt trường shard thành các giá trị x , y hoặc z để tăng giới hạn ghi trên bộ sưu tập lên 1.500 lần ghi mỗi giây.

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();
}

Truy vấn dấu thời gian được phân chia

Việc thêm trường shard yêu cầu bạn cập nhật các truy vấn của mình để tổng hợp các kết quả được phân đoạn:

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]);
    });

Cập nhật định nghĩa chỉ mục

Để loại bỏ giới hạn 500 lần ghi mỗi giây, hãy xóa các chỉ mục trường đơn và tổng hợp hiện có sử dụng trường timestamp .

Xóa định nghĩa chỉ mục tổng hợp

Bảng điều khiển Firebase

  1. Mở trang Chỉ mục tổng hợp Cloud Firestore trong bảng điều khiển Firebase.

    Chuyển đến Chỉ mục tổng hợp

  2. Đối với mỗi chỉ mục chứa trường timestamp , hãy nhấp vào nút và nhấp vào Xóa .

Bảng điều khiển GCP

  1. Trong Google Cloud Platform Console, hãy truy cập trang Cơ sở dữ liệu .

    Đi tới cơ sở dữ liệu

  2. Chọn cơ sở dữ liệu cần thiết từ danh sách cơ sở dữ liệu.

  3. Trong menu điều hướng, nhấp vào Chỉ mục , sau đó nhấp vào tab Tổng hợp .

  4. Sử dụng trường Bộ lọc để tìm kiếm định nghĩa chỉ mục có chứa trường timestamp .

  5. Đối với mỗi chỉ mục này, hãy nhấp vào nút và nhấp vào Xóa .

Firebase CLI

  1. Nếu bạn chưa thiết lập Firebase CLI, hãy làm theo hướng dẫn sau để cài đặt CLI và chạy lệnh firebase init . Trong lệnh init , đảm bảo chọn Firestore: Deploy rules and create indexes for Firestore .
  2. Trong quá trình thiết lập, Firebase CLI tải các định nghĩa chỉ mục hiện có của bạn xuống một tệp có tên mặc định là firestore.indexes.json .
  3. Xóa mọi định nghĩa chỉ mục có chứa trường timestamp , ví dụ:

    {
    "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. Triển khai các định nghĩa chỉ mục được cập nhật của bạn:

    firebase deploy --only firestore:indexes
    

Cập nhật định nghĩa chỉ mục một trường

Bảng điều khiển Firebase

  1. Mở trang Chỉ mục trường đơn của Cloud Firestore trong bảng điều khiển Firebase.

    Chuyển đến Chỉ mục trường đơn

  2. Nhấp vào Thêm miễn trừ .

  3. Đối với ID Bộ sưu tập , hãy nhập instruments . Đối với Đường dẫn trường , hãy nhập timestamp .

  4. Trong phạm vi Truy vấn , chọn cả nhóm Bộ sưu tập và Bộ sưu tập.

  5. Bấm tiếp

  6. Chuyển tất cả cài đặt chỉ mục thành Tắt . Nhấp vào để lưu .

  7. Lặp lại các bước tương tự cho trường shard .

Bảng điều khiển GCP

  1. Trong Google Cloud Platform Console, hãy truy cập trang Cơ sở dữ liệu .

    Đi tới cơ sở dữ liệu

  2. Chọn cơ sở dữ liệu cần thiết từ danh sách cơ sở dữ liệu.

  3. Trong menu điều hướng, hãy bấm vào Chỉ mục , rồi bấm vào tab Trường Đơn .

  4. Bấm vào tab Trường đơn .

  5. Nhấp vào Thêm miễn trừ .

  6. Đối với ID Bộ sưu tập , hãy nhập instruments . Đối với Đường dẫn trường , hãy nhập timestamp .

  7. Trong phạm vi Truy vấn , chọn cả nhóm Bộ sưu tập và Bộ sưu tập.

  8. Bấm tiếp

  9. Chuyển tất cả cài đặt chỉ mục thành Tắt . Nhấp vào để lưu .

  10. Lặp lại các bước tương tự cho trường shard .

Firebase CLI

  1. Thêm phần sau vào phần fieldOverrides của tệp định nghĩa chỉ mục của bạn:

    {
     "fieldOverrides": [
       // Disable single-field indexing for the timestamp field
       {
         "collectionGroup": "instruments",
         "fieldPath": "timestamp",
         "indexes": []
       },
     ]
    }
    
  2. Triển khai các định nghĩa chỉ mục được cập nhật của bạn:

    firebase deploy --only firestore:indexes
    

Tạo chỉ mục tổng hợp mới

Sau khi xóa tất cả các chỉ mục trước đó có chứa timestamp , hãy xác định các chỉ mục mới mà ứng dụng của bạn yêu cầu. Bất kỳ chỉ mục nào chứa trường timestamp cũng phải chứa trường shard . Ví dụ: để hỗ trợ các truy vấn trên, hãy thêm các chỉ mục sau:

Bộ sưu tập Các trường được lập chỉ mục Phạm vi truy vấn
dụng cụ mũi , tăng.tiền tệ, thời gian giảm dần Bộ sưu tập
dụng cụ mũi , đổi hướng lên, thời gian hướng xuống Bộ sưu tập
dụng cụ mũi , tên_loại công cụ hướng lên, thời gian hướng xuống Bộ sưu tập

Thông báo lỗi

Bạn có thể xây dựng các chỉ mục này bằng cách chạy các truy vấn được cập nhật.

Mỗi truy vấn trả về một thông báo lỗi kèm theo liên kết để tạo chỉ mục bắt buộc trong Bảng điều khiển Firebase.

Firebase CLI

  1. Thêm các chỉ mục sau vào tệp định nghĩa chỉ mục của bạn:

     {
       "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. Triển khai các định nghĩa chỉ mục được cập nhật của bạn:

    firebase deploy --only firestore:indexes
    

Hiểu cách viết cho các trường được lập chỉ mục tuần tự giới hạn

Giới hạn về tốc độ ghi đối với các trường được lập chỉ mục tuần tự xuất phát từ cách Cloud Firestore lưu trữ các giá trị chỉ mục và tỷ lệ ghi chỉ mục. Đối với mỗi lần ghi chỉ mục, Cloud Firestore xác định mục nhập khóa-giá trị nối tên tài liệu và giá trị của từng trường được lập chỉ mục. Cloud Firestore tổ chức các mục chỉ mục này thành các nhóm dữ liệu được gọi là máy tính bảng . Mỗi máy chủ Cloud Firestore chứa một hoặc nhiều máy tính bảng. Khi tải ghi vào một máy tính bảng cụ thể trở nên quá cao, Cloud Firestore sẽ mở rộng theo chiều ngang bằng cách chia máy tính bảng thành các máy tính bảng nhỏ hơn và trải rộng các máy tính bảng mới trên các máy chủ Cloud Firestore khác nhau.

Cloud Firestore đặt các mục chỉ mục đóng theo từ điển trên cùng một máy tính bảng. Nếu các giá trị chỉ mục trong máy tính bảng quá gần nhau, chẳng hạn như đối với các trường dấu thời gian, Cloud Firestore không thể chia máy tính bảng thành các máy tính bảng nhỏ hơn một cách hiệu quả. Điều này tạo ra một điểm nóng trong đó một máy tính bảng nhận được quá nhiều lưu lượng truy cập và các hoạt động đọc và ghi vào điểm nóng trở nên chậm hơn.

Bằng cách phân chia trường dấu thời gian, bạn có thể giúp Cloud Firestore phân chia khối lượng công việc một cách hiệu quả trên nhiều máy tính bảng. Mặc dù các giá trị của trường dấu thời gian có thể vẫn gần nhau, nhưng giá trị chỉ mục và phân đoạn được nối sẽ cung cấp cho Cloud Firestore đủ không gian giữa các mục chỉ mục để phân chia các mục giữa nhiều máy tính bảng.

Cái gì tiếp theo