Next.js API রুটে রেস কন্ডিশন
একটি সিঙ্গেল-ইউজ (একবার ব্যবহারযোগ্য) কুপন কেবল একবারই কাজ করার কথা।
কিন্তু এই পরিস্থিতিতে, একই সময়ে ৩০ জন মানুষ এটি ব্যবহার করতে পারে।
এটি ঘটে একটি Time-of-Check Time-of-Use (TOCTOU) race condition-এর কারণে।
ত্রুটিটি দুটি ধাপে ঘটে:
- ধাপ ১: সার্ভার ব্যবহৃত সংখ্যা (used count) পরীক্ষা করার জন্য ডাটাবেস থেকে তথ্য পড়ে।
- ধাপ ২: সার্ভার সেই সংখ্যাটি বাড়ানোর জন্য দ্বিতীয় একটি কমান্ড পাঠায়।
আপনি যদি একসাথে অনেকগুলো রিকোয়েস্ট পাঠান, তবে সার্ভার এই দুটি ধাপের মাঝখানের সময়েই সব রিকোয়েস্ট হ্যান্ডেল করে ফেলে।
অনেকগুলো রিকোয়েস্ট সংখ্যাটি পড়ার সময় সেটি তখনও শূন্য থাকে। ফলে সবগুলোই চেক পাস করে ফেলে। সবগুলোই ডিসকাউন্ট প্রয়োগ করে এবং সবগুলোই কাউন্টারটি বাড়িয়ে দেয়।
ফলাফল? একজনের জন্য নির্ধারিত একটি কুপন ৩০ বার ব্যবহার করা হয়।
কোড রিভিউ করার সময় এটি সহজেই এড়িয়ে যাওয়া সম্ভব। লাইন বাই লাইন পড়লে লজিকটি একদম নিখুঁত মনে হয়। কিন্তু যখন আপনি গতি (speed) এবং কনকারেন্সি (concurrency)-র কথা ভাববেন, তখনই কেবল এই ত্রুটিটি ধরা পড়ে।
এটি কীভাবে সমাধান করবেন:
আলাদা আলাদা read এবং write কমান্ড ব্যবহার করা বন্ধ করুন। পরিবর্তে, একটি single atomic operation ব্যবহার করুন।
Prisma-তে, আপনি WHERE clause-এ একটি কন্ডিশনসহ updateMany ব্যবহার করতে পারেন:
• ডাটাবেস একটি মাত্র ধাপে কন্ডিশনটি পরীক্ষা করে এবং আপডেট সম্পন্ন করে। • যদি সংখ্যাটি ইতিমধ্যে লিমিটে পৌঁছে যায়, তবে আপডেটটি সাথে সাথে ব্যর্থ হবে। • চেক এবং রাইটের মাঝখানে অন্য কোনো রিকোয়েস্ট ঢুকে পড়ার সুযোগ পাবে না।
আরেকটি বিকল্প হলো database transaction ব্যবহার করা। এটি ডাটাকে লক করে দেয় যাতে আপনার কাজ শেষ না হওয়া পর্যন্ত অন্য কোনো রিকোয়েস্ট এটি স্পর্শ করতে না পারে।
SQLite-এর জন্য, একটি সিঙ্গেল update কমান্ড হলো সবচেয়ে নির্ভরযোগ্য পদ্ধতি।
নিজেকে সবসময় প্রশ্ন করুন: যদি দুটি রিকোয়েস্ট একই মিলিসেকেন্ডে এই লজিকের ওপর আঘাত করে, তবে কী ঘটবে?
উৎস: https://dev.to/oopssec-store/racing-a-nextjs-api-route-coupon-abuse-with-prisma-and-sqlite-3gma