कुछ समय पहले, हमें Drizzle ORM के साथ सहयोग करने का अवसर मिला।
यह देखकर कि यह TypeScript ORM समुदाय द्वारा कितना पसंद किया जाता है, हमारा "हां 😳" कहने का निर्णय आसान था:

इस लेख में, हम देखेंगे कि कैसे हमारा Upstash Redis x Drizzle कैशिंग एकीकरण SQL प्रदर्शन में सुधार करता है, और कैसे हमने एकीकरण को अनुकूलित करने के लिए Lua स्क्रिप्ट्स और हैश डेटा संरचना का उपयोग किया।
चुनौती: आधुनिक एप्लिकेशन में SQL प्रदर्शन
पारंपरिक SQL डेटाबेस स्थिरता और जटिल संबंधों को मॉडल करने में महान हैं, लेकिन वे संघर्ष कर सकते हैं:
- उच्च विलंबता वितरित वातावरण में
- कनेक्शन पूलिंग सीमाएं सर्वरलेस फ़ंक्शंस में
- दोहरावदार क्वेरी ओवरहेड अक्सर एक्सेस किए गए डेटा के लिए
- स्केलिंग बॉटलनेक्स भारी रीड लोड के तहत
समाधान? एक कैशिंग लेयर जो आपके डेटा संबंधों को समझता है और स्वचालित रूप से कैश अमान्यकरण का प्रबंधन करता है।
Upstash x Drizzle कैशिंग कैसे काम करता है
रीड प्रदर्शन में सुधार: फॉलबैक के साथ कैश-फर्स्ट
जब आप Drizzle कैशिंग सक्षम के साथ एक क्वेरी निष्पादित करते हैं, तो हमारा एकीकरण पहले Redis में कैश्ड परिणाम की जांच करता है:
- कैश मिस: यदि नहीं मिला, तो क्वेरी आपके डेटाबेस से पढ़ती है। परिणाम निर्भर टेबल्स के बारे में मेटाडेटा के साथ Redis में संग्रहीत होता है
- कैश हिट: बाद की समान क्वेरीज़ रिलेशनल डेटाबेस से पढ़े बिना तुरंत Redis से लौटती हैं
// यह क्वेरी पहले Redis की जांच करती है और केवल जरूरत पड़ने पर डेटाबेस से पढ़ती है
const users = await db.select().from(usersTable)
.where(eq(usersTable.status, 'active'))
.$withCache();
राइट ऑपरेशंस के लिए स्मार्ट अमान्यकरण
जादू राइट ऑपरेशंस के दौरान होता है। जब आप अपने रिलेशनल डेटाबेस में डेटा संशोधित करते हैं, तो हमारा एकीकरण स्वचालित रूप से:
- निर्भरताओं की पहचान करता है: निर्धारित करता है कि कौन सी कैश्ड क्वेरीज़ संशोधित टेबल्स पर निर्भर करती हैं
- बैच अमान्यकरण: सभी प्रभावित कैश एंट्रीज़ को हटा देता है
// यह इंसर्ट स्वचालित रूप से usersTable पर निर्भर सभी कैश्ड क्वेरीज़ को अमान्य कर देता है
await db.insert(usersTable).values({ email: 'new@user.com', status: 'active' });
एक साधारण कैश बनाना: "नाइव" दृष्टिकोण
आइए उन समस्याओं को समझने के लिए सबसे सरल संभव कार्यान्वयन से शुरू करें जिन्हें हमारा कैशिंग एकीकरण हल करता है। क्वेरी परिणाम को कैश करते समय, हमें चाहिए:
- कैश्ड मान स्टोर करें
- अमान्यकरण के लिए इस क्वेरी पर निर्भर टेबल्स को ट्रैक करें
सरल कैश स्टोरेज
// कैश में आइटम जोड़ते समय
await redis.set(itemHash, cachedValue);
await Promise.all(
dependentTables.map((table) => redis.sadd(table, itemHash))
);
यह दृष्टिकोण कैश्ड परिणाम को की-वैल्यू पेयर के रूप में स्टोर करता है और प्रत्येक निर्भर टेबल के नाम पर सेट में आइटम हैश जोड़कर निर्भरताओं को ट्रैक करता है।
सरल कैश अमान्यकरण
// टेबल परिवर्तनों के आधार पर अमान्यकरण करते समय
const hashesToInvalidate = await redis.sunion(dependentTables);
await redis.del(...hashesToInvalidate);
यह उन सभी कैश्ड आइटम्स को ढूंढकर काम करता है जो संशोधित टेबल्स पर निर्भर करते हैं, फिर उन्हें हटा देता है।
इस "नाइव" दृष्टिकोण की समस्याएं
जबकि यह तकनीकी रूप से काम करता है, इस नाइव कार्यान्वयन में दो प्रदर्शन समस्याएं हैं।
समस्या 1: एकाधिक राउंड ट्रिप्स
अमान्यकरण प्रक्रिया के लिए दो अलग Redis ऑपरेशंस की आवश्यकता होती है:
- पहले, हम
SUNIONको हटाने के लिए कुंजियों की सूची प्राप्त करने के लिए कॉल करते हैं - फिर, हम चरण 1 के परिणाम के साथ
DELकॉल करते हैं
यह एक राउंड-ट्रिप निर्भरता बनाता है जहां दूसरा ऑपरेशन पहले के पूरा होने का इंतजार करना चाहिए।
समस्या 2: धीमा मास डिलीशन
DEL कमांड कई कुंजियों को अमान्य करते समय बॉटलनेक बन सकता है:
// यह संभावित रूप से हजारों कुंजियों को हटा सकता है
await redis.del(...hashesToInvalidate);
जब आपके पास users जैसी लोकप्रिय टेबल होती है जिसका सैकड़ों कैश्ड क्वेरीज़ द्वारा संदर्भ दिया जाता है, तो एक अपडेट सैकड़ों व्यक्तिगत Redis कुंजियों के डिलीशन को ट्रिगर कर सकता है। सैकड़ों या हजारों कुंजियों के साथ, यह संभावित रूप से बहुत धीमा हो सकता है।
समाधान 1: Lua स्क्रिप्ट्स
Upstash Redis में Lua स्क्रिप्ट मूल्यांकन के लिए पूर्ण समर्थन है।
Lua स्क्रिप्ट्स सर्वर-साइड पर कई Redis कमांड्स को निष्पादित करके राउंड-ट्रिप समस्या को हल करते हैं:
-- अमान्यकरण स्क्रिप्ट जो SUNION और DEL को जोड़ती है
local tables = KEYS -- टेबल नाम कुंजी के रूप में पास किए गए
local keysToDelete = {}
if #tables > 0 then
-- इन टेबल्स पर निर्भर सभी हैश प्राप्त करें
local hashesToInvalidate = redis.call('SUNION', unpack(tables))
-- डिलीशन के लिए तैयार करें
for _, hash in ipairs(hashesToInvalidate) do
keysToDelete[#keysToDelete + 1] = hash
end
-- टेबल सेट्स को स्वयं डिलीशन सूची में जोड़ें
for _, table in ipairs(tables) do
keysToDelete[#keysToDelete + 1] = table
end
-- सिंगल एटॉमिक डिलीशन
if #keysToDelete > 0 then
redis.call('DEL', unpack(keysToDelete))
end
end
Lua स्क्रिप्ट्स के लाभ:
- सिंगल राउंड ट्रिप: सभी ऑपरेशंस सर्वर-साइड होते हैं
- कम विलंबता: ऑपरेशंस के बीच कोई नेटवर्क ओवरहेड नहीं
- संगति: नेटवर्क मुद्दों के कारण आंशिक अपडेट का कोई जोखिम नहीं
समाधान 2: कुशल डिलीशन के लिए हैश-आधारित स्टोरेज
Lua स्क्रिप्ट्स के साथ भी, सैकड़ों व्यक्तिगत कुंजियों को हटाना हमारे जितना तेज़ हो सकता है उतना तेज़ नहीं हो सकता। Redis हैशेज एक बहुत अधिक कुशल समाधान प्रदान करते हैं:
हैश-आधारित दृष्टिकोण
प्रत्येक कैश्ड क्वेरी को एक अलग Redis कुंजी के रूप में स्टोर करने के बजाय, हम उन्हीं टेबल्स पर निर्भर क्वेरीज़ को हैश में समूहित करते हैं:
// पुराना दृष्टिकोण: प्रत्येक क्वेरी को अपनी कुंजी मिलती है
await redis.set('query_hash_1', result1);
await redis.set('query_hash_2', result2);
await redis.set('query_hash_3', result3);
// नया दृष्टिकोण: टेबल निर्भरताओं द्वारा क्वेरीज़ को समूहित करें
const compositeKey = 'users,posts'; // users और posts टेबल्स के लिए हैश कुंजी
await redis.hset(compositeKey, {
'query_hash_1': result1,
'query_hash_2': result2,
'query_hash_3': result3
});
हैशेज बहुत तेज़ क्यों हैं
जब users टेबल पर निर्भर क्वेरीज़ को अमान्य करते हैं:
// पुराना तरीका: कई व्यक्तिगत कुंजियों को हटाएं (धीमा)
await redis.del('query_hash_1', 'query_hash_2', /* ...सैकड़ों और... */);
// नया तरीका: संपूर्ण हैश टेबल हटाएं (तेज़)
await redis.del('__CT__users,posts');
प्रदर्शन लाभ:
- सिंगल डिलीशन ऑपरेशन: एक
DELकमांड सैकड़ों कैश्ड क्वेरीज़ को हटा देता है - मेमोरी दक्षता: Redis पूरे हैश टेबल्स को एक ऑपरेशन में मुक्त कर सकता है
- एटॉमिक सफाई: सभी संबंधित क्वेरीज़ एक साथ अमान्य हो जाती हैं
यह देखने के लिए कि अंत में Lua स्क्रिप्ट्स कैसी दिखती हैं, आप Drizzle रिपॉजिटरी में कार्यान्वयन देख सकते हैं।
बारीक नियंत्रण के लिए कैश टैग्स
टेबल-आधारित अमान्यकरण से परे, Drizzle बारीक कैश नियंत्रण के लिए कस्टम टैग्स का समर्थन करता है:
// कस्टम टैग के साथ कैश
const premiumUsers = await db.select().from(usersTable)
.where(eq(usersTable.plan, 'premium'))
.$withCache({ tag: 'premium_users' });
// बाद में, केवल इस विशिष्ट क्वेरी को अमान्य करें
await db.$cache?.invalidate({ tags: 'premium_users' });
ऑटो बनाम मैन्युअल अमान्यकरण
स्वचालित अमान्यकरण (डिफ़ॉल्ट): जब निर्भर टेबल्स बदलते हैं तो क्वेरीज़ अमान्य हो जाती हैं, डेटा स्थिरता सुनिश्चित करती हैं लेकिन अधिक आक्रामक कैश क्लियरिंग के साथ।
मैन्युअल अमान्यकरण: उन परिदृश्यों के लिए जहां अंतिम स्थिरता स्वीकार्य है, आप ऑटो-अमान्यकरण अक्षम कर सकते हैं और मैन्युअल रूप से नियंत्रित कर सकते हैं कि कैश कब साफ़ करना है:
// स्वचालित रूप से अमान्य नहीं होगा - एनालिटिक्स डेटा के लिए अच्छा
const monthlyStats = await db.select()
.from(analyticsTable)
.$withCache({ autoInvalidate: false });
// जरूरत पड़ने पर मैन्युअली अमान्य करें (जैसे दैनिक बैच जॉब)
await db.$cache?.invalidate({ tables: ['analyticsTable'] });
वास्तविक दुनिया के उपयोग के मामले
अब जब हमने एकीकरण के तकनीकी पक्ष को कवर कर लिया है, तो आइए देखें कि ये अवधारणाएं व्यावहारिक अनुप्रयोगों में कैसे अनुवादित होती हैं।
ई-कॉमर्स उत्पाद कैटलॉग
// स्वचालित अमान्यकरण के साथ उत्पाद सूचियों को कैश करें
const products = await db.select()
.from(productsTable)
.where(eq(productsTable.active, true))
.$withCache({ tag: 'active_products' });
// जब इन्वेंट्री बदलती है, तो कैश स्वचालित रूप से अमान्य हो जाता है
await db.update(productsTable)
.set({ stock: newStock })
.where(eq(productsTable.id, productId));
कंटेंट प्रबंधन
// मैन्युअल अमान्यकरण के साथ प्रकाशित लेखों को कैश करें
const articles = await db.select()
.from(articlesTable)
.where(eq(articlesTable.status, 'published'))
.$withCache({ autoInvalidate: false, tag: 'published_articles' });
// कंटेंट अपडेट होने पर मैन्युअली अमान्य करें
await db.$cache?.invalidate({ tags: 'published_articles' });
निष्कर्ष
Upstash Redis और Drizzle कैशिंग एकीकरण (काफी) न्यूनतम कोड परिवर्तनों के साथ SQL क्वेरी प्रदर्शन को नाटकीय रूप से सुधार सकता है और डेटाबेस लोड को कम कर सकता है।
जब आप कैश सक्षम करते हैं, तो आप उम्मीद कर सकते हैं:
- नाटकीय रूप से तेज़ कैश्ड डेटा के लिए क्वेरी प्रतिक्रिया समय
- कम डेटाबेस लोड और बेहतर स्केलेबिलिटी
Upstash Redis का वैश्विक वितरण और सर्वरलेस-फर्स्ट आर्किटेक्चर पे-एज-यू-गो मूल्य निर्धारण के साथ आधुनिक एप्लिकेशन के लिए एक बेहतरीन आधार है।
ई-कॉमर्स प्लेटफॉर्म, एनालिटिक्स डैशबोर्ड, कंटेंट प्रबंधन सिस्टम और अधिक के लिए बिल्कुल सही।
आगे पढ़ना
गहराई से गोता लगाना चाहते हैं? यहां कुछ बेहतरीन संसाधन हैं जिन्हें मैं आपको जांचने की सलाह देता हूं:
- Upstash Redis और Drizzle एकीकरण गाइड
- Drizzle कैशिंग दस्तावेज
- Upstash Redis के साथ शुरुआत
- Upstash Rate Limit SDK (TypeScript) - इष्टतम प्रदर्शन के लिए Lua स्क्रिप्ट्स का लाभ उठाने वाला एक और शक्तिशाली SDK
- Upstash Rate Limit SDK (Python) - Rate Limit SDK का Python कार्यान्वयन।