भाग 3: 26 मिनट में छोटा, सस्ता, Highly Available Database, और AI को अपना PostgreSQL क्यों मिला
एक बड़े MySQL node को 26-minute window में cheaper Standard cluster में standby के साथ move किया, standby को read के लिए split किया और AI को pgvector के साथ अपना PostgreSQL दिया। Real exam evening ने दिखाया database कभी bottleneck नहीं था।
5 अक्टूबर की रात 22:33 पर, हमने दोनों load balancer को एक empty tag पर point किया। जिसने site खोला maintenance page देखा। इसके पीछे, हर विद्यार्थी, answer और result एक नए database को copy किए जा रहे थे, और window जो हमने business के साथ agree किया वह 26 मिनट थी।
किसी को complain नहीं किया कि database slow था। Problem इसकी shape थी। यह एक large MySQL node था कोई standby नहीं के साथ, तो दिन जब machine fail होगा वह दिन होगा जब पूरा platform fail होगा। यह भी बड़ा था, और काम से ज़्यादा महंगा।
यह article NovaCommerce के data layer के बारे में है, platform जो course बेचता है और timed online exam चलाता है। यह 26-minute move को cover करता है, क्लाइंट जिसे हम भूल गए, कैसे हमने read और write को split किया, क्यों AI को अपना PostgreSQL मिला, और 7 अक्टूबर की real exam evening ने इसके बारे में क्या कहा। यह Part 2 को follow करता है, जहाँ application layer scale करना सीखा।
यह "From One Server to Exam-Day Ready" five-part case study का हिस्सा 3 है। NovaCommerce एक काल्पनिक नाम है; architecture, संख्याएँ और गलतियाँ वास्तविक हैं।
ग़लत तरह का बड़ा
पुराना database एक Managed MySQL "Advanced" node था 8 vCPU और 32 GB RAM के साथ। यह बड़ा था जैसे एक single enormous bridge बड़ा है। Impressive, और एक पार का एक मात्र तरीका।
Project के शुरुआत में हमने लिख दिया कि High Availability हमारे लिए क्या मतलब होना चाहिए। एक line कहता है: database एक node failure से survive करता है। एक node किसी भी size पर उस line को meet नहीं कर सकता। Size और availability अलग सवाल हैं, और पुराना setup ने सिर्फ़ पहले को answer दिया था।
Data लगभग 11 GB था। तो हमने Managed MySQL "Standard" 8.4 में move किए 4 vCPU और 16 GB per node के साथ, और दो node: एक primary, और एक standby जो take over करता है अगर primary fail करे। नए cluster में buffer pool 7 GB है, max connection 1,601 है और storage autoscale on है। Status: Implemented, 5 अक्टूबर।
पहले
अब
Plan
Advanced, single node
Standard 8.4, दो node (primary + standby)
Size
8 vCPU / 32 GB
4 vCPU / 16 GB per node
अगर एक node fail करे
Database down है
Standby take over करता है
Monthly cost
ज़्यादा
कम; primary + standby लगभग $389 list price पर है
सस्ता और highly available एक ही समय पर rare है, तो हमने इसे ले लिया। यहाँ पूरा data layer है जैसा यह आज दिखता है। Article का बाकी यह walk through करता है।
Write primary को जाते हैं, read primary और standby के बीच share होते हैं, और AI, cache और file हर एक अपने service पर रहते हैं।
26-minute cutover, step by step
Window planned था और business के साथ agree किया। यह order है जो हमने 5 अक्टूबर को follow किया।
22:33। दोनों load balancer एक empty tag पर point किए। विद्यार्थी maintenance page देखते थे।
Queue drain होते थे और scheduler stop करता था, तो कोई job copy के बीच write नहीं कर सकता था।
पुराने database पर 30 second के लिए zero write। वह gate था: copy सिर्फ़ शुरू होता था पुरानी side quiet हो जाने के बाद।
4 parallel stream में mysqldump के साथ copy। 10 मिनट लगे।
Count check: 341 table और 18,727,244 row old और new के बीच exactly match करते थे।
Application नए database के लिए test किया गया। फिर एक नया golden snapshot, दोनों pool का rollout और एक fresh worker।
22:59। Load balancer back, हमने शुरू करने के 26 मिनट बाद।
Window के अंदर सात planned step, और window के 25 मिनट बाद एक surprise।
कैसे हमने prove किया copy सही था
Row count matching एक weak test है, क्योंकि दो table same number row hold कर सकते हैं different content के साथ। तो हमने count से ज़्यादा check किया, और हमने site back होने के बाद check करते रहे।
Check
Result
Table और row, old vs new
341 table, 18,727,244 row, exact match
हर table का content
सब table पर full-content checksum, window के बाद
सबसे बड़ा result table
Column by column compare: 2.76 मिलियन row identical
App user का rights
CREATE TABLE refused, जैसा होना चाहिए
पहले 4 मिनट live
लगभग 2,900 request, 0 5xx, 110 notification job, 0 failed
क्लाइंट जिसे हम भूल गए
23:24 पर, site वापस आने के 25 मिनट बाद, हमें कुछ मिला जो हमने plan नहीं किया था। एक legacy admin panel, एक अलग पुराने server पर चल रहा, अब भी पुराने database को write कर रहा था।
हमने हर चीज़ switch off किया जो हमें याद था: web node, worker, scheduler। हमने कुछ switch off नहीं किया जिसे हम भूल गए कि exist करता है। House move करने की सोचो। Post office आपका mail forward करता है, लेकिन एक दोस्त जिसके पास अब भी पुरानी address है वहाँ letter post करते रहते हैं।
तब तक 48 row पुराने database में land हो गए थे। हमने उन्हें नए में merge किया एक conditional upsert के साथ: row insert करो, या सिर्फ़ update करो जब पुरानी copy newer हो। 44 row merge किए गए। 4 row के लिए नया database पहले से ही एक newer version hold कर रहा था, और हमने उन्हें keep किए। फिर हमने legacy panel को नए database पर point किया और पुराने cluster का app password change किया, तो कुछ भी इसे फिर से write नहीं कर सके। Status: Implemented।
Password change matter करता है जितना merge करता है। एक forgotten client फिर loudly fail करता है silently एक database fill करने की जगह जिसे कोई read नहीं करता। पुराना cluster 48 घंटे freeze रहा हमारे rollback तरीके के रूप में, और उसके बाद delete किया गया।
Lesson एक checklist item है, story नहीं:
Cutover से पहले, database के हर external client को list करो। पुराने cluster के trusted source read करो, और हर entry के लिए पूछो: यह किसका है?
हर client को application के साथ same window में switch करो।
Switch के बाद, पुरानी password change करो, तो एक client जिसे तुम miss किया fail करे loudly।
पुराने cluster को एक while के लिए frozen रखो rollback के रूप में।
Standby से Read: read/write split
क्यों read replica नहीं
"Read heavy हैं" का textbook जवाब एक read-only replica node है। हमने इसे consider किया। इस platform पर एक read-only node कम से कम primary जितना बड़ा होना चाहिए, तो यह लगभग same cost करता। इसी बीच हमने पहले से ही standby के लिए pay किया है जो idle बैठा है failure का wait करते।
तो हमने implement किया कुछ सस्ता: standby भी एक reader है, replica hostname के through reach किया जाता है। Read primary और standby के बीच share होते हैं, और हर एक दूसरे को cover करता है। Same hardware, bill पर कोई नई line नहीं।
Option
Extra cost
Status
Note
हर चीज़ primary पर
कोई नहीं
Baseline
सबसे simple, लेकिन primary हर read करता है
Dedicated read-only replica node
लगभग primary जितना फिर
Considered
कम से कम primary जितना बड़ा होना चाहिए
Standby primary के साथ read share करता है
कोई नहीं
Implemented, 6 अक्टूबर
Read एक थोड़ा lag कर सकते हैं; sticky read उसे cover करते हैं
Rule, सादी शब्दों में
हमने 6 अक्टूबर को 00:13 पर split on करा। Sketch form में, Laravel database configuration कहता है:
Read standby को जाते हैं, और primary दूसरे read host के रूप में listed है। अगर एक answer नहीं दे, दूसरा देता है।
Sticky। एक write के बाद, same request primary से read करते रहते हैं, तो एक विद्यार्थी अपना answer देखता है। एक छोटा middleware अगले request को भी primary पर रखता है। एक whiteboard पर नोट लिखने जैसे सोचो: आप वापस whiteboard पर जाते read करने के लिए, photocopy पर नहीं जो अभी print हो रहा है।
2-second connect timeout। एक slow standby quickly primary में fall back जाता है, और कोई विद्यार्थी इस पर wait नहीं करता।
Worker कभी standby इस्तेमाल नहीं करता। यह primary पर रहता है, क्योंकि job को fresh data की ज़रूरत है।
क्या यह सच में split होता है?
हमने एक live node पर check किया config को trust करने की जगह। हमने 8 fresh connection खोले, दो बार। पहली run दोनों server के बीच 6 और 2 split किया, दूसरी 4 और 4 split किया। Primary report किया read_only=0 और standby read_only=1, तो दोनों use किए जा रहे थे, और हमने जानते थे कौन सा कौन था।
एक gotcha
Production एक authoritative class map से class load करता है। एक brand-new class, जैसे एक नया middleware, जो map में नहीं है application के लिए exist नहीं करता, और request एक 500 return करता है। Deploy के हिस्से के रूप में class map को add करो।
Fixed size, narrow right, access by tag
Weekly resizing: considered। एक idea exam day से पहले database को up resize करना और बाद में down था। Business ने एक fixed 4 vCPU / 16 GB चुना। नीचे exam evening के नंबर वह choice back करते हैं।
Least privilege: implemented। App के database user को SELECT, INSERT, UPDATE और DELETE है, DDL नहीं। यह row change कर सकता है, जो इसे चाहिए, लेकिन यह table create, alter या drop नहीं कर सकता। हमने cutover के दौरान test किया: CREATE TABLE refused।
Access by tag: implemented। हर database backend tag, worker और office और jump address को admit करता है। एक नया autoscale backend node manual step के साथ connect करता है।
वह एक piece arithmetic छोड़ता है। हर backend node 80 php-fpm worker चलाता है, और हर एक एक MySQL connection hold कर सकता है। Total 1,601 allowed के अंतर्गत रहना चाहिए।
Backend node
php-fpm worker per node
Worst case connection
MySQL limit
2
80
160
1,601
4
80
320
1,601
6
80
480
1,601
10 (pool maximum)
80
800
1,601
Pool maximum पर भी हम लगभग आधी limit use करते हैं, जो worker और admin session के लिए room छोड़ता है।
Data layer में Valkey
एक managed Valkey, एक standby के साथ, cache, session और queue hold करता है, और यह noeviction policy के साथ run करता है। Part 4 गहरा जाता है। यहाँ सिर्फ़ वह जो data layer को belong करता है।
6 अक्टूबर को 02:28 पर, एक backend load test 2,854 application 500 दे गया। हर एक "Operation timed out" था जबकि Valkey से connect करता, 5-second connect timeout के साथ। हर request को एक fresh TLS connection खोलता, जो लगभग 5.4 ms CPU per request Valkey के लिए, MySQL के लिए लगभग 3.3 ms के विपरीत। 4 GB पर, Valkey connection को fast enough accept नहीं कर सकता।
Implemented: हमने इसे in place resize किया 8 GB को दो node के साथ। Data kept, host unchanged। Retest 25 से 500 request/s तक एक gradual climb के साथ run किया, 2 backend node पर शुरू करते हुए pool scale out: 0 application error, 0 backend 5xx। Exam evening पर यह अपनी memory का लगभग 12.5% use करता है। यह ज़्यादा पैसे cost करता है, और यह एक real failure remove किया।
क्यों AI को अपना PostgreSQL मिला
NovaCommerce के पास AI study feature हैं: adaptive learning, weakness analysis और predicted question। वे embedding पर काम करते हैं, जो संख्याओं की लंबी list हैं जो एक question के meaning को describe करते हैं। सबसे similar को find करना को vector similarity search कहते हैं।
हम उन embedding को एक अलग Managed PostgreSQL 17 में pgvector extension के साथ keep करते हैं, 2 vCPU और 4 GB पर। MySQL में नहीं। तीन reason:
एक अलग query shape। Vector search का मतलब big vector, approximate-nearest-neighbour index और CPU-heavy query। Exam traffic short read और write है।
Exam write के साथ कोई competition नहीं। एक heavy similarity query को कभी नहीं एक विद्यार्थी से CPU लेना चाहिए answer save करते समय। दो server मतलब दो budget।
सही tool। MySQL के पास इस काम के लिए कोई first-class vector index नहीं है।
दो workload अलग shape के साथ और अलग failure cost, तो दो database अलग size के साथ।
Downsize जिसे हमने revert किया
PostgreSQL node छोटा है, और हमने इसे और छोटा बनाना try किया: 1 vCPU और 2 GB। Tested, फिर reverted। हमने इसे 2 vCPU और 4 GB में वापस रखा exam के दौरान आने वाले write की वजह से। एक छोटी saving उस घंटे के लिए एक node जो struggle करे का worth नहीं है जो matter करता है। Quiet day के लिए नहीं, exam hour के लिए size करो।
Generic version के लिए इस advice का, जैसे एक index type चुनना और filter vector search को accurate रखना, हमारा field guide देखें The Data Tier Under Load। यह case study सिर्फ़ उससे सटिक रहता है जो हमने किया।
Backup और recovery
एक standby एक dead machine से protect करता है। यह एक mistake से protect नहीं करता: अगर कोई एक table delete करता है, standby एक moment बाद delete करता है। यही backup के लिए है। हमने दोनों use करते हैं, और वे अलग सवाल answer करते हैं।
क्या wrong होता है
क्या हमें protect करता है
Status
एक MySQL node fail करता है
Standby take over करता है (provider failover)
Implemented
Data खो जाता है या damaged होता है
Managed database के daily backup (provider)
Implemented
Cutover wrong जाता है
पुराना cluster 48 घंटे frozen रखा, फिर deleted
Implemented
एक web node lost होता है या rollout fail करता है
Web node के golden snapshot; last 3 अच्छे image रखे हैं
Implemented
Worker lost होता है
Worker का एक snapshot; यह अभी भी एक single point of failure है
Snapshot implemented, standby worker planned
Database bottleneck नहीं था
हमने database move नहीं किया क्योंकि यह slow था। हमने इसे availability और cost के लिए move किया। Measurement ने decide किया क्या *नहीं* करना: 7 अक्टूबर की real exam evening पर, दो exam back to back के साथ, database system का quiet part था।
Measure, 7 अक्टूबर exam evening
Value
MySQL CPU, max / average
24.5% / 13.4%
MySQL running query, max
5
MySQL lock wait
0
Valkey memory
12.5%
Backend peak
लगभग 69 requests/s
Backend node CPU, 2-minute average, max
47% (एक node एक मिनट के लिए 86% को touched)
Bottleneck backend CPU per node था, और database के पास लगभग 6x headroom था। हमारा capacity model कहता है MySQL limit near 450 requests/s के पास आते है, against 69 पीक जो शाम को था। वह model एक multiple-choice exam से आता है। Written exam PDF upload के साथ worker को बहुत ज़्यादा load करते हैं और must be measured separately।
Money term में, तीन data store लगभग $689 एक महीने में cost करते हैं list price पर: MySQL लगभग $389, Valkey लगभग $240 और PostgreSQL $60। यह लगभग 70% है roughly $990 monthly bill का backend pool 2 node के साथ, यही वजह है कि हम एक store grow करते हैं सिर्फ़ measure करने के बाद।
हमने क्या सीखा
एक big node high availability नहीं है। Availability को एक दूसरे node की जरूरत है जो take over कर सके, और एक छोटी pair एक giant machine पर price में beat कर सकते हैं।
एक cutover को gate की जरूरत है copy से पहले (zero write), दौरान count, और बाद में checksum।
Cutover से पहले पुराने database के हर client को list करो, फिर बाद में पुरानी password change करो तो एक missed client loudly fail करे।
Standby को एक reader के रूप में use करो replica buy करने से पहले। Sticky read add करो और short connect timeout, और job को primary पर रखो।
App user को right दो जिसकी उसे जरूरत है कुछ नहीं। कोई DDL नहीं।
AI को अपना PostgreSQL दो, और exam hour के लिए size करो।
Spend करने से पहले measure करो। Database के पास 6x headroom था; काम backend में था।
Database अब calm हैं, तो अगले सवाल हर चीज़ के बारे में हैं जो background में चलता है: single worker, queue, log और monitoring। Part 4 उन्हें cover करता है: Valkey, worker, logging और reliability।