अगर किसी कलेक्शन में, इंडेक्स की गई वैल्यू के क्रम में दस्तावेज़ शामिल हैं, तो Cloud Firestore, लिखने की दर को हर सेकंड 500 लिखने तक सीमित कर देता है. इस पेज पर, इस सीमा को पार करने के लिए दस्तावेज़ फ़ील्ड को शेयर करने का तरीका बताया गया है. सबसे पहले, हम यह बताएं कि "क्रम से इंडेक्स किए गए फ़ील्ड" से हमारा क्या मतलब है. साथ ही, यह भी बताएं कि यह सीमा कब लागू होती है.
क्रम से इंडेक्स किए गए फ़ील्ड
"क्रम से इंडेक्स किए गए फ़ील्ड" का मतलब है, दस्तावेज़ों का ऐसा कलेक्शन जिसमें इंडेक्स किया गया ऐसा फ़ील्ड हो जो लगातार बढ़ रहा हो या घट रहा हो. ज़्यादातर मामलों में, इसका मतलब timestamp
फ़ील्ड होता है. हालांकि, लगातार बढ़ती या घटती फ़ील्ड वैल्यू, हर सेकंड 500 लिखने की सीमा को ट्रिगर कर सकती है.
उदाहरण के लिए, अगर ऐप्लिकेशन userid
वैल्यू को इस तरह असाइन करता है, तो यह सीमा, इंडेक्स किए गए फ़ील्ड userid
वाले user
दस्तावेज़ों के कलेक्शन पर लागू होती है:
1281, 1282, 1283, 1284, 1285, ...
दूसरी ओर, सभी timestamp
फ़ील्ड इस सीमा को ट्रिगर नहीं करते. अगर कोई timestamp
फ़ील्ड, रैंडम तौर पर डिस्ट्रिब्यूट की गई वैल्यू को ट्रैक करता है, तो डेटा डालने की सीमा लागू नहीं होती. फ़ील्ड की असल वैल्यू का भी कोई फ़र्क़ नहीं पड़ता. फ़र्क़ सिर्फ़ इस बात से पड़ता है कि फ़ील्ड में वैल्यू लगातार बढ़ रही है या घट रही है. उदाहरण के लिए, एक ही क्रम में बढ़ने वाली फ़ील्ड वैल्यू के इन दोनों सेट से, लिखने की सीमा ट्रिगर होती है:
100000, 100001, 100002, 100003, ...
0, 1, 2, 3, ...
टाइमस्टैंप फ़ील्ड को अलग-अलग हिस्सों में बांटना
मान लें कि आपका ऐप्लिकेशन, लगातार बढ़ते हुए timestamp
फ़ील्ड का इस्तेमाल करता है.
अगर आपका ऐप्लिकेशन किसी भी क्वेरी में timestamp
फ़ील्ड का इस्तेमाल नहीं करता है, तो टाइमस्टैंप फ़ील्ड को इंडेक्स न करके, हर सेकंड में 500 रिकॉर्ड करने की सीमा को हटाया जा सकता है. अगर आपको अपनी क्वेरी के लिए timestamp
फ़ील्ड की ज़रूरत है, तो शर्ड किए गए टाइमस्टैंप का इस्तेमाल करके, इस सीमा को बढ़ाया जा सकता है:
timestamp
फ़ील्ड के साथshard
फ़ील्ड जोड़ें.shard
फ़ील्ड के लिए,1..n
अलग-अलग वैल्यू का इस्तेमाल करें. इससे कलेक्शन में डेटा लिखने की सीमा500*n
हो जाती है. हालांकि, आपकोn
क्वेरी इकट्ठा करनी होंगी.- अपने लिखने के लॉजिक को अपडेट करें, ताकि हर दस्तावेज़ को किसी भी क्रम में
shard
वैल्यू असाइन की जा सके. - अलग-अलग हिस्सों में बंटे नतीजों के सेट को इकट्ठा करने के लिए, अपनी क्वेरी अपडेट करें.
shard
फ़ील्ड औरtimestamp
फ़ील्ड, दोनों के लिए एक फ़ील्ड वाले इंडेक्स बंद करें. ऐसे मौजूदा कंपोजिट इंडेक्स मिटाएं जिनमेंtimestamp
फ़ील्ड शामिल है.- अपडेट की गई क्वेरी के साथ काम करने के लिए, नए कंपोजिट इंडेक्स बनाएं. इंडेक्स में फ़ील्ड का क्रम ज़रूरी है. साथ ही,
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]); });
कुछ रिसर्च करने के बाद, आपको पता चलता है कि ऐप्लिकेशन को हर सेकंड 1,000 से 1,500 इंस्ट्रूमेंट अपडेट मिलेंगे. यह इंडेक्स किए गए टाइमस्टैंप फ़ील्ड वाले दस्तावेज़ों वाले कलेक्शन के लिए, हर सेकंड 500 बदलाव करने की अनुमति से ज़्यादा है. डेटा डालने की दर बढ़ाने के लिए, आपको तीन शर्ड वैल्यू,
MAX_INSTRUMENT_UPDATES/500 = 3
की ज़रूरत होगी. इस उदाहरण में, x
,
y
, और z
जैसी शर्ड वैल्यू का इस्तेमाल किया गया है. आपके पास अपने स्HARD की वैल्यू के लिए, संख्याओं या अन्य वर्णों का इस्तेमाल करने का विकल्प भी होता है.
शार्ड फ़ील्ड जोड़ना
अपने दस्तावेज़ों में shard
फ़ील्ड जोड़ें. shard
फ़ील्ड को x
, y
या z
वैल्यू पर सेट करें. इससे कलेक्शन में, हर सेकंड 1,500 रिकॉर्ड लिखे जा सकते हैं.
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 कंसोल
Firebase कंसोल में, Cloud Firestore कंपोजिट इंडेक्स पेज खोलें.
जिन इंडेक्स में
timestamp
फ़ील्ड है उनके लिए, बटन पर क्लिक करें. इसके बाद, मिटाएं पर क्लिक करें.
जीसीपी कंसोल
Google Cloud Console में, डेटाबेस पेज पर जाएं.
डेटाबेस की सूची से वह डेटाबेस चुनें जिसकी ज़रूरत है.
नेविगेशन मेन्यू में, इंडेक्स पर क्लिक करें. इसके बाद, कंपोज़िट टैब पर क्लिक करें.
फ़िल्टर फ़ील्ड का इस्तेमाल करके, इंडेक्स की उन परिभाषाओं को खोजें जिनमें
timestamp
फ़ील्ड शामिल है.इनमें से हर इंडेक्स के लिए,
button पर क्लिक करें. इसके बाद, मिटाएं पर क्लिक करें.
Firebase CLI
- अगर आपने Firebase CLI सेट अप नहीं किया है, तो सीएलआई इंस्टॉल करने और
firebase init
कमांड चलाने के लिए, इन निर्देशों का पालन करें.init
निर्देश देते समय,Firestore: Deploy rules and create indexes for Firestore
को चुनना न भूलें. - सेटअप के दौरान, Firebase CLI आपकी मौजूदा इंडेक्स परिभाषाओं को
firestore.indexes.json
नाम की डिफ़ॉल्ट फ़ाइल में डाउनलोड करता है. 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" } ] }, ] }
इंडेक्स की अपडेट की गई परिभाषाएं डिप्लॉय करें:
firebase deploy --only firestore:indexes
एक फ़ील्ड वाले इंडेक्स की परिभाषाएं अपडेट करना
Firebase कंसोल
Firebase कंसोल में, Cloud Firestore सिंगल फ़ील्ड इंडेक्स पेज खोलें.
छूट जोड़ें पर क्लिक करें.
कलेक्शन आईडी के लिए,
instruments
डालें. फ़ील्ड पाथ के लिए,timestamp
डालें.क्वेरी का दायरा में जाकर, कलेक्शन और कलेक्शन ग्रुप, दोनों को चुनें.
आगे बढ़ें पर क्लिक करें
इंडेक्स की सभी सेटिंग को बंद है पर टॉगल करें. सेव करें पर क्लिक करें.
shard
फ़ील्ड के लिए भी यही तरीका दोहराएं.
जीसीपी कंसोल
Google Cloud Console में, डेटाबेस पेज पर जाएं.
डेटाबेस की सूची से वह डेटाबेस चुनें जिसकी ज़रूरत है.
नेविगेशन मेन्यू में, इंडेक्स पर क्लिक करें. इसके बाद, सिंगल फ़ील्ड टैब पर क्लिक करें.
सिंगल फ़ील्ड टैब पर क्लिक करें.
छूट जोड़ें पर क्लिक करें.
कलेक्शन आईडी के लिए,
instruments
डालें. फ़ील्ड पाथ के लिए,timestamp
डालें.क्वेरी का दायरा में जाकर, कलेक्शन और कलेक्शन ग्रुप, दोनों को चुनें.
आगे बढ़ें पर क्लिक करें
इंडेक्स की सभी सेटिंग को बंद है पर टॉगल करें. सेव करें पर क्लिक करें.
shard
फ़ील्ड के लिए भी यही तरीका दोहराएं.
Firebase CLI
इंडेक्स की परिभाषाओं वाली फ़ाइल के
fieldOverrides
सेक्शन में ये चीज़ें जोड़ें:{ "fieldOverrides": [ // Disable single-field indexing for the timestamp field { "collectionGroup": "instruments", "fieldPath": "timestamp", "indexes": [] }, ] }
इंडेक्स की अपडेट की गई परिभाषाएं डिप्लॉय करें:
firebase deploy --only firestore:indexes
नए कंपोजिट इंडेक्स बनाना
timestamp
वाले सभी पुराने इंडेक्स हटाने के बाद,
अपने ऐप्लिकेशन के लिए ज़रूरी नए इंडेक्स तय करें. timestamp
फ़ील्ड वाले किसी भी इंडेक्स में, shard
फ़ील्ड भी होना चाहिए. उदाहरण के लिए, ऊपर दी गई क्वेरी के साथ काम करने के लिए, ये इंडेक्स जोड़ें:
कलेक्शन | इंडेक्स किए गए फ़ील्ड | क्वेरी का स्कोप |
---|---|---|
इंस्ट्रूमेंट | shard, price.currency, timestamp | कलेक्शन |
इंस्ट्रूमेंट | शर्ड, एक्सचेंज, टाइमस्टैंप | कलेक्शन |
इंस्ट्रूमेंट | शर्ड, instrumentType, टाइमस्टैंप | कलेक्शन |
गड़बड़ी के मैसेज
अपडेट की गई क्वेरी चलाकर, ये इंडेक्स बनाए जा सकते हैं.
हर क्वेरी से, गड़बड़ी का एक मैसेज मिलता है. साथ ही, Firebase कंसोल में ज़रूरी इंडेक्स बनाने के लिए एक लिंक भी मिलता है.
Firebase CLI
इंडेक्स की परिभाषा वाली फ़ाइल में ये इंडेक्स जोड़ें:
{ "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" } ] }, ] }
इंडेक्स की अपडेट की गई परिभाषाएं डिप्लॉय करें:
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 के लिए ज़रूरत के मुताबिक जगह बन जाती है, ताकि एंट्री को कई टैबलेट में बांटा जा सके.
आगे क्या करना है
- बड़े पैमाने पर डिज़ाइन करने के सबसे सही तरीके पढ़ें
- किसी एक दस्तावेज़ में डेटा लिखने की दर ज़्यादा होने पर, डिस्ट्रिब्यूट किए गए काउंटर देखें
- Cloud Firestore के लिए स्टैंडर्ड सीमाएं देखें