800 मिलियन ChatGPT युजर्सच्या समर्थनासाठी PostgreSQL स्केलिंग
बोहान झांग, तांत्रिक कर्मचारी सदस्य म्हणून
वर्षानुवर्षे, PostgreSQL हे ChatGPT आणि OpenAI च्या API सारख्या मुख्य उत्पादनांना चालवणाऱ्या सर्वात महत्त्वाच्या, पडद्यामागील डेटा प्रणालींपैकी एक आहे. आमचा वापरकर्ता आधार झपाट्याने वाढत असल्यामुळे, आमच्या डेटाबेसवरील मागण्या देखील घातांकीय पद्धतीने वाढल्या आहेत. गेल्या वर्षभरात, आमचा PostgreSQL लोड 10 पट पेक्षा जास्त वाढला आहे, आणि तो अजूनही झपाट्याने वाढत आहे.
या वाढीला टिकवून ठेवण्यासाठी आमच्या उत्पादन पायाभूत सुविधांना पुढे नेण्याच्या प्रयत्नांतून एक नवीन अंतर्दृष्टी समोर आली: PostgreSQL ला पूर्वी अनेकांना शक्य वाटत होते त्यापेक्षा खूप मोठ्या वाचन-प्रधान कामांच्या भाराला विश्वसनीयपणे समर्थन देण्यासाठी स्केल करता येते. ही प्रणाली (सुरुवातीला कॅलिफोर्निया विद्यापीठ, बर्कले येथील शास्त्रज्ञांच्या संघाने तयार केलेली) आम्हाला एकाच प्राथमिक Azure PostgreSQL लवचिक सर्व्हर उदाहरण(नवीन विंडोमध्ये उघडेल) आणि जगभरातील अनेक प्रदेशांमध्ये पसरलेल्या जवळजवळ 50 रीड रेप्लिकां सह प्रचंड जागतिक ट्रॅफिकला समर्थन देण्यास सक्षम करते. ही कथा आहे की OpenAI मध्ये आम्ही PostgreSQL कसे स्केल केले, ज्यामुळे कठोर ऑप्टिमायझेशन्स आणि मजबूत अभियांत्रिकीच्या माध्यमातून 800 मिलियन वापरकर्त्यांसाठी प्रति सेकंद लाखो क्वेरीजना समर्थन देता येईल. तसेच, या प्रवासात आम्ही शिकलेल्या मुख्य शिकवणीही आम्ही मांडू.
ChatGPT लाँच झाल्यानंतर, रहदारी अभूतपूर्व वेगाने वाढली. त्यासाठी आम्ही ॲप्लिकेशन आणि PostgreSQL डेटाबेस या दोन्ही थरात त्वरीत व्यापक ऑप्टिमायझेशन्स अंमलात आणल्या, इन्स्टन्सचा आकार वाढवून स्केल अप केले आणि अधिक रीड रेप्लिका जोडून स्केल आउट केले. हे आर्किटेक्चर आमच्यासाठी बराच काळ चांगले काम करत आहे. सतत सुधारणा होत असताना, भविष्यातील वाढीसाठी ते पुरेशी संधी उपलब्ध करून देत आहे.
हे आश्चर्यकारक वाटू शकते की एकल-प्राथमिक आर्किटेक्चर OpenAI च्या स्केल करण्याच्या मागण्या पूर्ण करू शकते; परंतु, प्रत्यक्षात हे कार्यान्वित करणे सोपे नाही. आम्ही Postgres ओव्हरलोडमुळे उद्भवलेल्या अनेक SEVs पाहिल्या आहेत, आणि त्या बहुतेक वेळा एकाच पद्धतीने घडतात: अपस्ट्रीममधील एखाद्या समस्येमुळे डेटाबेस लोडमध्ये अचानक वाढ होते, जसे की कॅशिंग-लेयरच्या अपयशामुळे व्यापक कॅश मिस, महागड्या मल्टी-वे जॉइन्समुळे CPU पूर्णपणे भरून जाणे, किंवा नवीन विशिष्ट लाँचमुळे होणारे राइट स्टॉर्म. संसाधनांचा वापर वाढत असताना, क्वेरी लेटन्सी वाढते आणि विनंत्या वेळ संपून थांबतात. पुन्हा प्रयत्न केल्याने लोड आणखी वाढतो, ज्यामुळे एक दुष्ट चक्र सुरू होते आणि संपूर्ण ChatGPT आणि API सेवांची कार्यक्षमता कमी होऊ शकते.
PostgreSQL आमच्या वाचन-केंद्रित वर्कलोडसाठी चांगल्या प्रकारे स्केल होते, परंतु उच्च लेखन ट्रॅफिकच्या कालावधीत आम्हाला अजूनही आव्हानांचा सामना करावा लागतो. हे मुख्यत्वे PostgreSQL च्या बहु-आवृत्ती समवर्ती नियंत्रण (MVCC) अंमलबजावणीमुळे आहे, ज्यामुळे लेखन-प्रधान वर्कलोड्ससाठी ते कमी कार्यक्षम ठरते. उदाहरणार्थ, जेव्हा एखादी क्वेरी ट्युपल किंवा एकच फील्ड अद्ययावत करते, तेव्हा नवीन आवृत्ती तयार करण्यासाठी संपूर्ण पंक्तीची प्रत केली जाते. जड लेखन भारांखाली, यामुळे लक्षणीय लेखन प्रवर्धन होते. यामुळे read amplification देखील वाढते, कारण क्वेरीजना नवीनतम आवृत्ती मिळवण्यासाठी अनेक tuple आवृत्त्यांमधून (मृत tuples) स्कॅन करावे लागते. MVCC अतिरिक्त आव्हाने निर्माण करते, जसे की टेबल आणि निर्देशांक फुगणे, वाढलेला निर्देशांक देखभाल ओव्हरहेड, आणि गुंतागुंतीचे ऑटोव्हॅक्यूम ट्यूनिंग. (या मुद्द्यांवरचा सखोल आढावा तुम्हाला मी प्रा. अँडी पॅव्हलो यांच्यासोबत कार्नेगी मेलॉन युनिव्हर्सिटीमध्ये लिहिलेल्या The Part of PostgreSQL We Hate the Most(नवीन विंडोमध्ये उघडेल) या ब्लॉगमध्ये मिळेल, जो PostgreSQL विकिपीडिया पानावर उद्धृत(नवीन विंडोमध्ये उघडेल) आहे.)
या मर्यादा कमी करण्यासाठी आणि लेखन दाब कमी करण्यासाठी, आम्ही स्थलांतर केले आहे, आणि स्थलांतर करत आहोत, विभाज्य (उदा. आडव्या पद्धतीने विभाजित करता येणारे कार्यभार), Azure Cosmos DB सारख्या शार्डेड प्रणालींसाठी लेखन-प्रधान कार्यभार, अनावश्यक लेखन कमी करण्यासाठी अनुप्रयोग लॉजिक अनुकूलित करणे. आम्ही सध्याच्या PostgreSQL तैनातीमध्ये नवीन टेबल्स जोडण्याची परवानगी आता देत नाही. नवीन वर्कलोड्स डीफॉल्टने शार्डेड सिस्टीम्सवर जातात.
आमच्या पायाभूत रचनेत बदल होत असतानाही, PostgreSQL शार्डिंग न करता राहिले आहे, आणि सर्व लेखनांसाठी एकच प्राथमिक इन्स्टन्स सेवा देत आहे. मुख्य कारण असे आहे की विद्यमान अनुप्रयोग वर्कलोड्सचे शार्डिंग करणे अत्यंत गुंतागुंतीचे आणि वेळखाऊ ठरेल, ज्यासाठी शेकडो अनुप्रयोग एंडपॉइंट्समध्ये बदल करावे लागतील आणि यासाठी अनेक महिने किंवा अगदी वर्षे लागू शकतात. आमचे वर्कलोड्स मुख्यतः वाचन-केंद्रित असल्यामुळे, आणि आम्ही व्यापक ऑप्टिमायझेशन्स अंमलात आणल्यामुळे, सध्याचे आर्किटेक्चर सतत वाढणाऱ्या ट्रॅफिकला समर्थन देण्यासाठी अजूनही पुरेशी जागा प्रदान करते. भविष्यात PostgreSQL चे शार्डिंग करण्याची शक्यता आम्ही नाकारत नाही, परंतु सध्याच्या आणि भविष्यातील वाढीसाठी आमच्याकडे पुरेसा मार्ग असल्याने ते नजीकच्या काळात प्राधान्य नाही.
पुढील विभागांमध्ये, आम्ही समोर आलेल्या आव्हानांवर आणि त्यांना हाताळण्यासाठी तसेच भविष्यातील आउटेजेस टाळण्यासाठी अंमलात आणलेल्या व्यापक ऑप्टिमायझेशन्सवर चर्चा करू. PostgreSQL ला त्याच्या मर्यादांपर्यंत ढकलत आणि ते प्रति सेकंद लाखो क्वेरीज (QPS) पर्यंत स्केल करत आहोत.
आव्हान: फक्त एकच लेखक असल्याने, एकल-प्राथमिक सेटअप लेखनाची क्षमता वाढवू शकत नाही. जास्त प्रमाणातील लेखन स्पाइक्समुळे प्राथमिक प्रणाली पटकन ओव्हरलोड होऊ शकते आणि ChatGPT आणि आमच्या API सारख्या सेवांवर परिणाम होऊ शकतो.
उपाय: write spikes हाताळण्यासाठी त्याच्याकडे पुरेशी क्षमता राहील याची खात्री करण्यासाठी, आम्ही primary वरचा लोड शक्य तितका कमी करतो—reads आणि writes दोन्ही. वाचन ट्रॅफिक शक्य तिथे प्रतिकृतींवर ऑफलोड केले जाते. तथापि, काही वाचन क्वेरी प्राथमिकवरच राहिल्या पाहिजेत कारण त्या लेखन व्यवहारांचा भाग असतात. त्यांच्यासाठी, आम्ही ते कार्यक्षम आहेत याची खात्री करण्यावर लक्ष केंद्रित करतो आणि मंद क्वेरीज टाळण्याचा प्रयत्न करतो. लेखन वाहतुकीसाठी, आम्ही विभाजनीय, लेखन-गंभीर कार्यभार Azure CosmosDB सारख्या विभाजित प्रणालींमध्ये स्थलांतरित केले आहेत. शार्ड करणे कठीण असलेले, परंतु उच्च लेखन खंड निर्माण करणारे वर्कलोड्स स्थलांतरित करण्यास अधिक वेळ घेतात, आणि ती प्रक्रिया अद्याप सुरू आहे. आम्ही write load कमी करण्यासाठी आमच्या अनुप्रयोगांचेही आक्रमकपणे ऑप्टिमायझेशन केले; उदाहरणार्थ, redundant writes होण्यास कारणीभूत ठरणारे अनुप्रयोगातील बग आम्ही दुरुस्त केले आणि योग्य ठिकाणी lazy writes लागू केले, जेणेकरून traffic spikes मुळे होणारे चढउतार सुरळीत होतील. याव्यतिरिक्त, टेबल फील्ड्स बॅकफिल करताना, अतिप्रमाणात लेखन दाब टाळण्यासाठी आम्ही कठोर दर मर्यादा लागू करतो.
आव्हान: आम्ही PostgreSQL मध्ये अनेक महागड्या क्वेरी ओळखल्या आहेत. पूर्वी, या क्वेरीजमध्ये अचानक व्हॉल्यूम स्पाइक्स आल्यास CPU चा मोठ्या प्रमाणात वापर होत असे, ज्यामुळे ChatGPT आणि API विनंत्या दोन्ही मंदावल्या जात.
उपाय: अनेक टेबल्स एकत्र जोडणाऱ्या काही महागड्या क्वेरीज संपूर्ण सेवेची कार्यक्षमता लक्षणीयरीत्या कमी करू शकतात किंवा संपूर्ण सेवा बंद पाडू शकतात. आपल्याला PostgreSQL क्वेरी सतत ऑप्टिमाइझ करणे आवश्यक आहे, जेणेकरून त्या कार्यक्षम राहतील आणि सामान्य Online Transaction Processing (OLTP) विरोधी नमुने टाळता येतील. उदाहरणार्थ, आम्ही एकदा १२ टेबल्स जोडणारी अत्यंत खर्चिक क्वेरी ओळखली होती, जिच्या स्पाइक्समुळे पूर्वी उच्च-तीव्रतेच्या SEVs घडल्या होत्या. शक्य असेल तेव्हा गुंतागुंतीचे मल्टी-टेबल जॉइन्स टाळावेत. जर जोड आवश्यक असतील, तर आम्ही क्वेरीचे विभाजन करण्याचा विचार करायला आणि गुंतागुंतीचे जोड तर्क application स्तरावर हलवायला शिकलो. यापैकी अनेक समस्याप्रधान क्वेरी Object-Relational Mapping frameworks (ORMs) द्वारे तयार केल्या जातात, त्यामुळे त्यांनी तयार केलेल्या SQL चे काळजीपूर्वक पुनरावलोकन करणे आणि ते अपेक्षेप्रमाणे कार्य करते याची खात्री करणे महत्त्वाचे आहे. PostgreSQL मध्ये दीर्घकाळ चालणाऱ्या निष्क्रिय क्वेरी आढळणे सामान्य आहे. idle_in_transaction_session_timeout सारखे टाइमआउट्स कॉन्फिगर करणे autovacuum ला ब्लॉक होण्यापासून रोखण्यासाठी आवश्यक आहे.
आव्हान: जर एखादी रीड रेप्लिका बंद पडली, तरीही ट्रॅफिक इतर रेप्लिकांकडे वळवला जाऊ शकतो. तथापि, एकाच लेखकावर अवलंबून राहणे म्हणजे अपयशाचा एकच बिंदू असणे—जर तो बंद पडला, तर संपूर्ण सेवा प्रभावित होते.
उपाय: सर्वात गंभीर विनंत्या केवळ वाचन क्वेरीजच समाविष्ट करतात. प्राथमिकमधील एकाच अपयश-बिंदूचा धोका कमी करण्यासाठी, आम्ही लेखकावरून त्या वाचनांना प्रतिकृतींवर ऑफलोड केले, ज्यामुळे प्राथमिक डाउन झाला तरीही त्या विनंत्या सेवा देत राहू शकतील. लेखन ऑपरेशन्स अजूनही अयशस्वी होतील, परंतु परिणाम कमी झाला आहे; वाचन उपलब्ध असल्यामुळे आता ते SEV0 राहिलेले नाही.
प्राथमिक अपयश कमी करण्यासाठी, आम्ही प्राथमिक प्रणालीला उच्च-उपलब्धता (HA) मोडमध्ये एक गरम स्टँडबाय, सतत समक्रमित प्रतिकृतीसह चालवतो, जी नेहमीच वाहतूक सेवा देण्याची जबाबदारी घेण्यासाठी तयार असते. जर प्राथमिक सर्व्हर बंद पडला किंवा देखभालीसाठी ऑफलाइन करावा लागला, तर आम्ही डाउनटाइम कमी करण्यासाठी स्टँडबाय सर्व्हरला त्वरीत सक्रिय करू शकतो. Azure PostgreSQL संघाने अतिशय उच्च लोडखालीसुद्धा या फेलओव्हर्सना सुरक्षित आणि विश्वासार्ह ठेवण्यासाठी लक्षणीय काम केले आहे. रीड रेप्लिका अयशस्वी होण्याची समस्या हाताळण्यासाठी, आम्ही प्रत्येक प्रदेशात पुरेशी क्षमता राखून अनेक रेप्लिका तैनात करतो, ज्यामुळे एका रेप्लिकाच्या अयशस्वीतेमुळे प्रादेशिक आउटेज होत नाही.
चॅलेंज: आम्हाला अनेकदा अशा परिस्थितींचा सामना करावा लागतो जिथे काही विनंत्या PostgreSQL इन्स्टन्सेसवर असमान प्रमाणात संसाधने वापरतात. यामुळे त्याच इन्स्टन्सेसवर चालणाऱ्या इतर वर्कलोड्सची कार्यक्षमता कमी होऊ शकते. उदाहरणार्थ, नवीन वैशिष्ट्याचे प्रक्षेपण अशा अकार्यक्षम क्वेरीज आणू शकते ज्या PostgreSQL CPU चा मोठ्या प्रमाणात वापर करतात, ज्यामुळे इतर महत्त्वाच्या वैशिष्ट्यांसाठीच्या विनंत्या मंदावतात.
उपाय: लेटन्सी कमी करण्यासाठी आम्ही अनेक भौगोलिक प्रदेशांमध्ये जवळपास 50 रीड रेप्लिका चालवतो. तथापि, सध्याच्या आर्किटेक्चरमध्ये, प्राथमिक सर्व प्रतिकृतींना WAL प्रवाहित करणे आवश्यक आहे. जरी ते सध्या अतिशय मोठ्या इन्स्टन्स प्रकारांबरोबर आणि उच्च नेटवर्क बँडविड्थसह चांगल्या प्रकारे स्केल होत असले, तरी आम्ही अनंत काळापर्यंत रेप्लिका जोडत राहू शकत नाही कारण शेवटी प्राथमिक ओव्हरलोड होईल. हे सोडवण्यासाठी, आम्ही Azure PostgreSQL संघासोबत कॅस्केडिंग रेप्लिकेशन(नवीन विंडोमध्ये उघडेल) वर सहकार्य करत आहोत, जिथे मधल्या रेप्लिका डाउनस्ट्रीम रेप्लिकांना WAL पाठवतात. हा दृष्टिकोन आम्हाला मुख्य प्रणालीवर ताण न आणता संभाव्यतः शंभरहून अधिक प्रतिकृतींमध्ये विस्तार करण्यास अनुमती देतो. तथापि, यामुळे अतिरिक्त ऑपरेशनल गुंतागुंतही येते, विशेषतः फेलओव्हर व्यवस्थापनाच्या बाबतीत. हे वैशिष्ट्य अजूनही चाचणीमध्ये आहे; आम्ही याची खात्री करू की ते मजबूत आहे आणि प्रॉडक्शनमध्ये रोलआउट करण्यापूर्वी सुरक्षितपणे फेलओव्हर होऊ शकते.
आव्हान: विशिष्ट एंडपॉइंट्सवर अचानक रहदारीत वाढ, महागड्या क्वेरीजमध्ये उधाण, किंवा रिट्राय स्टॉर्ममुळे CPU, I/O, आणि कनेक्शन्स यांसारखी महत्त्वाची संसाधने पटकन संपुष्टात येऊ शकतात, ज्यामुळे व्यापक सेवा ऱ्हास होतो.
उपाय: आम्ही डेटाबेस इन्स्टन्सेसवर अचानक रहदारीच्या वाढीचा भार पडू नये आणि कॅस्केडिंग अपयश होऊ नये म्हणून अॅप्लिकेशन, कनेक्शन पूलर, प्रॉक्सी आणि क्वेरी—अशा अनेक स्तरांवर दर मर्यादित केले. पुन्हा प्रयत्न करण्याचे अंतर खूपच कमी ठेवणे टाळणेही अत्यंत महत्त्वाचे आहे, कारण त्यामुळे पुनःप्रयत्न वादळे ट्रिगर होऊ शकतात. आम्ही ORM लेयरमध्ये सुधारणा केल्या आहेत ज्यामुळे दर मर्यादांना समर्थन मिळते आणि आवश्यकतेनुसार, विशिष्ट क्वेरी डाइजेस्ट्स पूर्णपणे अवरोधित करता येतात. लोड शेडिंगचा हा लक्षित प्रकार महागड्या क्वेरींमध्ये अचानक वाढ झाल्यास जलद पुनर्प्राप्ती सक्षम करतो.
आव्हान: स्तंभाचा प्रकार बदलण्यासारखा अगदी लहान स्कीमा बदलसुद्धा संपूर्ण टेबल पुन्हा लिहिण्यास प्रवृत्त करू शकतो(नवीन विंडोमध्ये उघडेल). त्यामुळे आम्ही स्कीमा बदल सावधपणे करतो—त्यांना हलक्या ऑपरेशन्सपुरते मर्यादित ठेवतो आणि संपूर्ण टेबल पुन्हा लिहिणारे कोणतेही बदल टाळतो.
उपाय: फक्त हलके स्कीमा बदल परवानगीचे आहेत, जसे की पूर्ण टेबल पुन्हा लिहिणे ट्रिगर न करणारे काही कॉलम जोडणे किंवा काढणे. आम्ही स्कीमा बदलांवर कठोर पाच सेकंदांचा टाइमआउट लागू करतो. निर्देशांक एकाच वेळी तयार करणे आणि काढून टाकणे अनुमत आहे. स्कीमा बदल विद्यमान तक्त्यांपुरते मर्यादित आहेत. जर एखाद्या नवीन वैशिष्ट्यासाठी अतिरिक्त टेबल्सची आवश्यकता असेल, तर ती PostgreSQL ऐवजी Azure CosmosDB सारख्या पर्यायी शार्डेड प्रणालींमध्ये असावीत. टेबल फील्ड बॅकफिल करताना, लेखन स्पाइक्स टाळण्यासाठी आम्ही कडक दर मर्यादा लागू करतो. जरी या प्रक्रियेला कधी कधी एका आठवड्यापेक्षा जास्त वेळ लागू शकतो, तरी ती स्थिरता सुनिश्चित करते आणि उत्पादनावर कोणताही परिणाम होण्यापासून टाळते.
हा प्रयत्न दर्शवतो की योग्य डिझाइन आणि ऑप्टिमायझेशन्ससह, Azure PostgreSQL सर्वात मोठ्या उत्पादन वर्कलोड्स हाताळण्यासाठी स्केल केला जाऊ शकतो. PostgreSQL वाचन-प्रधान वर्कलोड्ससाठी लाखो QPS हाताळते, OpenAI च्या सर्वात महत्त्वाच्या उत्पादनांना, जसे की ChatGPT आणि API प्लॅटफॉर्म, समर्थित करते. आम्ही जवळपास 50 रीड रेप्लिका जोडल्या, प्रतिकृतीकरण विलंब जवळजवळ शून्यावर ठेवला, भौगोलिकदृष्ट्या वितरित प्रदेशांमध्ये कमी विलंब असलेली वाचन सेवा राखली, आणि भविष्यातील वाढीसाठी पुरेशी क्षमता राखीव ठेवली.
हे स्केलिंग विलंब कमी करत आणि विश्वासार्हता सुधारत असतानाही कार्य करते. आम्ही उत्पादनात सातत्याने कमी दुहेरी-अंकी मिलिसेकंद p99 क्लायंट-साइड विलंबता आणि पाच-नऊ उपलब्धता प्रदान करतो. आणि गेल्या 12 महिन्यांत, आमच्याकडे फक्त एकच SEV-0 PostgreSQL घटना झाली आहे (ती ChatGPT ImageGen च्या व्हायरल लाँच(नवीन विंडोमध्ये उघडेल) दरम्यान घडली, जेव्हा एका आठवड्यात १०० मिलियनहून अधिक नवीन वापरकर्त्यांनी साइन अप केल्यामुळे लेखन ट्रॅफिक अचानक १० पट वाढला.)
PostgreSQL ने आम्हाला आतापर्यंत किती पुढे नेले आहे याबद्दल आम्ही समाधानी असलो तरी, भविष्यातील वाढीसाठी पुरेसा मार्ग उपलब्ध राहील याची खात्री करण्यासाठी आम्ही त्याच्या मर्यादा सतत पुढे ढकलत आहोत. आम्ही आधीच विभाजनीय, लेखन-प्रधान कार्यभार आमच्या CosmosDB सारख्या विभाजित प्रणालींमध्ये स्थलांतरित केले आहेत. उरलेले राइट हेवी वर्कलोड शार्ड करणे अधिक आव्हानात्मक आहे—आम्ही त्यांचेही सक्रियपणे स्थलांतर करत आहोत, जेणेकरून PostgreSQL प्राथमिक सर्व्हरवरील लिहिण्याचे कार्य आणखी कमी करता येईल. आम्ही Azure सोबतही कास्केडिंग रिप्लिकेशन सक्षम करण्यासाठी काम करत आहोत, ज्यामुळे आम्ही सुरक्षितपणे खूप अधिक रीड रिप्लिकापर्यंत स्केल करू शकू.
पुढे पाहता, आमच्या पायाभूत सुविधांच्या गरजा वाढत असताना, आम्ही पुढील स्केलिंगसाठी अतिरिक्त पद्धतींचा शोध सुरू ठेवू, ज्यामध्ये शार्डेड PostgreSQL किंवा पर्यायी वितरित प्रणालींचा समावेश असेल.
लेखक
आभार
या पोस्टमध्ये योगदान दिल्याबद्दल Jon Lee, Sicheng Liu, Chaomin Yu, आणि Chenglong Hao यांचे विशेष आभार, तसेच PostgreSQL चे स्केलिंग करण्यात मदत करणाऱ्या संपूर्ण टीमचेही आभार. आम्ही Azure PostgreSQL टीमचे त्यांच्या मजबूत भागीदारीसाठी आभार मानू इच्छितो.


