ব্লগ

Publish দুবার চাপলে এখন একবারই প্রকাশ হয়

একই অ্যাপের তিনটি publish অনুরোধ ৮৪ মিলিসেকেন্ড ব্যবধানে আমাদের API-তে পৌঁছায়, এবং প্রতিটি সেই অ্যাপের একটি পূর্ণ deploy শুরু করে। তারা একে অপরের সঙ্গে একই hosting-এ প্রতিযোগিতা করে, এবং ফলস্বরূপ কয়েকটি release ব্যর্থ হয়, কারণ অন্য একটি আগে পৌঁছে গিয়েছিল। একমাত্র দৃশ্যমান লক্ষণ ছিল একটি "release failed" ইমেইল, অ্যাপটি শেষ পর্যন্ত ঠিকই ছিল। এখন একটি publish যদি একই অ্যাপের জন্য আরেকটি চলার সময় আসে, তা দ্বিতীয় build শুরু করে না। ডেটাবেস ঠিক করে কোন অনুরোধ জিতবে, এবং বাকিদের সত্যিটা জানানো হয়: একটি publish ইতিমধ্যে চলছে।

কী ঘটেছিল

আপনি Mythex-এ একটি অ্যাপ publish করলে API সেটির জন্য একটি deployment রেকর্ড করে এবং আমাদের orchestrator-কে অনুরোধ করে, যে সার্ভিসটি build চালায়, সেটি build ও release করতে। Publish শুরু হতে পারে Publish বোতাম থেকে, অন্য ব্রাউজার ট্যাব থেকে, বা আপনি চাইলে এজেন্ট থেকে। একটি ডাবল ক্লিক, দুটি ট্যাব, বা দুবার আসা এজেন্টের অনুরোধ, প্রায় একই মুহূর্তে একই অ্যাপের একাধিক publish তৈরি করতে পারে।

এই ক্ষেত্রে তিনটি ৮৪ ms-এর মধ্যে আসে। প্রতিটি একটি পূর্ণ deploy চালায়। Hosting platform-এ একই অ্যাপের releases একই সেকেন্ডে তৈরি হয়, এবং তিনটির মধ্যে তিনটি ব্যর্থ হয় কারণ অন্যরা আগেই চলছিল। একটি পরবর্তী release আটকে যায়, এবং পরে অ্যাপটি কাজ করে।

সেটাই এটিকে সহজে নজর এড়ানো করে তোলে। ব্যবহারকারী একটি কাজ করা অ্যাপ দেখে। ব্যর্থতাগুলো শুধু release-ব্যর্থতার ইমেইলে দেখা যায়।

কেন কিছুই থামায়নি

Publish কোডে চলমান deploy-গুলোর দিকে তাকানোর একটি ধাপ ছিল। কিন্তু সেটি শুরুর আগে প্রতিটি চলমান deploy-কে ব্যর্থ হিসেবে চিহ্নিত করত — "একটি নতুন publish দ্বারা প্রতিস্থাপিত"। উদ্দেশ্য ছিল একটি ক্র্যাশ করা প্রক্রিয়ার রেখে যাওয়া build পরিষ্কার করা।

বাস্তবে এর মানে ছিল, build চলাকালীন দ্বিতীয় publish এলে সে প্রথমটিকে মৃত ঘোষণা করে নিজের build শুরু করত। দুটিই একই অ্যাপের বিরুদ্ধে শেষ পর্যন্ত চলত। কোনো lock ছিল না; ছিল একটি ধাপ যা সক্রিয়ভাবে রেসের পথ পরিষ্কার করত।

প্রথম-publish-এর জন্য দ্বিতীয়, আরও অদ্ভুত একটি লক্ষণ ছিল। কোনো পুনঃব্যবহারযোগ্য deployment রেকর্ড না থাকায়, একসঙ্গে দুটি প্রথম publish একটি সারি ঢোকানোর চেষ্টা করত। ডেটাবেসে অ্যাপের link name-এর (তার slug, যেমন your-app.mythex.ai-এর your-app) উপর একটি unique index আছে, তাই দ্বিতীয় insert ব্যর্থ হতো। API সেটিকে "সেই link name নেওয়া হয়ে গেছে" বলে অনুবাদ করত, এবং ব্যবহারকারীকে একটি অ্যাপের নাম বদলাতে বলত, যদিও সেই মুহূর্তে অ্যাপটি ঠিক সেই নামেই সফলভাবে publish হচ্ছিল।

সমাধান: দাবিটাই লক

Deployment রেকর্ড দাবি করা এখন একটি একক শর্তযুক্ত database update: এই সারিটি নাও এবং building হিসেবে চিহ্নিত করো শুধু যদি কেউ এটি publish না করে। এটি একটি একক statement হওয়ায়, ডেটাবেস একই মিলিসেকেন্ডে আসা অনুরোধগুলো নিষ্পত্তি করে। ঠিক একটি অনুরোধ সারিটি বদলায়। বাকিরা দেখে যে শূন্য সারি বদলেছে, থামে, এবং কখনও orchestrator-এ পৌঁছায় না।

লকের মালিক মারা গেলে বের হওয়ার উপায়ও দরকার। API যদি একটি publish-এর মাঝখানে পুনরায় চালু হয়, সারিটি চিরকাল "building" হিসেবে থেকে যেত এবং অ্যাপটি আর publish করা যেত না। তাই যে সারির মালিক heartbeat পাঠানো বন্ধ করেছে, তা আবার দাবি করা যায়। চলমান publish প্রতি ৩০ সেকেন্ডে সারিটি আপডেট করে, এবং পাঁচ মিনিট নীরবতা মানে মৃত। এই সীমাটি heartbeat থেকে আসে, build কতক্ষণ নেবে সে সম্পর্কে অনুমান থেকে নয়। ব্যবহারকারীর নিজস্ব Dockerfile build করতে বৈধভাবেই দীর্ঘ সময় লাগতে পারে, এবং যে ধীর build এখনও heartbeat পাঠাচ্ছে, সে তার দাবি ধরে রাখে।

যে ধাপটি publish-এর আগে প্রতিটি চলমান deploy ব্যর্থ করত, সেটি আর চলে না। শুধু সেই সারিগুলো পরিষ্কার হয় যেগুলো একটি মৃত প্রক্রিয়া রেখে গেছে।

হেরে যাওয়া অনুরোধকে সত্যি বলা

যে অনুরোধ হারে, সে এখন একটি স্পষ্ট উত্তর পায়: publish_in_progress, সঙ্গে চলমান deployment-এর ID। যে প্রতিটি জায়গা publish শুরু করতে পারে, সেটি একে হ্যান্ডল করে:

  • Workspace ইতিমধ্যে চলমান build অনুসরণ করে, তার উপর একটি error না দেখিয়ে।
  • এজেন্টকে বলা হয় যে এই অ্যাপের একটি publish ইতিমধ্যে চলছে এবং তার জন্য অপেক্ষা করতে, বরং বলা হয় না যে কিছু ব্যর্থ হয়েছে — যা আগে তাকে একটি Dockerfile "ঠিক" করতে এবং আবার চেষ্টা করতে পাঠাতে পারত।
  • প্রথম-publish-এর link name সংঘর্ষ পরীক্ষা করা হয়: যদি নামটি ধরে রাখা সারিটি এই প্রজেক্টের নিজের চলমান publish হয়, উত্তর হলো "ইতিমধ্যে publish হচ্ছে", "সেই নাম নেওয়া" নয়। অন্য প্রজেক্টের সঙ্গে সত্যিকারের সংঘর্ষ এখনও নাম বদলানোর বার্তা পায়।

কীভাবে পরীক্ষা করেছি

আমরা একটি পুনরুৎপাদন script লিখেছি, যা একটি বাস্তব ডেটাবেস ও একটি stub orchestrator-এর বিরুদ্ধে একই অ্যাপের তিনটি publish একই সময়ে চালায় এবং কতগুলো deploy orchestrator-কে চাওয়া হয় তা গোনে। এটি প্রথম publish এবং একটি লাইভ অ্যাপের republish দুটিই ঢেকে রাখে। ফিক্সের আগে এটি দুটি deploy গুনেছিল। পরে, একটি।

Script আরেকটি পথও ঢেকে রাখে: এটি অ্যাপের সারিটিকে দশ মিনিট কোনো heartbeat ছাড়া building হিসেবে চিহ্নিত করে, আবার publish করে, এবং যাচাই করে যে নতুন publish সারিটি দাবি করে এবং চলে।

Unit test লকের দুটি কাজ ধরে রাখে: একই সঙ্গে তিনটি republish-এর মধ্যে ঠিক একটি পার হয়, এবং বাকি দুটিকে জানানো হয় যে একটি publish চলছে; এবং যে build-এর মালিক মারা গেছে তা দাবি করা যায়। আরেকটি পরীক্ষা নিশ্চিত করে যে নতুন চেষ্টা আগের চেষ্টার error বার্তা বহন করে না।

আমরা যা শিখেছি

  • Cleanup কোড ছদ্মবেশী race হতে পারে। "চলমান সবকিছু ব্যর্থ করো, তারপর শুরু করো" পরিচ্ছন্নতার মতো শোনায়। দুটি caller থাকলে, এটি প্রতিটি অন্যটিকে বাতিল করা।
  • ডেটাবেসকে সিদ্ধান্ত নিতে দিন। আগে পড়া, তারপর লেখা, অন্য অনুরোধের জন্য একটি ফাঁক রেখে যায়। একটি একক শর্তযুক্ত update সেটি রাখে না।
  • প্রতিটি লকের liveness-এর সঙ্গে যুক্ত একটি মেয়াদ দরকার। মেয়াদ না থাকলে একটি ক্র্যাশ একটি আটকে থাকা অ্যাপে পরিণত হয়।
  • Error বার্তা হলো নির্দেশনা। "সেই নাম নেওয়া" মানুষকে ভুল কাজ করতে বলত। সঠিক বার্তাটি সহজ ছিল এবং সত্যি।

Publishing নিয়ে আরও ডকসে আছে: how to publish। এবং একটি সংশ্লিষ্ট ফিক্স: আপনার প্রকাশিত অ্যাপ republish-এর সময়ও চালু থাকে।

Mythex · Pricing · AI দিয়ে যা খুশি বানান · ওয়েবসাইট ও অ্যাপের টেমপ্লেট · Mythex-এর তুলনা · AI অ্যাপ তৈরির শব্দকোষ · AI অ্যাপ তৈরির বিনামূল্যের টুল · English

English አማርኛ العربية Български Bosanski Català Čeština Dansk Deutsch Ελληνικά Español Español (España) Eesti فارسی Suomi Français Français (Canada) ગુજરાતી हिन्दी Hrvatski Magyar Հայերեն Bahasa Indonesia Íslenska Italiano 日本語 ქართული Қазақ тілі ಕನ್ನಡ 한국어 Lietuvių Latviešu Македонски മലയാളം Монгол मराठी Bahasa Melayu မြန်မာ Norsk bokmål Nederlands ਪੰਜਾਬੀ Polski Português (Brasil) Português (Portugal) Română Русский Slovenčina Slovenščina Soomaali Shqip Srpski Svenska Kiswahili தமிழ் తెలుగు ไทย Tagalog Türkçe Українська اردو Tiếng Việt 简体中文 繁體中文(香港) 繁體中文(台灣)