Bir koleksiyon, sıralı dizine eklenmiş değerler içeren dokümanlar barındırıyorsa Cloud Firestore, yazma hızını saniyede 500 yazma işlemiyle sınırlandırır. Bu sayfada, bu sınırı aşmak için bir doküman alanının nasıl parçalanacağı açıklanmaktadır. Öncelikle "sıralı dizine eklenmiş alanlar" derken neyi kastettiğimizi tanımlayalım ve bu sınırın ne zaman geçerli olduğunu açıklayalım.
Sıralı dizine eklenmiş alanlar
"Sıralı dizine eklenmiş alanlar", monoton olarak artan veya azalan dizine eklenmiş alan içeren herhangi bir doküman koleksiyonunu ifade eder. Çoğu durumda bu, timestamp alanı anlamına gelir ancak monoton olarak artan veya azalan herhangi bir alan değeri, saniyede 500 yazma sınırını tetikleyebilir.
Örneğin, uygulama userid değerlerini aşağıdaki gibi atarsa sınır, dizine eklenmiş userid alanına sahip user doküman koleksiyonu için geçerli olur:
1281, 1282, 1283, 1284, 1285, ...
Öte yandan, tüm timestamp alanları bu sınırı tetiklemez. Bir timestamp alanı rastgele dağıtılmış değerleri izliyorsa yazma sınırı geçerli olmaz. Alanın gerçek değeri de önemli değildir. Önemli olan, alanın monoton bir şekilde artması veya azalmasıdır. Örneğin, aşağıdaki monoton şekilde artan alan değerleri kümelerinin her ikisi de yazma sınırını tetikler:
100000, 100001, 100002, 100003, ...0, 1, 2, 3, ...
Zaman damgası alanını parçalama
Uygulamanızın monoton şekilde artan bir timestamp alanı kullandığını varsayalım.
Uygulamanız hiçbir sorguda timestamp alanını kullanmıyorsa zaman damgası alanını dizine eklemeyerek saniyede 500 yazma sınırını kaldırabilirsiniz. Sorgularınız için timestamp alanı gerekiyorsa parçalı zaman damgaları kullanarak sınırı aşabilirsiniz:
timestampalanının yanına birshardalanı ekleyin.shardalanı için1..nfarklı değer kullanın. Bu işlem, koleksiyonun yazma sınırını500*ndeğerine yükseltir ancaknsorgularını toplamanız gerekir.- Yazma mantığınızı, her belgeye rastgele bir
sharddeğeri atayacak şekilde güncelleyin. - Parçalı sonuç kümelerini birleştirmek için sorgularınızı güncelleyin.
- Hem
shardalanı hem detimestampalanı için tek alanlı dizinleri devre dışı bırakın.timestampalanını içeren mevcut bileşik dizinleri silin. - Güncellenen sorgularınızı desteklemek için yeni bileşik dizinler oluşturun. Dizindeki alanların sırası önemlidir ve
shardalanı,timestampalanından önce gelmelidir.timestampalanını içeren tüm dizinlershardalanını da içermelidir.
Parçalı zaman damgalarını yalnızca saniyede 500 yazma işleminin üzerinde sürekli yazma hızına sahip kullanım alanlarında uygulamanız gerekir. Aksi takdirde, bu erken bir optimizasyon olur. timestamp alanını parçalara ayırmak, saniyede 500 yazma kısıtlamasını kaldırır ancak istemci tarafı sorgu toplamalarına ihtiyaç duyulur.
Aşağıdaki örneklerde, bir timestamp alanının nasıl parçalanacağı ve parçalanmış bir sonuç kümesinin nasıl sorgulanacağı gösterilmektedir.
Örnek veri modeli ve sorgular
Örneğin, para birimleri, adi hisse senetleri ve ETF'ler gibi finansal araçların neredeyse anlık analizini yapan bir uygulama düşünün. Bu uygulama, dokümanları instruments koleksiyonuna şu şekilde yazar:
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(); }
Bu uygulama aşağıdaki sorguları çalıştırır ve timestamp alanına göre sıralar:
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]); });
Biraz araştırma yaptıktan sonra uygulamanın saniyede 1.000 ila 1.500 enstrüman güncellemesi alacağını belirlediniz. Bu sayı, dizine eklenmiş zaman damgası alanları içeren dokümanların bulunduğu koleksiyonlar için izin verilen saniyede 500 yazma işlemini aşar. Yazma işleme kapasitesini artırmak için 3 parça değerine ihtiyacınız vardır,
MAX_INSTRUMENT_UPDATES/500 = 3. Bu örnekte x, y ve z parça değerleri kullanılmıştır. Parça değerleriniz için sayıları veya diğer karakterleri de kullanabilirsiniz.
Parça alanı ekleme
Dokümanlarınıza shard alanı ekleyin. shard alanını x, y veya z değerlerine ayarlayın. Bu işlem, koleksiyondaki yazma sınırını saniyede 1.500 yazma işlemine yükseltir.
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(); }
Parçalı zaman damgasını sorgulama
shard alanı eklemek, parçalı sonuçları toplamak için sorgularınızı güncellemenizi gerektirir:
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]); });
Dizin tanımlarını güncelleme
Saniyede 500 yazma kısıtlamasını kaldırmak için timestamp alanını kullanan mevcut tek alanlı ve bileşik dizinleri silin.
Bileşik dizin tanımlarını silme
Firebase konsolu
Firebase konsolunda Databases & Storage (Veritabanları ve Depolama) > Firestore > Composite Indexes (Bileşik Dizinler) sekmesine gidin.
timestampalanını içeren her dizin için düğmesini ve Sil'i tıklayın.
GCP Console
Google Cloud Console'da Veritabanları sayfasına gidin.
Veritabanları listesinden gerekli veritabanını seçin.
Gezinme menüsünde Dizinler'i ve ardından Bileşik sekmesini tıklayın.
timestampalanını içeren dizin tanımlarını aramak için Filtre alanını kullanın.Bu dizinlerin her biri için düğmesini ve Sil'i tıklayın.
Firebase CLI
- Firebase CLI'yı kurmadıysanız CLI'yı yüklemek ve
firebase initkomutunu çalıştırmak için bu talimatları uygulayın.initkomutu sırasındaFirestore: Deploy rules and create indexes for Firestoreseçeneğini belirlediğinizden emin olun. - Kurulum sırasında Firebase CLI, mevcut dizin tanımlarınızı varsayılan olarak
firestore.indexes.jsonadlı bir dosyaya indirir. timestampalanını içeren tüm dizin tanımlarını kaldırın. Örneğin:{ "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" } ] }, ] }Güncellenen dizin tanımlarınızı dağıtın:
firebase deploy --only firestore:indexes
Tek alanlı dizin tanımlarını güncelleme
Firebase konsolu
Firebase konsolunda Databases & Storage (Veritabanları ve Depolama) > Firestore > Single Field Indexes (Tek Alan Dizinleri) sekmesine gidin.
Muafiyet ekle'yi tıklayın.
Collection ID (Koleksiyon kimliği) için
instrumentsdeğerini girin. Alan yolu içintimestampdeğerini girin.Sorgu kapsamı bölümünde hem Koleksiyon hem de Koleksiyon grubu'nu seçin.
İleri'yi tıklayın.
Tüm dizin ayarlarını Devre dışı olarak değiştirin. Kaydet'i tıklayın.
Aynı adımları
shardalanı için tekrarlayın.
GCP Console
Google Cloud Console'da Veritabanları sayfasına gidin.
Veritabanları listesinden gerekli veritabanını seçin.
Gezinme menüsünde Dizinler'i ve ardından Tek Alan sekmesini tıklayın.
Tek Alan sekmesini tıklayın.
Muafiyet ekle'yi tıklayın.
Collection ID (Koleksiyon kimliği) için
instrumentsdeğerini girin. Alan yolu içintimestampdeğerini girin.Sorgu kapsamı bölümünde hem Koleksiyon hem de Koleksiyon grubu'nu seçin.
İleri'yi tıklayın.
Tüm dizin ayarlarını Devre dışı olarak değiştirin. Kaydet'i tıklayın.
Aynı adımları
shardalanı için tekrarlayın.
Firebase CLI
Dizin tanımları dosyanızın
fieldOverridesbölümüne aşağıdakileri ekleyin:{ "fieldOverrides": [ // Disable single-field indexing for the timestamp field { "collectionGroup": "instruments", "fieldPath": "timestamp", "indexes": [] }, ] }Güncellenen dizin tanımlarınızı dağıtın:
firebase deploy --only firestore:indexes
Yeni bileşik dizinler oluşturma
timestamp içeren önceki tüm dizinleri kaldırdıktan sonra uygulamanızın gerektirdiği yeni dizinleri tanımlayın. timestamp alanını içeren tüm dizinler shard alanını da içermelidir. Örneğin, yukarıdaki sorguları desteklemek için aşağıdaki dizinleri ekleyin:
| Toplama | Dizine eklenen alanlar | Sorgu kapsamı |
|---|---|---|
| enstrümanlar | parça, price.currency, zaman damgası | Toplama |
| enstrümanlar | parça, alışveriş, zaman damgası | Toplama |
| enstrümanlar | parça, instrumentType, zaman damgası | Toplama |
Hata Mesajları
Güncellenen sorguları çalıştırarak bu dizinleri oluşturabilirsiniz.
Her sorgu, Firebase konsolunda gerekli dizini oluşturmak için bir bağlantı içeren hata mesajı döndürür.
Firebase CLI
Dizin tanımı dosyanıza aşağıdaki dizinleri ekleyin:
{ "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" } ] }, ] }Güncellenen dizin tanımlarınızı dağıtın:
firebase deploy --only firestore:indexes
Sıralı dizine eklenmiş alanlar için yazma sınırını anlama
Sıralı dizine eklenmiş alanların yazma hızı sınırı, Cloud Firestore dizin değerlerini depolama ve dizin yazma işlemlerini ölçeklendirme yönteminden kaynaklanır. Her dizin yazma işlemi için Cloud Firestore, doküman adını ve dizine eklenen her alanın değerini birleştiren bir anahtar/değer girişi tanımlar. Cloud Firestore bu dizin girişlerini tablet adı verilen veri grupları halinde düzenler. Her Cloud Firestore sunucusu bir veya daha fazla tablet barındırır. Belirli bir tablete yazma yükü çok fazla olduğunda Cloud Firestore, tableti daha küçük tabletlere bölerek ve yeni tabletleri farklı Cloud Firestore sunucularına yayarak yatay olarak ölçeklendirir.
Cloud Firestore, sözlük sırasına göre yakın dizin girişlerini aynı tablete yerleştirir. Bir tabletteki dizin değerleri birbirine çok yakınsa (ör. zaman damgası alanları için) Cloud Firestore, tableti daha küçük tabletlere verimli bir şekilde bölemez. Bu durum, tek bir tabletin çok fazla trafik aldığı ve etkin noktaya okuma ve yazma işlemlerinin yavaşladığı bir etkin nokta oluşturur.
Zaman damgası alanını parçalara ayırarak Cloud Firestore'in iş yüklerini birden fazla tablete verimli bir şekilde bölmesini sağlarsınız. Zaman damgası alanının değerleri birbirine yakın kalsa da birleştirilmiş parça ve dizin değeri, Cloud Firestore için dizin girişleri arasında girişleri birden fazla tablete bölmeye yetecek kadar alan sağlar.
Sırada ne var?
- Ölçeklenebilir tasarım için en iyi uygulamaları okuyun.
- Tek bir dokümana yüksek yazma oranlarının olduğu durumlar için Dağıtılmış sayaçlar bölümüne bakın.
- Cloud Firestore için standart sınırlara bakın.