Dấu thời gian phân ��oạn

Nếu một bộ sưu tập chứa 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ượt ghi/giây. Trang này mô tả cách phân đoạn một 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 sẽ được áp dụng.

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

"Trường được lập chỉ mục tuần tự" có nghĩa là bất kỳ tập hợp tài liệu nào có 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 mọi giá trị trường tăng hoặc giảm đơn điệu có thể kích hoạt giới hạn ghi là 500 lượt ghi/giây.

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

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

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

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

Phân đoạn trường dấu thời gian

Giả sử ứng dụng của bạn sử dụng trường timestamp tăng đơn điệ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ể xoá trường Giới hạn 500 lượt ghi/giây do không lập chỉ mục trường dấu thời gian. Nếu bạn muốn yêu cầu trường timestamp cho các truy vấn của bạn, bạn có thể khắc phục giới hạn bằng cách sử dụng dấu thời gian được phân đoạn:

  1. Th��m trường shard bên cạnh trường timestamp. Sử dụng 1..n khác biệt các giá trị cho trường shard. Điều này làm tăng mức độ ghi giới hạn cho tập hợp thành 500*n, nhưng bạn phải tổng hợp n truy vấn.
  2. Cập nhật logic ghi để chỉ định giá trị shard ngẫu nhiên cho mỗi tài liệu.
  3. Cập nhật truy vấn của bạn để tổng hợp các nhóm kết quả được phân đoạn.
  4. Tắt chỉ mục trường đơn cho cả trường shardtimestamp . Xóa chỉ mục tổng hợp hiện có chứa timestamp .
  5. Tạo 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 một chỉ mục rất quan trọng và trường shard 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 đoạn trong các trường hợp sử dụng được duy trì tốc độ ghi trên 500 lần ghi/giây. Nếu không, đây là tối ưu hoá sớm. Việc phân đoạn trường timestamp sẽ xoá 500 lượt ghi mỗi giây nhưng có thể đánh đổi bằng việc cần truy vấn phía máy khách dữ liệu tổng hợp.

Các ví dụ sau đây minh hoạ cách phân đoạn một trường timestamp và cách truy vấn một trường tập hợp kết quả được phân đoạn.

Ví dụ về mô hình dữ liệu và truy vấn

Ví dụ: hãy tưởng tượng một ứng dụng giúp phân tích tài chính gần như theo thời gian thực công cụ như tiền tệ, cổ phiếu phổ thông và quỹ ETF. Ứng dụng này ghi 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 khi nghiên cứu, bạn xác định rằng ứng dụng sẽ nhận được giữa 1.000 và 1.500 cập nhật công cụ mỗi giây. Con số này vượt qua con số 500 lượt ghi mỗi lần giây được phép đối với các bộ sưu tập chứa tài liệu có dấu thời gian được lập chỉ mục mới. Để 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 giá trị phân đoạn x, yz. Bạn cũng có thể sử dụng số hoặc các ký tự khác cho phân đoạn của mình giá trị.

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

Thêm trường shard vào tài liệu. Đặt trường shard thành các giá trị x, y hoặc z, việc này làm tăng giới hạn ghi trên tập hợp lên đến 1.500 lượt ghi/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 đoạn

Để thêm trường shard, bạn phải cập nhật các truy vấn của mình để tổng hợp 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 về chỉ mục

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

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

Bảng điều khiển của Firebase

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

    Chuyển đến trang Chỉ số tổng hợp

  2. Đối với mỗi chỉ mục chứa trường timestamp, hãy nhấp vào rồi nhấp vào Xoá.

Bảng điều khiển Google Cloud Platform (GCP)

  1. Trong Bảng điều khiển Google Cloud Platform, hãy chuyển đến trang Cơ sở dữ liệu.

    Truy cập trang Cơ sở dữ liệu

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

  3. Trong trình đơn điều hướng, nhấp vào Chỉ mục, sau đó nhấp vào thẻ Tổng hợp.

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

  5. Đối với mỗi chỉ mục trong số các chỉ mục này, hãy nhấp vào rồi nhấp vào Xoá.

Giao diện dòng lệnh (CLI) của Firebase

  1. Nếu bạn chưa thiết lập Giao diện dòng lệnh (CLI) của Firebase, hãy làm theo hướng dẫn này để cài đặt CLI và chạy lệnh firebase init. Trong lệnh init, hãy thực hiện hãy nhớ chọn Firestore: Deploy rules and create indexes for Firestore.
  2. Trong quá trình thiết lập, Firebase CLI sẽ 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 theo mặc định là firestore.indexes.json.
  3. Xoá mọi định nghĩa chỉ mục chứa trường timestamp, cho 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 định nghĩa chỉ mục đã cập nhật:

    firebase deploy --only firestore:indexes
    

Cập nhật định nghĩa về Chỉ mục trường đơn

Bảng điều khiển của Firebase

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

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

  2. Nhấp vào Thêm trường hợp miễn trừ.

  3. Đối với Mã 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, hãy chọn cả Thu thậpNhóm thu thập.

  5. Nhấp vào Tiếp theo

  6. Chuyển tất cả chế độ cài đặt chỉ mục sang trạng thái 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 Google Cloud Platform (GCP)

  1. Trong Bảng điều khiển Google Cloud Platform, hãy chuyển đến trang Cơ sở dữ liệu.

    Truy cập trang Cơ sở dữ liệu

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

  3. Trong trình đơn điều hướng, hãy nhấp vào Chỉ mục, sau đó nhấp vào thẻ Một trường.

  4. Nhấp vào thẻ Một trường.

  5. Nhấp vào Thêm trường hợp miễn trừ.

  6. Đối với Mã 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, hãy chọn cả Thu thậpNhóm thu thập.

  8. Nhấp vào Tiếp theo

  9. Chuyển tất cả chế độ cài đặt chỉ mục sang trạng thái Tắt. Nhấp vào Lưu.

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

Giao diện dòng lệnh (CLI) của Firebase

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

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

    firebase deploy --only firestore:indexes
    

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

Sau khi xoá tất cả chỉ mục trước đó chứa timestamp, 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 có 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:

Thu thập Các trường đã được lập chỉ mục Phạm vi truy vấn
nhạc cụ Phân đoạn , price.currency, dấu thời gian Thu thập
nhạc cụ phân đoạn, trao đổi , dấu thời gian Thu thập
nhạc cụ phân đoạn , loại thiết bị , dấu thời gian Thu thập

Thông báo lỗi

Bạn có thể tạo các chỉ mục này bằng cách chạy các truy vấn đã cập nhật.

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

Giao diện dòng lệnh (CLI) của Firebase

  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 định nghĩa chỉ mục đã cập nhật:

    firebase deploy --only firestore:indexes
    

Tìm hiểu cách ghi cho các trường được lập chỉ mục tuần tự theo giới hạn

Giới hạn về tốc độ ghi cho các trường được lập chỉ mục tuần tự được lấy từ cách Cloud Firestore lưu trữ các giá trị chỉ mục và ghi chỉ mục theo tỷ lệ. Đối với mỗi ghi chỉ mục, Cloud Firestore xác định một mục khoá-giá trị nối với nhau tên tài liệu và giá trị của mỗi trường được lập chỉ mục. Cloud Firestore tổ chức các mục nhập 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ột Máy chủ Cloud Firestore lưu trữ một hoặc nhiều máy tính bảng. Khi quá trình ghi tải vào một máy tính bảng cụ thể trở nên quá cao, Cloud Firestore chia tỷ lệ 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 các máy tính bảng mới trên nhiều máy chủ Cloud Firestore.

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

Bằng cách phân đoạn trường dấu thời gian, bạn giúp việc đó để Cloud Firestore phân chia hiệu quả khối lượng công việc 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, giá trị phân đoạn và chỉ mục được nối lại cung cấp cho Cloud Firestore đủ không gian giữa các mục nhập chỉ mục để chia các mục nhập giữa nhiều máy tính bảng.

Bước tiếp theo