বর্তমানে পরীক্ষা চলছে: সম্পূর্ণ হলে GitHub কোড উন্মুক্ত করা হবে।

Ticketing কী?

Ticketing হলো শুধুমাত্র ডিস্ট্রিবিউটেড লকের জন্য তৈরি একটি ডেডিকেটেড লক সার্ভার। এটি Rust-এ লেখা একটি সার্ভার বাইনারি এবং একই বাইনারি প্রোটোকল বাইট-বাই-বাইট বাস্তবায়ন করা ভাষাভিত্তিক অফিসিয়াল ক্লায়েন্ট নিয়ে গঠিত। মাত্র দুটি অপারেশন — অর্জন (acquire) ও মুক্তি (release), প্রতি-কী FIFO সারি, ফেন্সিং টোকেন, আর প্রয়োজনে চালু করা যায় এমন একটি Raft ক্লাস্টার — ডিস্ট্রিবিউটেড লকের জন্য যা যা দরকার শুধু সেটুকুই আছে, বাকি সবকিছু বাদ দেওয়া হয়েছে।

স্টোর-ভিত্তিক লক থেকে এটি কীভাবে আলাদা

বাস্তবে বেশিরভাগ ডিস্ট্রিবিউটেড লক একটি কী-ভ্যালু স্টোরের উপর নির্ভর করে তৈরি হয় — Redis-এর উপর Lua স্ক্রিপ্ট বা Redlock, অথবা ZooKeeper বা etcd-এর সেশন ও লিজের উপর গড়া লক। Redis একটি চমৎকার স্টোর, কিন্তু এটি ডিস্ট্রিবিউটেড লকের জন্য বানানো একটি বিশেষায়িত সিস্টেম নয় — একটি কী-ভ্যালু স্টোরকে লকের কাজে ব্যবহার করা হচ্ছে মাত্র, ফলে গঠনগত ওভারহেড থেকেই যায়: সাধারণ-উদ্দেশ্যের প্রোটোকল পার্স করার খরচ, আর সাধারণ-উদ্দেশ্যের ডেটা স্ট্রাকচারের ওভারহেড — দুটোই সঙ্গে থেকে যায়।

Ticketing শুরু থেকেই শুধুমাত্র ডিস্ট্রিবিউটেড লকের জন্য ডিজাইন করা হয়েছে, তাই প্রতিটি স্তর লক-কেন্দ্রিক করে বানানো।

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

পারফরম্যান্স

  • প্রতি সেকেন্ডে ১০ লক্ষেরও বেশি অপারেশন সামলায়, মেমরি ব্যবহার ১০ মেগাবাইটের কম
সবচেয়ে জনপ্রিয় ডিস্ট্রিবিউটেড লক সিস্টেম Redisson (Redis)-এর সাথে গতির তুলনা।
Ticketing ক্লায়েন্টRedis (Redisson RLock)
JavaScript
210.2 ms · 190,295 ops/s
210.2 ms
Rust
225.9 ms · 177,069 ops/s
225.9 ms
Go
228.4 ms · 175,131 ops/s
228.4 ms
C#
286.9 ms · 139,421 ops/s
286.9 ms
Kotlin
443.5 ms · 90,192 ops/s
443.5 ms
Java
457.8 ms · 87,374 ops/s
457.8 ms
C/C++
659.7 ms · 60,634 ops/s
659.7 ms
Python
739.4 ms · 54,098 ops/s
739.4 ms
Ruby
1,682.4 ms · 23,776 ops/s
1,682.4 ms
Redisবেসলাইন
4,909.1 ms · 8,148 ops/s
4,909.1 ms
বারের দৈর্ঘ্য পুরো প্রক্রিয়ায় লাগা সময় (ms) নির্দেশ করে — যত ছোট, তত দ্রুত।
32 contended keys × 1,250 ops · concurrency 64 · mac mini m4 2024 basic (10 core), single local server, acknowledged release, 1s min-work budget

কেন Ticketing

🚦 ক্রম-সংক্রান্ত সঠিকতা — সরাসরি হস্তান্তরসহ FIFO সারি

আগে যে অপেক্ষা করেছে, সে-ই আগে সেবা পায়। ধারক মুক্তি দিলে, লক পুনরায় প্রতিযোগিতা ছাড়াই সরাসরি সারির সামনের জনের কাছে হস্তান্তরিত হয়। পোলিং-অ্যান্ড-রিট্রাই পদ্ধতির লক প্রতিযোগিতা থেকে সৃষ্ট ক্ষুধার্ততা (starvation), কিংবা রিট্রাই-ঝড় — কোনোটিই ঘটে না।

🛟 মৃত ক্লায়েন্টও লক আটকে রাখে না — লিজ

লক ধারণকারী প্রসেস মারা গেলে বা সংযোগ বিচ্ছিন্ন হয়ে গেলে, অর্জনের সময় নির্ধারিত lease মেয়াদ শেষ হওয়ার পর সার্ভার স্বয়ংক্রিয়ভাবে সেটি পুনরুদ্ধার করে পরবর্তী অপেক্ষমাণকে দিয়ে দেয়। মুক্তি দিতে ভুলে গেলেও সম্পূর্ণ সিস্টেম থেমে যায় না।

🔑 ভুল ক্রমে কাজ হওয়া প্রতিরোধ — ফেন্সিং টোকেন

শুধু লক দিয়ে ঠেকানো যায় না এমন একটি বিপত্তি আছে: ধারক তার lease শেষ হয়ে যাওয়া টের না পেয়ে দেরিতে জেগে ওঠে এবং কাজ শেষ করার অনুরোধ পাঠায়। Ticketing প্রতিটি অর্জনের সঙ্গে একটি ক্রমবর্ধমান ফেন্সিং টোকেন ইস্যু করে। সুরক্ষিত রিসোর্স শুধু একটি নিয়ম মেনে চললেই — "সর্বশেষ দেখা টোকেনের চেয়ে কম যেকোনো টোকেন প্রত্যাখ্যান করো" — এই শেষ ফাঁকটিও বন্ধ হয়ে যায়। বিস্তারিত ব্যাখ্যার জন্য দেখুন কার্যপ্রণালী

🗳️ Raft-ভিত্তিক নন-স্টপ ক্লাস্টার

একটি সার্ভারই যথেষ্ট, কিন্তু জিরো-ডাউনটাইম দরকার হলে ৩টি (বা ৫টি) নোডের একটি ক্লাস্টার গঠন করুন। প্রতিটি লক-স্টেট পরিবর্তন সংখ্যাগরিষ্ঠ নোডের Raft ঐকমত্যের মধ্য দিয়ে যাওয়ার পরই চূড়ান্ত হয়, ফলে লিডার মারা গেলেও বা নেটওয়ার্ক বিভক্ত হয়ে গেলেও একই লক কখনো একসঙ্গে দুইবার ইস্যু হয় না। লিডার ব্যর্থ হলে প্রায় এক সেকেন্ডের মধ্যে নতুন লিডার নির্বাচিত হয় আর ক্লায়েন্টরা স্বয়ংক্রিয়ভাবে সেখানে সরে যায়। ক্লাউড ইনস্ট্যান্স রিবুট বা ধারাবাহিক রক্ষণাবেক্ষণের মতো পরিস্থিতিতে নোড একে একে বন্ধ হলেও সেবা চালু থাকে।

🔒 বিল্ট-ইন প্রমাণীকরণ ও TLS

সংযোগ স্থাপনের সঙ্গে সঙ্গেই প্রতিটি সংযোগ challenge-response টোকেন প্রমাণীকরণের মধ্য দিয়ে যায়, আর টোকেন কখনো ওয়্যারে প্লেইনটেক্সটে পাঠানো হয় না। প্রয়োজনে TLS দিয়ে পুরো সংযোগ এনক্রিপ্ট করা যায়।

🌐 প্রতিটি প্রধান ভাষায় বাইট-বাই-বাইট একই প্রোটোকল

Rust, Java/Kotlin, JavaScript/TypeScript, Python, C#, Go, Ruby, C/C++ — প্রতিটি অফিসিয়াল ক্লায়েন্ট একই বাইনারি প্রোটোকল বাস্তবায়ন করে, প্রতিটি ভাষার ইকোসিস্টেমের সঙ্গে স্বাভাবিকভাবে মানানসই API (async বা ব্লকিং) নিয়ে। যেকোনো ভাষা থেকে অর্জিত লক অন্য যেকোনো ভাষার ক্লায়েন্টের সঙ্গে একই সারিতে ন্যায্যভাবে অপেক্ষা করে। ইনস্টল নির্দেশনা ও উদাহরণের জন্য দেখুন লাইব্রেরি

মাত্র দুটি অপারেশন

খাঁটি লক-সার্ভার হওয়ায় এর পরিধিও ন্যূনতম।

  1. যে রিসোর্স সুরক্ষিত করতে চান তাতে একটি কী যুক্ত করুন — যেমন order-1234, account-77, daily-batch
  2. কাজ শুরুর আগে সেই কী অর্জন (acquire) করুন — অন্য কেউ ব্যবহার করলে FIFO সারিতে আপনার পালার জন্য অপেক্ষা করুন।
  3. কাজ শেষ হলে মুক্তি (release) দিন — পরবর্তী অপেক্ষমাণ পুনরায় প্রতিযোগিতা ছাড়াই সঙ্গে সঙ্গে দায়িত্ব নেয়।

এই দুটি অপারেশনের উপর কেবল lease (স্বয়ংক্রিয় পুনরুদ্ধার) এবং waitTimeout (অপেক্ষা ছেড়ে দেওয়া) — এই দুটি অপশনই যোগ হয়, এটুকুই সম্পূর্ণ API।

এরপর যা পড়বেন