লেখার নিয়মের চ্যালেঞ্জ

এই পৃষ্ঠাটি দক্ষ ব্যাজেল নিয়ম লেখার নির্দিষ্ট সমস্যা এবং চ্যালেঞ্জগুলির একটি উচ্চ-স্তরের ওভারভিউ দেয়।

সারাংশ প্রয়োজনীয়তা

  • অনুমান: সঠিকতা, থ্রুপুট, ব্যবহারের সহজতা এবং বিলম্বের লক্ষ্য
  • অনুমান: বড় আকারের ভান্ডার
  • অনুমান: বিল্ড-মত বর্ণনা ভাষা
  • ঐতিহাসিক: লোডিং, অ্যানালাইসিস এবং এক্সিকিউশনের মধ্যে হার্ড সেপারেশন পুরানো, কিন্তু এখনও API প্রভাবিত করে
  • অন্তর্নিহিত: রিমোট এক্সিকিউশন এবং ক্যাশিং কঠিন
  • অন্তর্নিহিত: সঠিক এবং দ্রুত বর্ধনশীল বিল্ডের জন্য পরিবর্তন তথ্য ব্যবহার করার জন্য অস্বাভাবিক কোডিং প্যাটার্নের প্রয়োজন
  • অন্তর্নিহিত: চতুর্মুখী সময় এবং মেমরি খরচ এড়ানো কঠিন

অনুমান

বিল্ড সিস্টেম সম্পর্কে এখানে কিছু অনুমান করা হয়েছে, যেমন সঠিকতার প্রয়োজন, ব্যবহারের সহজতা, থ্রুপুট এবং বড় আকারের সংগ্রহস্থল। নিম্নলিখিত বিভাগগুলি এই অনুমানগুলিকে সম্বোধন করে এবং নিয়মগুলি কার্যকরভাবে লেখা হয়েছে তা নিশ্চিত করার জন্য নির্দেশিকা অফার করে৷

সঠিকতা, থ্রুপুট, ব্যবহারের সহজতা এবং লেটেন্সি লক্ষ্য করুন

আমরা অনুমান করি যে বিল্ড সিস্টেমটি ক্রমবর্ধমান বিল্ডের ক্ষেত্রে প্রথম এবং সর্বাগ্রে সঠিক হওয়া দরকার। একটি প্রদত্ত সোর্স ট্রির জন্য, একই বিল্ডের আউটপুট সবসময় একই হওয়া উচিত, আউটপুট ট্রি দেখতে কেমন হোক না কেন। প্রথম আনুমানিকতায়, এর মানে Bazel-এর প্রতিটি একক ইনপুট জানতে হবে যা একটি প্রদত্ত বিল্ড ধাপে যায়, যাতে কোনো ইনপুট পরিবর্তন হলে এটি সেই ধাপটি পুনরায় চালু করতে পারে। বেজেল কীভাবে সঠিক পেতে পারে তার সীমাবদ্ধতা রয়েছে, কারণ এটি কিছু তথ্য যেমন বিল্ডের তারিখ/সময় ফাঁস করে এবং নির্দিষ্ট ধরণের পরিবর্তন যেমন ফাইলের বৈশিষ্ট্যগুলিতে পরিবর্তন উপেক্ষা করে। স্যান্ডবক্সিং অঘোষিত ইনপুট ফাইলগুলিতে পড়া প্রতিরোধ করে সঠিকতা নিশ্চিত করতে সহায়তা করে। সিস্টেমের অভ্যন্তরীণ সীমার পাশাপাশি, কিছু পরিচিত শুদ্ধতার সমস্যা রয়েছে, যার বেশিরভাগই ফাইলসেট বা C++ নিয়মের সাথে সম্পর্কিত, যেগুলি উভয়ই কঠিন সমস্যা। এগুলো ঠিক করার জন্য আমাদের দীর্ঘমেয়াদি প্রচেষ্টা রয়েছে।

বিল্ড সিস্টেমের দ্বিতীয় লক্ষ্য হল উচ্চ থ্রুপুট; দূরবর্তী এক্সিকিউশন পরিষেবার জন্য বর্তমান মেশিন বরাদ্দের মধ্যে কী করা যেতে পারে তার সীমানা আমরা স্থায়ীভাবে ঠেলে দিচ্ছি। রিমোট এক্সিকিউশন সার্ভিস ওভারলোড হয়ে গেলে, কেউ কাজ করতে পারবে না।

ব্যবহারের সহজতা পরবর্তী আসে. রিমোট এক্সিকিউশন সার্ভিসের একই (বা অনুরূপ) পদচিহ্ন সহ একাধিক সঠিক পদ্ধতির মধ্যে, আমরা এমন একটি বেছে নিই যা ব্যবহার করা সহজ।

লেটেন্সি একটি বিল্ড শুরু করা থেকে শুরু করে প্রত্যাশিত ফলাফল পেতে যে সময় লাগে তা বোঝায়, সেটা পরীক্ষায় পাস করা বা ফেল করা পরীক্ষার লগ, বা একটি BUILD ফাইলে টাইপো আছে এমন একটি ত্রুটি বার্তা।

লক্ষ্য করুন যে এই লক্ষ্যগুলি প্রায়ই ওভারল্যাপ হয়; লেটেন্সি হল রিমোট এক্সিকিউশন সার্ভিসের থ্রুপুটের একটি ফাংশন যতটা সঠিক ব্যবহার সহজে প্রাসঙ্গিক।

বড় মাপের ভান্ডার

বিল্ড সিস্টেমটিকে বড় সংগ্রহস্থলের স্কেলে কাজ করতে হবে যেখানে বড় আকারের মানে হল এটি একটি একক হার্ড ড্রাইভে ফিট করে না, তাই কার্যত সমস্ত বিকাশকারী মেশিনে সম্পূর্ণ চেকআউট করা অসম্ভব। একটি মাঝারি আকারের বিল্ডের হাজার হাজার BUILD ফাইল পড়তে এবং পার্স করতে হবে এবং কয়েক হাজার গ্লোব মূল্যায়ন করতে হবে। যদিও তাত্ত্বিকভাবে একটি মেশিনে সমস্ত BUILD ফাইল পড়া সম্ভব, আমরা এখনও যুক্তিসঙ্গত সময় এবং মেমরির মধ্যে এটি করতে সক্ষম হইনি। যেমন, এটি গুরুত্বপূর্ণ যে BUILD ফাইলগুলি স্বাধীনভাবে লোড এবং পার্স করা যেতে পারে।

বিল্ড-এর মতো বর্ণনা ভাষা

এই প্রসঙ্গে, আমরা একটি কনফিগারেশন ভাষা ধরে নিই যেটি লাইব্রেরি এবং বাইনারি নিয়ম এবং তাদের আন্তঃনির্ভরতার ঘোষণায় মোটামুটিভাবে BUILD ফাইলের মতো। BUILD ফাইলগুলি স্বাধীনভাবে পড়া এবং পার্স করা যেতে পারে এবং আমরা যখনই পারি (অস্তিত্ব ব্যতীত) সোর্স ফাইলগুলি দেখাও এড়িয়ে যাই।

ঐতিহাসিক

Bazel সংস্করণগুলির মধ্যে পার্থক্য রয়েছে যা চ্যালেঞ্জ সৃষ্টি করে এবং এর মধ্যে কয়েকটি নিম্নলিখিত বিভাগে বর্ণিত হয়েছে।

লোডিং, বিশ্লেষণ এবং সম্পাদনের মধ্যে কঠিন বিচ্ছেদ পুরানো কিন্তু এখনও API-কে প্রভাবিত করে

টেকনিক্যালি, ক্রিয়াটি রিমোট এক্সিকিউশনে পাঠানোর ঠিক আগে একটি অ্যাকশনের ইনপুট এবং আউটপুট ফাইলগুলি জানা একটি নিয়মের জন্য যথেষ্ট। যাইহোক, মূল বেজেল কোড বেস লোডিং প্যাকেজগুলির একটি কঠোর বিভাজন ছিল, তারপর একটি কনফিগারেশন ব্যবহার করে নিয়ম বিশ্লেষণ করে (কমান্ড-লাইন পতাকাগুলি, মূলত), এবং শুধুমাত্র তারপরে যেকোন অ্যাকশন চালানো। এই পার্থক্যটি আজও নিয়ম API এর অংশ, যদিও Bazel-এর মূলের আর এটির প্রয়োজন নেই (নীচে আরও বিশদ বিবরণ)।

এর মানে হল যে নিয়ম API-এর জন্য নিয়ম ইন্টারফেসের একটি ঘোষণামূলক বিবরণ প্রয়োজন (এতে কী কী বৈশিষ্ট্য রয়েছে, বৈশিষ্ট্যগুলির প্রকারগুলি)। কিছু ব্যতিক্রম আছে যেখানে API আউটপুট ফাইলের অন্তর্নিহিত নাম এবং গুণাবলীর অন্তর্নিহিত মান গণনা করতে লোডিং পর্যায়ে কাস্টম কোড চালানোর অনুমতি দেয়। উদাহরণ স্বরূপ, 'foo' নামের একটি java_library নিয়ম অন্তর্নিহিতভাবে 'libfoo.jar' নামে একটি আউটপুট তৈরি করে, যা বিল্ড গ্রাফের অন্যান্য নিয়ম থেকে উল্লেখ করা যেতে পারে।

তদ্ব্যতীত, একটি নিয়মের বিশ্লেষণ কোনো উত্স ফাইল পড়তে বা একটি কর্মের আউটপুট পরিদর্শন করতে পারে না; পরিবর্তে, এটিকে বিল্ড স্টেপ এবং আউটপুট ফাইলের নামগুলির একটি আংশিক নির্দেশিত দ্বিপক্ষীয় গ্রাফ তৈরি করতে হবে যা শুধুমাত্র নিয়ম এবং এর নির্ভরতা থেকে নির্ধারিত হয়।

অন্তর্নিহিত

কিছু অন্তর্নিহিত বৈশিষ্ট্য রয়েছে যা লেখার নিয়মকে চ্যালেঞ্জিং করে তোলে এবং সবচেয়ে সাধারণ কিছু নিম্নলিখিত বিভাগে বর্ণিত হয়েছে।

রিমোট এক্সিকিউশন এবং ক্যাশে করা কঠিন

একটি একক মেশিনে বিল্ড চালানোর তুলনায় রিমোট এক্সিকিউশন এবং ক্যাশিং বড় সংগ্রহস্থলে বিল্ড টাইমকে মোটামুটিভাবে দুই অর্ডার দিয়ে উন্নত করে। যাইহোক, যে স্কেলটিতে এটি সম্পাদন করতে হবে তা বিস্ময়কর: Google এর রিমোট এক্সিকিউশন পরিষেবাটি প্রতি সেকেন্ডে বিপুল সংখ্যক অনুরোধ পরিচালনা করার জন্য ডিজাইন করা হয়েছে এবং প্রোটোকলটি সাবধানে অপ্রয়োজনীয় রাউন্ডট্রিপ এবং সেইসাথে পরিষেবার দিকে অপ্রয়োজনীয় কাজ এড়ায়।

এই সময়ে, প্রোটোকলের প্রয়োজন হয় যে বিল্ড সিস্টেম সময়ের আগে প্রদত্ত কর্মের সমস্ত ইনপুট জানে; বিল্ড সিস্টেম তারপর একটি অনন্য অ্যাকশন ফিঙ্গারপ্রিন্ট গণনা করে এবং একটি ক্যাশে হিটের জন্য শিডিউলকারীকে জিজ্ঞাসা করে। যদি ক্যাশে হিট পাওয়া যায়, শিডিউলকারী আউটপুট ফাইলগুলির হজমের সাথে উত্তর দেয়; ফাইল নিজেই পরে ডাইজেস্ট দ্বারা সম্বোধন করা হয়. যাইহোক, এটি Bazel নিয়মের উপর বিধিনিষেধ আরোপ করে, যা সময়ের আগে সমস্ত ইনপুট ফাইল ঘোষণা করতে হবে।

সঠিক এবং দ্রুত ক্রমবর্ধমান বিল্ডের জন্য পরিবর্তন তথ্য ব্যবহার করার জন্য অস্বাভাবিক কোডিং প্যাটার্ন প্রয়োজন

উপরে, আমরা যুক্তি দিয়েছিলাম যে সঠিক হওয়ার জন্য, Bazel-কে সেই বিল্ড স্টেপ এখনও আপ-টু-ডেট কিনা তা সনাক্ত করতে একটি বিল্ড স্টেপে যায় এমন সমস্ত ইনপুট ফাইল জানতে হবে। প্যাকেজ লোডিং এবং নিয়ম বিশ্লেষণের ক্ষেত্রেও এটি সত্য, এবং আমরা সাধারণভাবে এটি পরিচালনা করার জন্য স্কাইফ্রেম ডিজাইন করেছি। স্কাইফ্রেম হল একটি গ্রাফ লাইব্রেরি এবং মূল্যায়ন ফ্রেমওয়ার্ক যা একটি লক্ষ্য নোড নেয় (যেমন 'এই বিকল্পগুলির সাথে //foo তৈরি করুন'), এবং এটিকে এর উপাদান অংশগুলিতে বিভক্ত করে, যা পরে মূল্যায়ন করা হয় এবং এই ফলাফলের জন্য একত্রিত হয়। এই প্রক্রিয়ার অংশ হিসাবে, স্কাইফ্রেম প্যাকেজগুলি পড়ে, নিয়ম বিশ্লেষণ করে এবং ক্রিয়া সম্পাদন করে।

প্রতিটি নোডে, স্কাইফ্রেম সঠিকভাবে ট্র্যাক করে কোন প্রদত্ত নোডটি তার নিজস্ব আউটপুট গণনা করার জন্য ব্যবহৃত নোডগুলিকে, লক্ষ্য নোড থেকে ইনপুট ফাইল পর্যন্ত (যা স্কাইফ্রেম নোডগুলিও) সমস্ত উপায়ে। এই গ্রাফটি মেমরিতে সুস্পষ্টভাবে উপস্থাপিত করার ফলে বিল্ড সিস্টেমটি সঠিকভাবে সনাক্ত করতে দেয় যে কোন নোডগুলি একটি ইনপুট ফাইলের প্রদত্ত পরিবর্তনের দ্বারা প্রভাবিত হয়েছে (একটি ইনপুট ফাইল তৈরি করা বা মুছে ফেলা সহ), আউটপুট ট্রিকে পুনরুদ্ধার করার জন্য ন্যূনতম পরিমাণ কাজ করে। উদ্দেশ্য রাষ্ট্র।

এর অংশ হিসাবে, প্রতিটি নোড একটি নির্ভরতা আবিষ্কার প্রক্রিয়া সম্পাদন করে। প্রতিটি নোড নির্ভরতা ঘোষণা করতে পারে এবং তারপরে আরও নির্ভরতা ঘোষণা করতে সেই নির্ভরতাগুলির বিষয়বস্তু ব্যবহার করতে পারে। নীতিগতভাবে, এটি একটি থ্রেড-পার-নোড মডেলের সাথে ভাল মানচিত্র। যাইহোক, মাঝারি আকারের বিল্ডগুলিতে কয়েক হাজার স্কাইফ্রেম নোড থাকে, যা বর্তমান জাভা প্রযুক্তির সাথে সহজে সম্ভব নয় (এবং ঐতিহাসিক কারণে, আমরা বর্তমানে জাভা ব্যবহারের সাথে আবদ্ধ, তাই কোন হালকা থ্রেড এবং কোন ধারাবাহিকতা নেই)।

পরিবর্তে, Bazel একটি নির্দিষ্ট আকারের থ্রেড পুল ব্যবহার করে। যাইহোক, এর মানে হল যে যদি একটি নোড এমন একটি নির্ভরতা ঘোষণা করে যা এখনও উপলব্ধ নয়, আমাদের সেই মূল্যায়ন বাতিল করতে হবে এবং এটি পুনরায় চালু করতে হতে পারে (সম্ভবত অন্য থ্রেডে), যখন নির্ভরতা উপলব্ধ থাকে। এর মানে হল যে নোডগুলি অতিরিক্তভাবে এটি করা উচিত নয়; একটি নোড যা ক্রমিকভাবে N নির্ভরতা ঘোষণা করে তা সম্ভাব্যভাবে N বার পুনরায় চালু করা যেতে পারে, O(N^2) সময় ব্যয় করে। পরিবর্তে, আমরা নির্ভরতাগুলির আপ-ফ্রন্ট বাল্ক ঘোষণার লক্ষ্য রাখি, যার জন্য কখনও কখনও কোডটিকে পুনর্গঠন করতে হয়, বা এমনকি পুনরায় আরম্ভের সংখ্যা সীমিত করতে একটি নোডকে একাধিক নোডে বিভক্ত করতে হয়।

মনে রাখবেন যে এই প্রযুক্তিটি বর্তমানে নিয়ম API-এ উপলব্ধ নয়; পরিবর্তে, লোডিং, বিশ্লেষণ, এবং নির্বাহের পর্যায়গুলির উত্তরাধিকার ধারণাগুলি ব্যবহার করে নিয়ম API এখনও সংজ্ঞায়িত করা হয়। যাইহোক, একটি মৌলিক সীমাবদ্ধতা হল যে অন্যান্য নোডের সমস্ত অ্যাক্সেসকে ফ্রেমওয়ার্কের মধ্য দিয়ে যেতে হবে যাতে এটি সংশ্লিষ্ট নির্ভরতাগুলিকে ট্র্যাক করতে পারে। বিল্ড সিস্টেমটি যে ভাষায় প্রয়োগ করা হয়েছে বা যে ভাষায় নিয়মগুলি লেখা হয়েছে (সেগুলি একই হতে হবে না), নিয়ম লেখকদের অবশ্যই স্ট্যান্ডার্ড লাইব্রেরি বা প্যাটার্ন ব্যবহার করা উচিত নয় যা স্কাইফ্রেমকে বাইপাস করে। জাভার জন্য, এর মানে হল java.io.File এড়িয়ে যাওয়া এবং সেইসাথে যেকোন ধরনের প্রতিফলন, এবং যেকোন লাইব্রেরি যা হয়। এই নিম্ন-স্তরের ইন্টারফেসের নির্ভরতা ইনজেকশন সমর্থন করে এমন লাইব্রেরিগুলি এখনও স্কাইফ্রেমের জন্য সঠিকভাবে সেটআপ করতে হবে।

এটি দৃঢ়ভাবে পরামর্শ দেয় যে নিয়ম লেখকদের প্রথম স্থানে সম্পূর্ণ ভাষার রানটাইমে প্রকাশ করা এড়াতে। এই জাতীয় APIগুলির দুর্ঘটনাজনিত ব্যবহারের বিপদটি খুব বড় - অতীতে বেশ কয়েকটি Bazel বাগ অনিরাপদ API ব্যবহার করার নিয়মের কারণে সৃষ্ট হয়েছিল, যদিও নিয়মগুলি Bazel টিম বা অন্যান্য ডোমেন বিশেষজ্ঞরা লিখেছিলেন।

দ্বিঘাত সময় এবং মেমরি খরচ এড়ানো কঠিন

বিষয়গুলিকে আরও খারাপ করার জন্য, স্কাইফ্রেম দ্বারা আরোপিত প্রয়োজনীয়তাগুলি ছাড়াও, জাভা ব্যবহারের ঐতিহাসিক সীমাবদ্ধতা এবং নিয়ম API-এর সেকেলেতা, ভুলবশত লাইব্রেরি এবং বাইনারি নিয়মগুলির উপর ভিত্তি করে যেকোন বিল্ড সিস্টেমে চতুর্মুখী সময় বা মেমরি খরচ প্রবর্তন একটি মৌলিক সমস্যা। দুটি খুব সাধারণ নিদর্শন রয়েছে যা দ্বিঘাত মেমরি খরচ (এবং তাই দ্বিঘাত সময় খরচ) প্রবর্তন করে।

  1. চেইন অফ লাইব্রেরি রুলস - লাইব্রেরির নিয়মগুলির একটি চেইনের ক্ষেত্রে বিবেচনা করুন A নির্ভর করে B এর উপর, নির্ভর করে C এর উপর, ইত্যাদি। তারপর, আমরা এই নিয়মগুলির ট্রানজিটিভ ক্লোজারের উপর কিছু সম্পত্তি গণনা করতে চাই, যেমন জাভা রানটাইম ক্লাসপথ, বা প্রতিটি লাইব্রেরির জন্য C++ লিঙ্কার কমান্ড। সহজভাবে, আমরা একটি আদর্শ তালিকা বাস্তবায়ন নিতে পারি; যাইহোক, এটি ইতিমধ্যেই চতুর্মুখী মেমরি খরচ প্রবর্তন করেছে: প্রথম লাইব্রেরিতে ক্লাসপথে একটি এন্ট্রি রয়েছে, দ্বিতীয়টিতে দুটি, তৃতীয় তিনটি এবং আরও অনেক কিছুর জন্য মোট 1+2+3+...N = O(N) ^2) এন্ট্রি।

  2. বাইনারি নিয়ম একই লাইব্রেরির নিয়মের উপর নির্ভর করে - সেই ক্ষেত্রে বিবেচনা করুন যেখানে বাইনারিগুলির একটি সেট যা একই লাইব্রেরির নিয়মের উপর নির্ভর করে — যেমন আপনার যদি একই লাইব্রেরি কোড পরীক্ষা করে এমন একাধিক পরীক্ষার নিয়ম থাকে। এন নিয়মের মধ্যে বলা যাক, অর্ধেক নিয়ম বাইনারি নিয়ম, এবং বাকি অর্ধেক লাইব্রেরি নিয়ম। এখন বিবেচনা করুন যে প্রতিটি বাইনারি লাইব্রেরি নিয়মের ট্রানজিটিভ ক্লোজার, যেমন জাভা রানটাইম ক্লাসপথ, বা C++ লিঙ্কার কমান্ড লাইনের উপর গণনা করা কিছু সম্পত্তির একটি অনুলিপি তৈরি করে। উদাহরণস্বরূপ, এটি C++ লিঙ্ক কর্মের কমান্ড লাইন স্ট্রিং উপস্থাপনাকে প্রসারিত করতে পারে। N/2 উপাদানগুলির N/2 অনুলিপি হল O(N^2) মেমরি।

দ্বিঘাত জটিলতা এড়াতে কাস্টম সংগ্রহের ক্লাস

Bazel এই উভয় পরিস্থিতি দ্বারা ব্যাপকভাবে প্রভাবিত হয়, তাই আমরা কাস্টম সংগ্রহ ক্লাসের একটি সেট চালু করেছি যা প্রতিটি ধাপে অনুলিপি এড়িয়ে মেমরিতে তথ্যকে কার্যকরভাবে সংকুচিত করে। এই ডাটা স্ট্রাকচারের প্রায় সবকটিই শব্দার্থবিদ্যা সেট করেছে, তাই আমরা একে ডেপসেট (অভ্যন্তরীণ বাস্তবায়নে NestedSet নামেও পরিচিত) বলেছি। বিগত বেশ কয়েক বছর ধরে Bazel এর মেমরি খরচ কমাতে বেশিরভাগ পরিবর্তনগুলি ছিল আগে যা ব্যবহার করা হয়েছিল তার পরিবর্তে depsets ব্যবহার করার পরিবর্তন।

দুর্ভাগ্যবশত, ডিপসেটের ব্যবহার স্বয়ংক্রিয়ভাবে সমস্ত সমস্যার সমাধান করে না; বিশেষ করে, এমনকি প্রতিটি নিয়মে একটি ডিপসেটের উপর পুনরাবৃত্তি করা দ্বিঘাত সময় খরচ পুনরায় চালু করে। অভ্যন্তরীণভাবে, NestedSets-এরও কিছু সহায়ক পদ্ধতি রয়েছে যাতে সাধারণ সংগ্রহের ক্লাসের সাথে আন্তঃকার্যক্ষমতা সহজতর করা যায়; দুর্ভাগ্যবশত, ভুলবশত এই পদ্ধতিগুলির মধ্যে একটিতে NestedSet পাস করা আচরণ অনুলিপি করার দিকে নিয়ে যায়, এবং দ্বিঘাত মেমরি খরচ পুনরায় চালু করে।