Distributed Rate Limiting: shared atomic counter
Distributed Rate Limits and Atomic Counters
এই পাঠের সূচিপত্র
এই পাঠে যা শিখবেন
- Multi-instance counter race
- Redis Lua atomic decision ও expiry
- Failover ও cross-region limit-এর সীমা
সমস্যা দিয়ে শুরু
দুটি Laravel instance একই user-এর count ৫৯ পড়ল। Limit ৬০; দুজনই allow করে count ৬০ লিখল। আসলে ৬১ request পাস হলো। Read-check-write আলাদা network commands হলে shared Redis থাকলেও race থাকে। Distributed limiter-এ decision ও state transition একই atomic boundary-তে আনতে হয়।
সহজ করে বুঝি
Redis single execution context-এ Lua script-এর commands অন্য client command-এর সঙ্গে interleave করে না। Fixed window counter increment এবং প্রথম increment-এ expiry একই script-এ করা যায়। Increment-এর পরে process crash হলেও expiry বাদ যায় না। তবে atomicity durability নয়; asynchronous failover-এ recent counter হারাতে পারে। Strict global limit ও multi-region low latency-এর মধ্যে coordination trade-off আছে। Monetary quota হলে durable ledger/invariant cache counter-এর বাইরে লাগতে পারে।
পরিচিত একটি উপমা
এক cashier token গুনে তারপর stamp দিলে দুই cashier একই শেষ token দিতে পারেন। Shared counter-এ একবারে “বাকি থাকলে বরাদ্দ করো” operation দরকার। কিন্তু খাতা হারিয়ে পুরোনো copy এলে সাম্প্রতিক stamp হারাতে পারে; atomic desk operation আর durable হিসাব আলাদা।
নকশাটি দেখুন
Diagram লোড হচ্ছে…
Diagram-এর text flow দেখুন
flowchart TD
A[Laravel instance A] --> S[Atomic Redis script]
B[Laravel instance B] --> S
S --> C[(Counter and expiry)]
S --> D{Count within budget}
D -->|Yes| O[Allow]
D -->|No| R[Reject 429]দুই caller একই script boundary ব্যবহার করছে। Counter increment আর expiry atomic execution-এ হয়। Limit ছাড়ালেও rejected attempt count বাড়ে; এটি উদাহরণের policy। Expiry প্রথম request থেকে ৬০ সেকেন্ড—calendar minute নয়। Key window semantics document করুন, নইলে client reset expectation ভুল হবে।
PHP ও Laravel-এ প্রয়োগ
Controller/service fragment; authenticated $userId server-derived এবং dedicated Redis connection configured। Laravel Redis facade eval signature অনুসারে key count ১। নিচে plain Redis string key ব্যবহার, Cache facade serialization নয়। ৬০ request/৬০ seconds illustrative; Redis failure exception centralized handler-এ controlled policy দিতে হবে।
use Illuminate\Support\Facades\Redis;
$script = <<<'LUA'
local count = redis.call('INCR', KEYS[1])
if count == 1 then
redis.call('EXPIRE', KEYS[1], ARGV[1])
end
return count
LUA;
$key = "rate:v1:user:{$userId}:search";
$count = (int) Redis::connection()->eval($script, 1, $key, 60);
if ($count > 60) {
return response()->json(['message' => 'অনুরোধের সীমা অতিক্রম হয়েছে'], 429);
}
// Continue the bounded search operation.সুবিধা, সীমাবদ্ধতা ও trade-off
Atomic script short race-free local decision দেয়, তবে network dependency এবং Redis availability যোগ হয়। Long script অন্য work block করতে পারে; bounded O(1) command রাখুন। Fixed window boundary burst এখনও আছে। Token bucket script smoother policy দিতে পারে, কিন্তু server/client clock choice ও state math পরীক্ষা করতে হবে। Multi-key script Redis Cluster-এ same slot constraint মানে।
সাধারণ ভুল
- GET তারপর SET দিয়ে atomic counter দাবি করা।
- INCR ও EXPIRE আলাদা করে permanent leaked counter রাখা।
- Redis failover-এ strict no-overrun guarantee বলা।
আরও একটু গভীরে
Budget reservation শেষে operation fail হলে token ফেরত দেবেন কি না policy-dependent; blind decrement duplicate refund বানাতে পারে। Paid API quotas-এ usage record, billing reconciliation ও idempotent reservation আলাদা design। Cross-region central counter latency বাড়ায়; regional budget ভাগ করলে unused allocation ও temporary imbalance থাকে। Wall-clock windows clock skew-এর প্রভাব পেতে পারে; consistent time source ও boundary tests দরকার।
মনে রাখুন
এক shared store থাকলেই check-and-update atomic হয় না। Script boundary ও expiry একসঙ্গে রাখুন। Atomic execution, durable accounting ও globally consistent budget তিনটি আলাদা requirement।
নিজে চেষ্টা করুন
দশ concurrent client দিয়ে limit-এর শেষ পাঁচটি allowance claim করুন। Global allowed count পরীক্ষা করুন। Redis restart/failover-এ কী guarantee হারায় এবং সেই risk storefront search বনাম billing quota-তে কেন আলাদা লিখুন।
নিজের বোঝা যাচাই করুন
1. Script atomic মানে durable?
না। Interleaving না হওয়া execution property। Persistence ও replication failover-এর durability guarantee আলাদা।
2. প্রথম request expiry কেন?
Counter-এর lifetime bounded করে এবং increment-expiry crash gap এড়ায়। এটি rolling first-hit window, calendar boundary নয়।
3. Rejected request count বাড়ে?
এই example-এ বাড়ে। অন্য policy চাইলে script ও abuse semantics বদলাতে হবে; behavior client contract-এ লিখুন।
4. Cross-region strict limit cheap?
Coordination latency ও availability trade-off আছে। Regional split budget low latency দেয়, কিন্তু utilization ও rebalance complexity যোগ করে।
5. Cluster-এ বহু key?
এক script-এর keys same hash slot requirement মানতে হয়। Hash tag ব্যবহার করা যায়, তবে hot partition risk বিবেচনা করুন।
আরও পড়ুন
এই পাঠের কুইজ
এই পাঠ থেকে ২টি প্রশ্ন। উত্তর দিয়ে ব্যাখ্যা দেখুন; অধ্যায়ের কুইজ আলাদাভাবে দিতে হবে।
এই পাঠের ২টি প্রশ্নের উত্তর দিন। পাস করতে দুটিই সঠিক হতে হবে।
জমা দেওয়ার পর সঠিক উত্তর ও ব্যাখ্যা দেখবেন। ফলাফল এই ব্রাউজারে সংরক্ষিত থাকবে। অনুশীলনের ফল অধ্যায় সম্পন্নের হিসাবে যোগ হয় না।
অগ্রগতি এই ব্রাউজারে সংরক্ষিত থাকে। শুধু পাঠ খুললে সম্পন্ন হয় না।