Publish दोनदा दाबल्यास आता एकदाच publish होते
एकाच अॅपसाठी तीन publish requests आमच्या API ला 84 मिलिसेकंद अंतराने पोहोचल्या, आणि प्रत्येकाने त्या अॅपसाठी पूर्ण deploy सुरू केला. त्या एकमेकांशी एकाच hosting वर शर्यत लावत होत्या, परिणामी अनेक releases अयशस्वी झाल्या कारण दुसरी आधी पोहोचली होती, आणि दिसणारी एकमेव खूण "release failed" ईमेल होता — अॅप स्वतः शेवटी ठीक होते. आता एकाच अॅपसाठी publish चालू असताना आलेला दुसरा publish नवीन build सुरू करत नाही. Database ठरवते कोणती request जिंकते, आणि इतरांना सत्य सांगितले जाते: एक publish आधीच चालू आहे.
काय घडले
Mythex वर अॅप publish करताना API त्या अॅपसाठी एक deployment नोंदवते आणि आमच्या orchestrator — build चालवणारी सेवा — ला build करून release करायला सांगते. Publish Publish बटणावरून, दुसऱ्या browser tab मधून, किंवा तुम्ही मागितल्यावर एजंटकडून सुरू होऊ शकते. डबल क्लिक, दोन tabs, किंवा दोनदा आलेली एजंट request — या सर्वांमुळे एकाच अॅपसाठी जवळजवळ एकाच वेळी एकापेक्षा जास्त publish होऊ शकतात.
या प्रकरणात तीन 84 ms मध्ये आले. प्रत्येकाने पूर्ण deploy चालवला. Hosting platform वर एकाच अॅपसाठी releases एकाच सेकंदात तयार झाल्या, आणि तीनपैकी तीन इतरांनी आधीच प्रगती सुरू केलेली असल्यामुळे अपयशी ठरल्या. एक नंतरची release अडकली, आणि नंतर अॅप काम करत होते.
हेच ते होते ज्यामुळे हे चुकणे सोपे होते. वापरकर्त्याला एक काम करणारे अॅप दिसले. अपयश फक्त release-failure ईमेलमधून दिसले.
काहीही का थांबवले नाही
Publish कोडमध्ये प्रोजेक्टसाठी प्रगतीत असलेल्या deploys कडे पाहणारी एक पायरी होती. पण ती करत होती, सुरू होण्यापूर्वी, प्रगतीतील प्रत्येक deploy अयशस्वी म्हणून चिन्हांकित करणे — "नवीन publish ने बदलले". ते मागे राहिलेले build, क्रॅश झालेल्या प्रक्रियेने सोडलेले, साफ करण्यासाठी होते.
व्यवहारात, मध्यात येणारा दुसरा publish पहिल्याला मृत घोषित करून स्वतःचा सुरू करत असे. दोन्ही एकाच अॅपवर पूर्ण होत. तिथे lock नव्हता; अशी पायरी होती जी सक्रियपणे शर्यतीसाठी मार्ग मोकळा करत होती.
अॅपच्या पहिल्या publish साठी आणखी एक, विचित्र लक्षण होते. पुन्हा वापरण्यासाठी deployment नोंद नसल्यामुळे, दोन समांतर पहिले publish दोन्ही एक insert करण्याचा प्रयत्न करत. Database ला अॅपच्या link नावावर (त्याचा slug, जसे your-app.mythex.ai मधील your-app) unique index आहे, त्यामुळे दुसरा insert अयशस्वी झाला. API ने त्याचे भाषांतर "हे link नाव आधीच घेतले आहे" असे केले — आणि त्या क्षणी ते अॅप त्याच नावाखाली यशस्वीपणे publish होत असताना त्याचे नाव बदलण्याचा सल्ला दिला.
उपाय: दावा हाच lock
Deployment नोंदीवर दावा आता एकच शर्तीवर आधारित database update आहे: ही पंक्ती घ्या आणि building म्हणून चिन्हांकित करा फक्त जर कोणी ती publish करत नसेल तर. ते एकच statement असल्यामुळे database त्याच मिलिसेकंदात येणाऱ्या requests निकाली काढते. नेमकी एक पंक्ती बदलते. इतर पाहतात की शून्य पंक्ती बदलल्या, थांबतात, आणि orchestrator पर्यंत कधीच पोहोचत नाहीत.
Lock ला त्याचा धारक मेला तर बाहेर पडण्याचा मार्गही हवा. Publish मध्येच API restart झाला तर पंक्ती कायमची "building" राहील आणि अॅप publish करता येणार नाही. म्हणून ज्या पंक्तीच्या मालकाने heartbeats पाठवणे थांबवले आहे — चालणारा publish दर 30 सेकंदांनी पंक्ती अपडेट करतो, आणि पाच मिनिटांची शांतता म्हणजे मृत — ती पुन्हा ताब्यात घेता येते. ती मर्यादा heartbeat वरून येते, build किती वेळ घेतो याच्या अंदाजावरून नव्हे. वापरकर्त्याचा स्वतःचा Dockerfile बांधणे कायदेशीररीत्या बराच वेळ घेऊ शकते, आणि जो धीमा build अजूनही heartbeat पाठवतो त्याचा दावा कायम राहतो.
प्रगतीतील प्रत्येक deploy अयशस्वी करणारी पायरी आता publish पूर्वी चालत नाही. फक्त मृत प्रक्रियेने मागे सोडलेल्या पंक्ती साफ केल्या जातात.
हरलेल्याला सत्य सांगणे
जी request हरते तिला आता स्पष्ट उत्तर मिळते: publish_in_progress, सोबत चालू असलेल्या deployment चा ID. publish सुरू करू शकणारी प्रत्येक जागा ते हाताळते:
- Workspace आधीपासून चालू असलेल्या build चे अनुसरण करते, त्यावर error दाखवण्याऐवजी.
- एजंटला सांगितले जाते की या अॅपचा publish आधीपासून चालू आहे आणि त्याची वाट पाहावी, अपयश आले आहे असे नव्हे — पूर्वी असे सांगितल्याने तो Dockerfile "दुरुस्त" करायला आणि पुन्हा प्रयत्न करायला जाऊ शकत होता.
- पहिल्या publish ची link नावाबाबत टक्कर तपासली जाते: जर नाव धरून ठेवलेली पंक्ती याच प्रोजेक्टचा चालू publish असेल, तर उत्तर "आधीच publish होत आहे" आहे, "हे नाव घेतले आहे" नव्हे. दुसऱ्या प्रोजेक्टसोबत खरी टक्कर झाली तर अजूनही नाव बदलण्याचा संदेश मिळतो.
आम्ही ते कसे तपासले
एक पुनरुत्पादन script लिहिली जी एकाच अॅपचे तीन publishes एकाच वेळी खऱ्या database आणि stub orchestrator विरुद्ध चालवते, आणि orchestrator ला किती deploys मागितले ते मोजते. त्यात पहिले publish आणि live अॅपचे पुन्हा publish दोन्ही येतात. दुरुस्तीआधी ती दोन deploys मोजली. नंतर एक.
Script सुटकेचा मार्गही कव्हर करते: ती अॅपची पंक्ती दहा मिनिटे heartbeat नसताना building म्हणून चिन्हांकित करते, पुन्हा publish करते, आणि नवीन publish पंक्तीवर दावा करून चालतो हे तपासते.
Unit tests lock ने करायच्या दोन गोष्टी धरतात: तीन एकाच वेळी पुन्हा publishes पैकी नेमका एक पुढे जातो आणि इतर दोघांना publish चालू आहे असे सांगितले जाते; आणि ज्याचा मालक मेला त्या build चा दावा करता येतो. दुसरी चाचणी तपासते की नवा प्रयत्न मागील प्रयत्नाचा error संदेश सोबत नेत नाही.
आम्ही काय शिकलो
- Cleanup code हा छुपा race असू शकतो. "प्रगतीतील सर्व अयशस्वी करा, मग सुरू करा" हे स्वच्छतेसारखे वाटते. दोन callers असताना, ते एकमेकांना रद्द करणे आहे.
- Database ला निर्णय घेऊ द्या. आधी वाचा मग लिहा यात दुसऱ्या request साठी फट राहते. एकच शर्तीवर update ती फट ठेवत नाही.
- प्रत्येक lock ला liveness शी जोडलेली समाप्ती हवी. तिच्याशिवाय crash अडकलेल्या अॅपमध्ये बदलतो.
- Error संदेश म्हणजे सूचना. "ते नाव घेतले आहे" ने लोकांना चुकीचे करण्यास सांगितले. योग्य संदेश सोपा होता आणि खरा होता.
Publish बद्दल docs मध्ये अधिक: how to publish. आणि संबंधित दुरुस्ती: republish करताना तुमचे published अॅप चालू राहते.