স্কেল ও মাল্টি-রিজিয়ন
নতুন এপিসোড রিলিজের রাতে সবাই একসাথে প্রথম সেগমেন্ট চায় — কোল্ড ক্যাশ, origin-এর উপর ঝড়, এনকোডিং ক্যাপাসিটি, রেপ্লিকেশন শেষ হওয়ার আগেই "ready" ঘোষণা, আর ক্যাটালগ ডেটাবেস হারালে কী হয়।
গল্পে বুঝি
ঈদের আগের সন্ধ্যা, সদরঘাট লঞ্চ টার্মিনাল। সিনার লঞ্চ কোম্পানির সবচেয়ে নামকরা লঞ্চ “এমভি বুখারা” রাত আটটায় বরিশাল ছাড়বে, আর পুরো ঢাকা যেন ঠিক ওই একটা লঞ্চেই উঠতে চায়। সারা বছর টিকিট কাউন্টারে লোক আসে ছড়িয়ে ছিটিয়ে, একজন সকালে, একজন দুপুরে। আজ সাতটা থেকে আটটার মধ্যে কয়েক হাজার মানুষ, আর সবাই চায় একই জিনিস: বুখারার টিকিট, প্রথম শ্রেণির কেবিন।
প্রথম বছর সিনা ব্যাপারটা হালকাভাবে নিয়েছিলেন। টার্মিনালের দশটা কাউন্টারের কারও কাছে আগে থেকে ছাপানো টিকিট ছিল না। প্রথম যাত্রী আসতেই প্রতিটা কাউন্টারের কেরানি মতিঝিলের হেড অফিসে ফোন করে সিট চার্ট চাইল, দশজন একসাথে, একই চার্টের জন্য। হেড অফিসের একটাই ফোন লাইন, খোয়ারিজমি একা ধরছেন, আর প্রতিটা কল তিন মিনিট। লাইন ব্যস্ত, কাউন্টারে লাইন লম্বা হচ্ছে, লোকজন চিৎকার করছে। লঞ্চ ঠিকই ছিল, টিকিটও ছিল, কিন্তু সবাই একই মুহূর্তে একই জিনিস চাওয়ায় ব্যবস্থাটা দম আটকে মরল।
পরের বছর সিনা চারটা জিনিস বদলালেন। এক, আগের রাতেই প্রতিটা কাউন্টারে প্রথম কয়েকশো টিকিট ছাপিয়ে পাঠিয়ে দিলেন, যাতে প্রথম ঢেউ হেড অফিস পর্যন্ত যায়ই না। দুই, টার্মিনালে একটা জোনাল অফিস বসালেন, হাইসাম সেখানে। কোনো কাউন্টারের কিছু লাগলে হাইসামকে বলে; দশটা কাউন্টার একই চার্ট চাইলে হাইসাম হেড অফিসে ফোন করেন একবারই, তারপর দশজনকে কপি দেন। তিন, ঈদের জন্য বাড়তি লঞ্চ রিজার্ভ বহর থেকে দুই সপ্তাহ আগে রং করে, ইঞ্জিন দেখে তৈরি রাখলেন; রিলিজের রাতে নতুন করে লঞ্চ বানানোর চেষ্টা করলেন না। আর চার, একটা নিয়ম করলেন: “লঞ্চ ঘাটে লেগেছে” এই ঘোষণা মাইকে দেওয়া হবে তখনই, যখন সেটা সদরঘাট আর ওপারের কেরানীগঞ্জ ঘাট দুই জায়গাতেই ভিড়েছে। আগের বছর একবার শুধু সদরঘাটে লাগার পরই ঘোষণা হয়েছিল, আর কেরানীগঞ্জের যাত্রীরা ঘাটে গিয়ে শূন্য জেটি দেখেছিল।
সাথে আরও দুইটা সাবধানতা। সিনা আরেকটা লঞ্চ কোম্পানির সাথে চুক্তি করলেন, যাতে নিজের কোনো লঞ্চের ইঞ্জিন বসে গেলে যাত্রীরা একই টিকিটে ওদের লঞ্চে উঠতে পারে। আর যাত্রী তালিকার মোটা খাতাটার একটা কপি প্রতি সন্ধ্যায় বরিশাল অফিসে পাঠাতে শুরু করলেন, কারণ লঞ্চ আবার কেনা যায়, কিন্তু কে কোন কেবিনের টাকা দিয়েছে সেই খাতা পুড়ে গেলে আর ফেরত আসে না।
মিলিয়ে নিই: এক লঞ্চের জন্য সবার একসাথে আসা হলো রিলিজ-ডে ফ্ল্যাশ ক্রাউড, আর প্রতিটা কাউন্টারে আগে থেকে টিকিট না থাকা হলো কোল্ড ক্যাশ। দশ কেরানির একসাথে হেড অফিসে ফোন হলো thundering herd, আর আগের রাতে টিকিট ছাপিয়ে পাঠানো হলো CDN প্রি-ওয়ার্মিং। হাইসামের জোনাল অফিস হলো origin shield, আর তার “একবার ফোন, দশ কপি” হলো request collapsing। দুই সপ্তাহ আগে বাড়তি লঞ্চ তৈরি রাখা মানে রিলিজের আগে এনকোড করে রাখা, আর প্রায়োরিটি কিউ। দুই ঘাটে ভেড়ার পরই ঘোষণা হলো সব রিজিয়নে রেপ্লিকেশন শেষ হলে তবেই “ready”। অন্য কোম্পানির সাথে চুক্তি হলো মাল্টি-CDN, আর যাত্রী খাতার কপি হলো ক্যাটালগ ডেটাবেসের DR। বাস্তবে যেকোনো স্ট্রিমিং সার্ভিস জনপ্রিয় সিরিজের নতুন সিজন বা বড় খেলার হাইলাইট ছাড়ার রাতে ঠিক এই সমস্যাগুলোই সামলায়।
রিলিজের রাত কেন আলাদা
সাধারণ দিনে VOD ট্রাফিক একটা “লম্বা লেজ”: হাজার হাজার ভিডিও, প্রত্যেকটা কেউ না কেউ দেখছে, আর প্রত্যেকে ভিডিওর ভিন্ন ভিন্ন জায়গায়। ক্যাশ ধীরে ধীরে গরম হয়, origin-এর উপর চাপ সমান থাকে।
রিলিজের রাতে ছবিটা উল্টে যায়। একটা ভিডিও, আর সবাই একই সময়ে একই সেগমেন্টে: প্রথমে মাস্টার প্লেলিস্ট, তারপর প্রতিটা রেন্ডিশনের init সেগমেন্ট, তারপর seg_000, seg_001। দশ মিনিট পর সবাই মোটামুটি একসাথে সেগমেন্ট ১৫০-এ। মানে ক্যাশ গরম হওয়ার আগেই সবচেয়ে বড় ঢেউটা আসে।
একটা আনুমানিক হিসাব দেখুন। ধরুন CDN-এর ৪০টা PoP থেকে দর্শক আসছে, ল্যাডারে ৫টা রেন্ডিশন, আর প্রথম মিনিটে প্লেয়ার প্রতিটা রেন্ডিশন থেকে গড়ে ৫টা অবজেক্ট চায় (প্লেলিস্ট, init, ৩টা সেগমেন্ট):
Without a shield, every PoP misses on its own:
40 PoPs x 5 renditions x 5 objects = 1,000 origin requests in the first minute
...and if 300 viewers hit one cold PoP in the same second,
a PoP without request collapsing forwards all 300.
With a shield (and collapsing at both layers):
1 shield x 5 renditions x 5 objects = 25 origin requests
Illustrative numbers. Plug in your own PoP count and ladder. সংখ্যাগুলো বানানো, কিন্তু অনুপাতটা আসল: shield ছাড়া origin-এর লোড PoP-এর সংখ্যার সাথে গুণ হয়। আর origin যদি তখন ধীর হয়ে যায়, প্রতিটা PoP টাইমআউট পেয়ে আবার চেষ্টা করে, ফলে লোড আরও বাড়ে। এই “retry storm” নিয়ে CDN ট্র্যাকের Origin আর্কিটেকচার চ্যাপ্টারে বিস্তারিত আছে; এখানে আমরা দেখব VOD-এর জন্য কী আলাদা।
VOD-এর সুবিধা: সেগমেন্ট কখনো বদলায় না
লাইভের সাথে তুলনা করলে VOD-এর একটা বড় সুবিধা আছে: একবার এনকোড হওয়া সেগমেন্ট আর কখনো বদলায় না। তাই:
- সেগমেন্ট আর init ফাইল লম্বা সময়ের জন্য ক্যাশ করা যায়:
Cache-Control: public, max-age=31536000, immutable। এগুলো যত লম্বা সময় এজে থাকে, origin তত শান্ত। - মিডিয়া প্লেলিস্ট (
#EXT-X-ENDLISTসহ) ও কার্যত বদলায় না, কিন্তু রি-এনকোড বা মুছে ফেলার সুযোগ রাখতে এগুলোকে একটু ছোট TTL দেওয়া ভালো। কেন, সেটা CDN ট্র্যাকের পার্জ ও ইনভ্যালিডেশন চ্যাপ্টারে আছে। - রি-এনকোড করলে নতুন পাথ: একই পাথে নতুন সেগমেন্ট লিখবেন না।
hls/ASSET_ID/v2/...এর মতো ভার্সনড পাথে লিখে প্লেব্যাক API-কে নতুন মাস্টার দেখান। তাহলে পুরনো ক্যাশ পার্জ করার দরকারই পড়ে না।
রিলিজের রাতে পার্জ-অল কখনো নয়। “সব ক্যাশ মুছে দাও” চাপলে প্রতিটা PoP একসাথে কোল্ড হয়ে যায়, আর আপনি নিজের হাতে সেই ঝড়টা বানান যেটা এড়াতে চাইছিলেন। কোনো একটা ভিডিও ঠিক করতে হলে শুধু সেই ভিডিওর পাথ বা ট্যাগ পার্জ করুন।
প্রি-ওয়ার্মিং: প্রথম ঢেউ origin-এ পৌঁছানোর আগেই থামানো
প্রি-ওয়ার্মিং মানে রিলিজের আগে নিজেই প্রতিটা এজে গিয়ে প্রথম কয়েকটা সেগমেন্ট চেয়ে নেওয়া, যাতে প্রথম আসল দর্শক HIT পায়। ধারণা সহজ, কিন্তু একটা ফাঁদ আছে।
ফাঁদটা হলো রাউটিং। অফিসের ল্যাপটপ থেকে curl চালালে আপনি শুধু আপনার নিকটতম PoP গরম করেন। Anycast বা GeoDNS আপনাকে সবসময় একই জায়গায় পাঠায় (DNS ট্র্যাকের DNS দিয়ে ট্রাফিক চালানো দেখুন)। চট্টগ্রামের PoP ঠান্ডাই থেকে যায়। তিনটা উপায় আছে:
- নিজের CDN হলে (CDN ট্র্যাকের নিজের CDN বানানো) প্রতিটা এজের IP আপনি জানেন। প্রতিটা IP-তে সরাসরি রিকোয়েস্ট পাঠান, একই URL আর Host হেডার দিয়ে। নিচের স্ক্রিপ্ট ঠিক এটাই করে।
- ভেন্ডর CDN হলে দেখুন তাদের prefetch বা preload API আছে কিনা। কিছু ভেন্ডরের আছে, কিছুর নেই, আর কোন প্ল্যানে কী আছে সেটা বদলায়; নিজের ভেন্ডরের ডকস দেখে নিশ্চিত হোন।
- কিছুই না থাকলে বিভিন্ন রিজিয়নের কয়েকটা ছোট VPS থেকে একই স্ক্রিপ্ট চালান। প্রতিটা VPS তার কাছের PoP গরম করবে। নিখুঁত না, কিন্তু প্রথম ঢেউয়ের বড় অংশ কমায়।
#!/usr/bin/env bash
# Usage: prewarm.sh MASTER_URL SEGMENTS EDGE_IP [EDGE_IP...]
# Pulls the first SEGMENTS segments of every rendition through each edge, so release-day viewers hit warm caches.
set -euo pipefail
master=$1 n=$2; shift 2
host=$(echo "$master" | sed -E 's#^https?://([^/:]+).*#\1#')
port=$(echo "$master" | sed -nE 's#^https?://[^/:]+:([0-9]+).*#\1#p')
[ -n "$port" ] || { [[ $master == https* ]] && port=443 || port=80; }
for edge in "$@"; do
via=(--connect-to "$host:$port:$edge:$port") # same URL and Host header, different machine
get() { curl -sf "${via[@]}" "$1"; }
echo "== edge $edge"
get "$master" | grep -vE '^(#|$)' | while read -r media_uri; do
media="${master%/*}/$media_uri"
playlist=$(get "$media")
init=$(echo "$playlist" | grep -oE '^#EXT-X-MAP:URI="[^"]+"' | sed -E 's/.*URI="([^"]+)"/\1/' || true)
for seg in $init $(echo "$playlist" | grep -vE '^(#|$)' | head -n "$n"); do
status=$(curl -s -o /dev/null -D - "${via[@]}" "${media%/*}/$seg" |
tr -d '\r' | grep -iE '^(x-cache-status|x-cache|cf-cache-status|cache-status):' | head -1 || true)
echo " $media_uri $seg ${status:-(no cache header)}"
done
done
done চ্যাপ্টার ১২-এর লোকাল প্ল্যাটফর্মের nginx এজে চালালে প্রথমবার আর দ্বিতীয়বার এমন দেখায় (আসল রান, কয়েকটা লাইন বাদ):
$ ./prewarm.sh http://localhost:8081/media/hls/4258cf13-.../master.m3u8 2 127.0.0.1
== edge 127.0.0.1
720p/index.m3u8 init_0.mp4 X-Cache-Status: MISS
720p/index.m3u8 seg_000.m4s X-Cache-Status: MISS
720p/index.m3u8 seg_001.m4s X-Cache-Status: MISS
480p/index.m3u8 init_1.mp4 X-Cache-Status: MISS
...
$ ./prewarm.sh http://localhost:8081/media/hls/4258cf13-.../master.m3u8 2 127.0.0.1
== edge 127.0.0.1
720p/index.m3u8 init_0.mp4 X-Cache-Status: HIT
720p/index.m3u8 seg_000.m4s X-Cache-Status: HIT
... কয়টা সেগমেন্ট গরম করবেন? প্রথম এক থেকে দুই মিনিট যথেষ্ট। এর পরের ঢেউ আসে প্রথম দর্শকদের পেছন পেছন, আর ততক্ষণে প্রথম দিকের দর্শকরাই ক্যাশ গরম করে দিয়েছে। পুরো ভিডিও প্রি-ওয়ার্ম করা মানে এজের ক্যাশ জায়গা নষ্ট করা আর অন্য জনপ্রিয় ভিডিওকে বের করে দেওয়া।
প্রি-ওয়ার্মিং পাবলিশ করার আগে করতে হয়, কিন্তু URL তখন পাবলিক নয়। তাই প্রি-ওয়ার্মের জন্য URL-গুলো এমন হতে হবে যে পরে দর্শকের URL-এর সাথে একই ক্যাশ কী পায়। Signed URL-এ টোকেন যদি ক্যাশ কীর অংশ হয়, প্রি-ওয়ার্ম কোনো কাজেই আসবে না। টোকেন কীভাবে ক্যাশ কী থেকে বাদ রাখতে হয়, সেটা সিকিউরিটি আর CDN ট্র্যাকের ক্যাশ কী চ্যাপ্টারে আছে।
Origin shield ও request collapsing: VOD-এর জন্য কী মনে রাখবেন
Shield আর collapsing-এর মেকানিজম CDN ট্র্যাকের Origin আর্কিটেকচার চ্যাপ্টারে পুরোটা আছে। VOD-এর জন্য তিনটা কথা যোগ করার আছে:
- Shield-কে origin-এর কাছে রাখুন। অবজেক্ট স্টোরেজ যে রিজিয়নে, shield সেখানে। নইলে প্রতিটা miss দুইবার মহাদেশ পার হয়।
- Collapsing ছাড়া shield অর্ধেক কাজ করে। একই সেকেন্ডে ৩০০ জন একই কোল্ড সেগমেন্ট চাইলে collapsing ছাড়া shield-ও ৩০০টা রিকোয়েস্ট origin-এ পাঠায়। nginx-এ এটা
proxy_cache_lock on, যেটা চ্যাপ্টার ১২-এর এজে আছে। - রেঞ্জ রিকোয়েস্টে সাবধান। কিছু প্লেয়ার একটা বড় ফাইলের ভিন্ন ভিন্ন বাইট-রেঞ্জ চায় (byte-range HLS বা প্রগ্রেসিভ MP4)। এজ যদি প্রতিটা রেঞ্জকে আলাদা অবজেক্ট হিসেবে নেয়, collapsing কাজ করে না। সেগমেন্টেড আউটপুট (প্রতিটা সেগমেন্ট আলাদা ফাইল) এই সমস্যা এড়ায়। বড় ফাইলের slice ক্যাশিং CDN ট্র্যাকের পারফরম্যান্স চ্যাপ্টারে আছে।
এনকোডিং ক্যাপাসিটি: রিলিজের রাতে এনকোড করবেন না
সবচেয়ে সহজ স্কেলিং টিপ: “এনকোড হয়েছে” আর “পাবলিশ হয়েছে” দুইটা আলাদা জিনিস। বড় রিলিজ দিন কয়েক আগে আপলোড আর এনকোড হয়, published_at ভবিষ্যতের একটা সময়ে সেট থাকে, আর প্লেব্যাক API সেই সময়ের আগে not_published ফেরত দেয়। রিলিজের মুহূর্তে শুধু একটা টাইমস্ট্যাম্প পার হয়, কোনো ffmpeg চলে না।
alter table assets add column published_at timestamptz;
-- The playback API's gate: ready AND published.
select status, published_at <= now() as is_live
from assets
where id = $1; তবু এমন দিন আসে যখন হঠাৎ অনেক এনকোড লাগে: ঈদের দিনে হাজার হাজার ইউজার আপলোড, বা একটা কোর্স প্ল্যাটফর্মে নতুন সেমিস্টার শুরু। তখন দুইটা জিনিস কাজে লাগে।
আলাদা কিউ, আলাদা ওয়ার্কার। সব কাজ এক কিউতে থাকলে ইউজারদের ১০,০০০ আপলোডের পেছনে আপনার প্রিমিয়ার এপিসোড আটকে থাকে। River-এ প্রতিটা কিউর নিজের MaxWorkers, তাই “প্রিমিয়ার” কিউকে আলাদা ক্যাপাসিটি দেওয়া যায়, আর একই কিউর ভেতরে Priority (১ সবচেয়ে উঁচু, ৪ সবচেয়ে নিচু; ২০২৬ সালের অক্টোবরে River v0.49-এর ডকস অনুযায়ী) দিয়ে আগে-পরে ঠিক করা যায়:
// Separate queue: a flood of user uploads can never starve a premiere.
client, err := river.NewClient(riverpgxv5.New(db), &river.Config{
Queues: map[string]river.QueueConfig{
"premiere": {MaxWorkers: 4},
river.QueueDefault: {MaxWorkers: 8},
},
Workers: workers,
})
_, err = client.Insert(ctx, EncodeArgs{AssetID: id}, &river.InsertOpts{Queue: "premiere", Priority: 1}) কিউর গভীরতা দেখে ওয়ার্কার বাড়ানো। এনকোড ওয়ার্কার স্টেটলেস (সোর্স নামায়, এনকোড করে, আপলোড করে), তাই নতুন VPS যোগ করলেই ক্যাপাসিটি বাড়ে। কখন বাড়াবেন, সেটা ঠিক করে কিউতে কত কাজ জমে আছে আর সবচেয়ে পুরনোটা কতক্ষণ ধরে অপেক্ষা করছে:
select queue,
count(*) as waiting,
max(now() - scheduled_at) as oldest_wait
from river_job
where state = 'available' and kind = 'encode'
group by queue; queue | waiting | oldest_wait
----------+---------+-----------------
default | 412 | 00:38:12.4
premiere | 0 |
(illustrative) oldest_wait হলো আসল মেট্রিক। ৪১২টা কাজ জমে থাকা খারাপ শোনায়, কিন্তু প্রতিটা দুই মিনিটের হলে আর ৫০টা ওয়ার্কার থাকলে কিউ ১৭ মিনিটে খালি হবে। অন্যদিকে ২০টা কাজ যদি এক ঘণ্টা ধরে অপেক্ষা করে, কিছু একটা আটকে আছে। ওয়ার্কার বাড়ানো, কাজের খরচ আর ক্যাপাসিটির হিসাব এনকোডিং জব অর্কেস্ট্রেশন আর অ্যানালিটিক্স ও খরচ চ্যাপ্টারে।
মাল্টি-রিজিয়ন স্টোরেজ ও origin
একটা রিজিয়নের অবজেক্ট স্টোরেজ পুরো সার্ভিসের single point of failure। সেই ডেটা সেন্টারের নেটওয়ার্ক কাটলে প্রতিটা ক্যাশ miss ফেল করে, আর কয়েক ঘণ্টার মধ্যে এজের ক্যাশ খালি হতে থাকলে সব প্লেব্যাক থেমে যায়। সমাধান দ্বিতীয় রিজিয়ন, কিন্তু সেটা নতুন একটা সমস্যা আনে।
রেপ্লিকেশন lag আর মিথ্যা “ready”
ওয়ার্কার প্রাইমারি রিজিয়নে সেগমেন্ট লিখল, অ্যাসেটকে ready করল, ওয়েবহুক গেল কাস্টমারের কাছে, কাস্টমার লিংক শেয়ার করল। কিন্তু দ্বিতীয় রিজিয়নে রেপ্লিকেশন তখনো চলছে। এখন যে দর্শকের রিকোয়েস্ট দ্বিতীয় রিজিয়নের origin-এ যায়, সে প্লেলিস্ট পায় কিন্তু শেষের সেগমেন্টগুলোতে 404। আরও খারাপ: এজ সেই 404 ক্যাশ করে ফেলতে পারে।
নিয়ম একটাই: সব রিজিয়নে আছে নিশ্চিত হওয়ার আগে ready নয়।
যাচাই করার সবচেয়ে সহজ উপায়: এনকোড শেষে প্রাইমারিতে লেখা অবজেক্টের একটা তালিকা (কী আর সাইজ) সেভ করুন, তারপর একটা আলাদা জব প্রতিটা রেপ্লিকা রিজিয়নে সেই তালিকা মিলিয়ে দেখবে। না মিললে জব রিট্রাই করবে (River নিজেই ব্যাকঅফ সহ রিট্রাই করে), মিললে স্ট্যাটাস ready। এতে “ready” মানে সত্যিই ready।
স্টোরেজ নিজে রেপ্লিকেট করে নাকি আপনাকে করতে হয়, সেটা সিস্টেমভেদে আলাদা। চ্যাপ্টার ১২-এ যে Garage ব্যবহার করা হয়েছে সেটা একটা ক্লাস্টারের ভেতরে কয়েকটা zone-এ ডেটা কপি রাখতে পারে (replication_factor আর লেআউটের zone দিয়ে), কিন্তু সেটা একটা ক্লাস্টার, দুইটা স্বাধীন রিজিয়ন নয়। দুই রিজিয়নে দুইটা আলাদা স্টোরেজ রাখলে কপি করার কাজটা আপনার নিজের জব দিয়েই করাতে হবে। আপনার স্টোরেজ ঠিক কী দেয়, তার ডকস দেখে নিশ্চিত হোন।
দর্শককে কোন origin-এ পাঠাবেন
দুই রিজিয়নে origin থাকলে CDN-কে বলতে হয় কোনটায় যাবে। সাধারণ প্যাটার্ন হলো active-passive: সব miss প্রাইমারিতে যায়, প্রাইমারির হেলথ চেক ফেল করলে CDN সেকেন্ডারিতে যায়। Active-active (কাছের origin-এ যাওয়া) লেটেন্সি কমায়, কিন্তু তখন উপরের রেপ্লিকেশন নিয়ম আরও কঠোরভাবে মানতে হয়, কারণ যেকোনো miss যেকোনো রিজিয়নে যেতে পারে। Origin ফেইলওভার আর হেলথ চেক CDN ট্র্যাকের Origin আর্কিটেকচার চ্যাপ্টারে।
VOD-এর জন্য মাল্টি-CDN
দুইটা CDN কেন আর কীভাবে, সেটা CDN ট্র্যাকের প্রোডাকশন CDN চ্যাপ্টারে আছে। VOD-এ একটা বাড়তি সুবিধা আছে যেটা ওয়েবপেজে নেই: প্লেয়ার নিজেই সেশনের মাঝখানে CDN বদলাতে পারে।
শর্ত দুইটা। এক, দুই CDN-এ পাথ হুবহু এক হতে হবে: cdn-a.example.com/hls/ID/720p/seg_042.m4s আর cdn-b.example.com/hls/ID/720p/seg_042.m4s, যাতে প্লেয়ার শুধু হোস্ট বদলে একই সেগমেন্ট পায়। দুই, দুইটা CDN একই origin (বা একই কনটেন্টের দুই রেপ্লিকা) থেকে টানবে।
এটা করার স্ট্যান্ডার্ড উপায় হলো Content Steering: মাস্টার প্লেলিস্টে #EXT-X-CONTENT-STEERING ট্যাগ একটা ছোট JSON “steering manifest”-এর দিকে দেখায়, যেটা বলে এখন কোন CDN আগে চেষ্টা করতে হবে, আর প্লেয়ার সেটা নিয়মিত রিফ্রেশ করে। এক CDN-এ সমস্যা হলে আপনি steering manifest বদলান, আর চলমান সেশনগুলোও কয়েক সেকেন্ডের মধ্যে অন্য CDN-এ চলে যায়, DNS TTL-এর জন্য অপেক্ষা না করেই। DASH-এও একই ধারণার একটা স্পেক আছে। hls.js 1.7.3-এর API ডকসে Content Steering ম্যানিফেস্ট লোডের কনফিগ আর ইভেন্ট আছে (২০২৬ সালের অক্টোবরে দেখা); Safari, নেটিভ প্লেয়ার আর আপনার প্লেয়ার লাইব্রেরির ভার্সনে কতটা সাপোর্ট আছে, সেটা নিজে যাচাই করে নিন। প্লেয়ারের দিকটা ভিডিও প্লেয়ার ট্র্যাকে।
#EXTM3U
#EXT-X-CONTENT-STEERING:SERVER-URI="https://steer.baytalhikma.org/v1/steering?asset=ID",PATHWAY-ID="cdn-a"
#EXT-X-STREAM-INF:BANDWIDTH=2939754,RESOLUTION=1280x720,CODECS="avc1.64001f,mp4a.40.2",PATHWAY-ID="cdn-a"
https://cdn-a.baytalhikma.org/hls/ID/720p/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2939754,RESOLUTION=1280x720,CODECS="avc1.64001f,mp4a.40.2",PATHWAY-ID="cdn-b"
https://cdn-b.baytalhikma.org/hls/ID/720p/index.m3u8
(illustrative) { "VERSION": 1, "TTL": 60, "PATHWAY-PRIORITY": ["cdn-b", "cdn-a"] } শেষ JSON-টা বলছে: আগামী ৬০ সেকেন্ড cdn-b আগে চেষ্টা করো। Steering সার্ভারটা ছোট একটা এন্ডপয়েন্ট, কিন্তু সেটা নিজে ডাউন হলে প্লেয়ার শেষ জানা অগ্রাধিকার নিয়েই চলতে থাকে, তাই এটা single point of failure নয়।
ডেটা রেসিডেন্সি: কোন ভিডিও কোথায় থাকতে পারে
কিছু কাস্টমার (সরকারি প্রতিষ্ঠান, হাসপাতাল, কিছু শিক্ষা প্রতিষ্ঠান) চুক্তিতে বা আইনে বাধ্য থাকে যে তাদের ভিডিও একটা নির্দিষ্ট দেশের বাইরে যাবে না। এর মানে শুধু অবজেক্ট স্টোরেজের রিজিয়ন নয়:
- সোর্স আর রেন্ডিশন দুটোই সেই দেশের স্টোরেজে।
- এনকোডিং ওয়ার্কারও সেখানে, কারণ ওয়ার্কার সোর্স নামিয়ে লোকাল ডিস্কে রাখে।
- CDN-এর ক্যাশ: বিদেশের PoP-এ ক্যাশ হওয়াটাও “কপি” কিনা, সেটা চুক্তিতে কী লেখা আছে তার উপর নির্ভর করে। কিছু CDN রিজিয়ন-সীমিত ডেলিভারি দেয়।
- ব্যাকআপ আর লগ: রেসিডেন্সি চুক্তিগুলো প্রায়ই ব্যাকআপেও প্রযোজ্য।
ডিজাইনে এর সহজ রূপ হলো অ্যাসেটের সাথে একটা region কলাম, আর সেই রিজিয়ন দিয়ে ঠিক হয় কোন বাকেট, কোন ওয়ার্কার কিউ, কোন origin। কোন দেশে ঠিক কী আইন, সেটা আইনজীবীর প্রশ্ন, আর এগুলো বদলায়; এখানে কোনো নির্দিষ্ট আইনের দাবি করা হচ্ছে না, শুধু মেকানিজম।
ক্যাটালগ ডেটাবেসের DR: কোনটা হারালে কী যায়
VOD প্ল্যাটফর্মে তিন রকম ডেটা আছে, আর তিনটার দাম আলাদা:
| ডেটা | হারালে কী হয় | ফেরত আনা যায়? | সুরক্ষা |
|---|---|---|---|
| রেন্ডিশন (সেগমেন্ট, প্লেলিস্ট) | প্লেব্যাক বন্ধ | হ্যাঁ, সোর্স থেকে রি-এনকোড করে (সময় আর খরচ লাগে) | এক কপি যথেষ্ট হতে পারে |
| সোর্স ফাইল (মেজানাইন) | রি-এনকোড অসম্ভব | না, কাস্টমারের কাছে থাকলে ছাড়া | দুইটা আলাদা জায়গায় কপি |
| ক্যাটালগ DB (অ্যাসেট, মালিক, পলিসি) | কোন ফাইল কার, কোনটা পাবলিক, কিছুই জানা নেই | না | রেপ্লিকা + পয়েন্ট-ইন-টাইম রিকভারি |
এখানে মূল কথা: ক্যাটালগ ডেটাবেস হলো সেই যাত্রী খাতা। স্টোরেজে লাখ লাখ সেগমেন্ট পড়ে থাকলেও কোনটা কোন কাস্টমারের, কোনটা প্রাইভেট, কোনটা মুছে ফেলার কথা, এই তথ্য না থাকলে সেগুলো অর্থহীন। PostgreSQL-এর জন্য দুই স্তরের সুরক্ষা লাগে: অন্য রিজিয়নে একটা streaming রেপ্লিকা (দ্রুত ফেইলওভারের জন্য), আর WAL আর্কাইভ সহ বেস ব্যাকআপ (ভুল করে delete চালালে তার আগের মুহূর্তে ফেরার জন্য, কারণ রেপ্লিকা সেই delete-ও সাথে সাথে কপি করে ফেলে)। RPO, RTO আর ব্যাকআপ টেস্ট করার পদ্ধতি ডিজাস্টার রিকভারি ট্র্যাকে আছে।
রিস্টোর টেস্ট না করা ব্যাকআপ ব্যাকআপ নয়। মাসে একবার ব্যাকআপ থেকে একটা আলাদা সার্ভারে ক্যাটালগ রিস্টোর করুন, তারপর র্যান্ডম দশটা অ্যাসেটের প্লেব্যাক URL বানিয়ে চালিয়ে দেখুন। প্রথমবার এটা করলে প্রায় সবসময়ই কিছু একটা ভাঙা পাওয়া যায়।
ডিবাগিং: এই চ্যাপ্টারের সমস্যাগুলো
| লক্ষণ | কীভাবে নিশ্চিত হবেন | সমাধান |
|---|---|---|
| রিলিজের প্রথম পাঁচ মিনিটে origin-এর CPU/নেটওয়ার্ক সর্বোচ্চ, তারপর ঠিক | origin-এর access log-এ সবচেয়ে বেশি আসা পাথগুলো গুনুন (টেবিলের নিচের কমান্ড); একই কয়েকটা seg_000 বারবার এলে এটাই কারণ | Shield চালু করুন, collapsing (proxy_cache_lock) চালু করুন, আর রিলিজের আগে প্রি-ওয়ার্ম করুন |
| প্রি-ওয়ার্ম করার পরও দর্শকেরা MISS পাচ্ছে | দর্শকের URL আর প্রি-ওয়ার্মের URL পাশাপাশি রাখুন; টোকেন বা query string ভিন্ন কিনা দেখুন, আর দুটোতেই curl -sI চালিয়ে ক্যাশ হেডার মেলান | টোকেন ক্যাশ কী থেকে বাদ দিন; সব PoP-এ প্রি-ওয়ার্ম হয়েছে কিনা (শুধু কাছেরটায় নয়) দেখুন |
| প্রিমিয়ার এপিসোড ঘণ্টাখানেক “processing” | উপরের river_job কিউ-ডেপথ কুয়েরি, oldest_wait দেখুন | প্রিমিয়ারের জন্য আলাদা কিউ আর আগে থেকে এনকোড; published_at দিয়ে রিলিজ করুন |
| “Ready” ওয়েবহুকের পর কিছু দর্শক শেষের দিকে 404 পায় | 404 পাওয়া সেগমেন্টটা প্রতিটা রিজিয়নের স্টোরেজে আলাদাভাবে curl -sI করুন | রেপ্লিকেশন যাচাই জব শেষ হওয়ার পরই ready; 404 ক্যাশ হয়ে থাকলে সেই পাথ পার্জ করুন |
| এক CDN-এ সমস্যা, কিন্তু চলমান দর্শকেরা সরছে না | প্লেয়ারের নেটওয়ার্ক ট্যাবে সেগমেন্ট কোন হোস্ট থেকে আসছে দেখুন; steering manifest-এর TTL দেখুন | Content Steering ব্যবহার করুন; শুধু DNS দিয়ে সরালে পুরনো সেশন DNS TTL পর্যন্ত আটকে থাকে |
| এক দেশের কাস্টমারের ভিডিও অন্য রিজিয়নের বাকেটে পাওয়া গেল | select id, region from assets আর বাকেটের কী মিলিয়ে দেখুন | রিজিয়ন অ্যাসেটে রাখুন, আর বাকেট, কিউ, origin সবকিছু সেখান থেকে ঠিক করুন; হার্ডকোড করা বাকেট নাম খুঁজে বের করুন |
ভুল delete-এর পর রেপ্লিকাতেও ডেটা নেই | রেপ্লিকায় একই কুয়েরি চালান | রেপ্লিকা ব্যাকআপ নয়; WAL আর্কাইভ থেকে delete-এর আগের সময়ে পয়েন্ট-ইন-টাইম রিকভারি করুন |
Origin-এর access log থেকে সবচেয়ে বেশি চাওয়া পাথ (nginx-এর ডিফল্ট combined ফরম্যাটে পাথ সপ্তম ফিল্ড):
awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head মূল শিক্ষা
- রিলিজের রাতে সবাই একই সেগমেন্টে। লম্বা লেজের ট্রাফিক নয়, একটা তীক্ষ্ণ চূড়া, আর সেটা আসে ক্যাশ গরম হওয়ার আগেই।
- Shield ছাড়া origin-এর লোড PoP-এর সংখ্যার সাথে গুণ হয়। Shield আর collapsing একসাথে সেটাকে প্রায় একটা রিকোয়েস্টে নামায়।
- প্রি-ওয়ার্ম প্রতিটা এজে করতে হয়, শুধু কাছেরটায় নয়। আর প্রি-ওয়ার্মের URL-কে দর্শকের URL-এর মতো একই ক্যাশ কী পেতে হয়।
- এনকোড আর পাবলিশ আলাদা করুন। রিলিজ মানে একটা টাইমস্ট্যাম্প পার হওয়া, ffmpeg চালু হওয়া নয়।
- কিউ আলাদা করুন, আর
oldest_waitদেখে স্কেল করুন, শুধু জমে থাকা কাজের সংখ্যা দেখে নয়। - সব রিজিয়নে অবজেক্ট নিশ্চিত না হলে “ready” নয়। মিথ্যা ready-র ফল হলো ক্যাশ হয়ে যাওয়া 404।
- VOD-এ প্লেয়ার মাঝপথে CDN বদলাতে পারে, যদি দুই CDN-এ পাথ হুবহু এক হয়। Content Steering এর স্ট্যান্ডার্ড উপায়।
- ক্যাটালগ DB সবচেয়ে দামি ডেটা। সেগমেন্ট আবার বানানো যায়, সোর্স আর ক্যাটালগ যায় না। রেপ্লিকা আর পয়েন্ট-ইন-টাইম ব্যাকআপ দুটোই লাগে, আর রিস্টোর নিয়মিত টেস্ট করতে হয়।