সার্ভারহীনের জন্য একটি ডাটাবেস ডিজাইন করা, আমাদের মনের সবচেয়ে বড় চ্যালেঞ্জ ছিল এমন একটি পরিকাঠামো তৈরি করা যা লাভজনক উপায়ে প্রতি অনুরোধ মূল্যকে সমর্থন করে। আমরা বিশ্বাস করি Upstash এটি অর্জন করেছে। আমরা পণ্যটি চালু করার পরে, আমরা দেখেছি যে আরেকটি বড় চ্যালেঞ্জ ছিল:ডেটাবেস সংযোগ!
আপনি জানেন যে, সার্ভারহীন ফাংশন 0 থেকে অসীম পর্যন্ত স্কেল করে। এর মানে হল যখন আপনার ফাংশনগুলি প্রচুর ট্রাফিক পায়, ক্লাউড প্রদানকারী সমান্তরালভাবে নতুন কন্টেইনার (ল্যাম্বডা ফাংশন) তৈরি করে এবং আপনার ব্যাকএন্ডকে স্কেল করে। আপনি যদি ফাংশনের মধ্যে একটি নতুন ডাটাবেস সংযোগ তৈরি করেন তবে আপনি দ্রুত আপনার ডাটাবেসের সংযোগ সীমাতে পৌঁছাতে পারবেন।
আপনি যদি ল্যাম্বডা ফাংশনগুলির বাইরে সংযোগটি ক্যাশে করার চেষ্টা করেন তবে অন্য সমস্যা দেখা দেয়। যখন AWS আপনার Lambda ফাংশন হিমায়িত করে, এটি সংযোগ বন্ধ করে না। সুতরাং আপনি অনেক নিষ্ক্রিয়/জম্বি সংযোগের সাথে শেষ হতে পারেন যা এখনও হুমকির সম্মুখীন হতে পারে।
এই সমস্যাটি রেডিসের জন্য নির্দিষ্ট নয়, এটি সমস্ত ডাটাবেসের ক্ষেত্রে প্রযোজ্য যা TCP সংযোগের উপর নির্ভর করে (Mysql, Postgre, MongoDB ইত্যাদি)। আপনি দেখতে পাচ্ছেন যে সার্ভারহীন সম্প্রদায় সার্ভারহীন-mysql এর মতো সমাধান তৈরি করছে। এগুলি ক্লায়েন্ট-সাইড সমাধান। Upstash হিসাবে, আমাদের সার্ভার-সাইড বাস্তবায়ন এবং বজায় রাখার সুবিধা আছে। তাই আমরা সংযোগগুলি পর্যবেক্ষণ করে এবং নিষ্ক্রিয়গুলিকে উচ্ছেদ করে সমস্যাটি প্রশমিত করার সিদ্ধান্ত নিয়েছি। সুতরাং এখানে অ্যালগরিদম:সর্বাধিক-সমবর্তী-সংযোগ হিসাবে, আমাদের একটি ডাটাবেসের জন্য দুটি সীমা রয়েছে, সফ্ট-সীমা এবং হার্ড-সীমা। যখন একটি ডাটাবেস নরম-সীমায় পৌঁছে তখন আমরা নিষ্ক্রিয় সংযোগগুলি বন্ধ করতে শুরু করি। হার্ড-লিমিটে না পৌঁছানো পর্যন্ত আমরা নতুন সংযোগের অনুরোধ গ্রহণ করতে থাকি। যদি ডাটাবেস কঠিন সীমাতে পৌঁছায় তাহলে আমরা নতুন সংযোগ প্রত্যাখ্যান করতে শুরু করি।
সংযোগ উচ্ছেদ অ্যালগরিদম
if( current_connection_count < SOFT_LIMIT ) {
ACCEPT_NEW_CONNECTIONS
}
if( current_connection_count > SOFT_LIMIT && current_connection_count < HARD_LIMIT ) {
ACCEPT_NEW_CONNECTIONS
START_EVICTING_IDLE_CONNECTIONS
}
if( current_connection_count > HARD_LIMIT ) {
REJECT_NEW_CONNECTIONS
}
মনে রাখবেন যে আপস্ট্যাশ ডক্সে তালিকাভুক্ত সর্বাধিক সমবর্তী সংযোগ সীমা হল নরম সীমা৷
ক্ষণস্থায়ী সংযোগ
উপরের অ্যালগরিদমটি স্থাপন করার পরে, আমরা সমস্ত অঞ্চলে প্রত্যাখ্যান করা সংযোগের সংখ্যায় একটি দুর্দান্ত হ্রাস দেখেছি। কিন্তু তারপরও যদি আপনি নিরাপদে থাকতে চান, তাহলে আপনি আপনার পক্ষেও সমস্যাটি সমাধান করতে পারেন। সংযোগ পুনঃব্যবহারের পরিবর্তে, আপনি ফাংশনের ভিতরে Redis কানেকশন খুলতে পারেন কিন্তু যখনই আপনি Redis-এর সাথে নিচের মত করে সম্পন্ন করেন তখন সেগুলি বন্ধও করতে পারেন:
exports.handler = async (event) => {
const client = new Redis(process.env.REDIS_URL);
/*
do stuff with redis
*/
await client.quit();
/*
do other stuff
*/
return {
response: "response",
};
};
উপরের কোডটি আপনাকে সমসাময়িক সংযোগের সংখ্যা কমাতে সাহায্য করে। লোকেরা নতুন সংযোগের লেটেন্সি ওভারহেড সম্পর্কে জিজ্ঞাসা করে। রেডিস সংযোগগুলি খুব হালকা বলে পরিচিত৷
৷রিডিস সংযোগগুলি কি সত্যিই হালকা?
Redis সংযোগগুলি কতটা হালকা তা দেখতে আমরা একটি বেঞ্চমার্ক পরীক্ষা চালিয়েছি। এই পরীক্ষায় আমরা দুটি পদ্ধতির লেটেন্সি সংখ্যার তুলনা করি:
1- ক্ষণস্থায়ী সংযোগ:আমরা সংযোগটি পুনরায় ব্যবহার করি না। পরিবর্তে আমরা প্রতিটি কমান্ডের জন্য একটি নতুন সংযোগ তৈরি করি এবং অবিলম্বে সংযোগ বন্ধ করি। আমরা ক্লায়েন্ট তৈরির লেটেন্সি, ping() এবং client.quit() একসাথে রেকর্ড করি। benchEphemeral()
দেখুন নীচের কোড বিভাগে পদ্ধতি।
2- সংযোগ পুনরায় ব্যবহার করুন:আমরা একবার একটি সংযোগ তৈরি করি এবং সমস্ত কমান্ডের জন্য একই সংযোগ পুনরায় ব্যবহার করি। এখানে, আমরা ping()
এর লেটেন্সি রেকর্ড করি অপারেশন. benchReuse()
দেখুন নিচের পদ্ধতি।
async function benchReuse() {
const client = new Redis(options);
const hist = hdr.build();
for (let index = 0; index < total; index++) {
let start = performance.now() * 1000; // to μs
client.ping();
let end = performance.now() * 1000; // to μs
hist.recordValue(end - start);
await delay(10);
}
client.quit();
console.log(hist.outputPercentileDistribution(1, 1));
}
async function benchEphemeral() {
const hist = hdr.build();
for (let index = 0; index < total; index++) {
let start = performance.now() * 1000; // to μs
const client = new Redis(options);
client.ping();
client.quit();
let end = performance.now() * 1000; // to μs
hist.recordValue(end - start);
await delay(10);
}
console.log(hist.outputPercentileDistribution(1, 1));
}
রেপো দেখুন, যদি আপনি নিজেই বেঞ্চমার্ক চালাতে চান।
আমরা এই বেঞ্চমার্ক কোডটি AWS EU-WEST-1 অঞ্চলে দুটি ভিন্ন সেটআপে চালিয়েছি। প্রথম সেটআপ হল SAME ZONE যেখানে ক্লায়েন্ট এবং ডাটাবেস একই প্রাপ্যতা অঞ্চলে রয়েছে। দ্বিতীয় সেটআপটি হল INTER ZONE যেখানে ক্লায়েন্ট ডাটাবেসের চেয়ে ভিন্ন প্রাপ্যতা অঞ্চলে চলে। আমরা ডাটাবেস সার্ভার হিসাবে Upstash স্ট্যান্ডার্ড টাইপ ব্যবহার করেছি।
আমরা দেখেছি একটি নতুন সংযোগ তৈরি এবং বন্ধ করার ওভারহেড (ক্ষণস্থায়ী পদ্ধতি) মাত্র 75 মাইক্রোসেকেন্ড (99 তম শতাংশ)। ইন্টারজোন সেটআপে ওভারহেড খুব একই রকম (80 মাইক্রোসেকেন্ড)।
তারপরে আমরা AWS Lambda ফাংশনের ভিতরে একই পরীক্ষা পুনরাবৃত্তি করার সিদ্ধান্ত নিয়েছি। ফলাফল ভিন্ন ছিল. বিশেষ করে যখন আমরা Lambda ফাংশনের মেমরি কম (128Mb) সেট করি, তখন আমরা Redis সংযোগের বড় ওভারহেড দেখেছি। আমরা AWS Lambda ফাংশনের ভিতরে 6-7 msec পর্যন্ত লেটেন্সি ওভারহেড দেখেছি।
Redis সংযোগ সম্পর্কে আমাদের উপসংহার:
- সিপিইউ পাওয়ার একটি যুক্তিসঙ্গত পরিমাণ সহ একটি সিস্টেমে রেডিস সংযোগগুলি সত্যিই হালকা। এমনকি t2.micro এও।
- ডিফল্ট AWS Lambda কনফিগারেশন সহ CPU পাওয়ার খুবই দুর্বল, যা Lambda ফাংশনের মোট এক্সিকিউশন সময়ের সাপেক্ষে TCP সংযোগের খরচ উল্লেখযোগ্যভাবে বাড়িয়ে দেয়।
- আপনি যদি ডিফল্ট/ন্যূনতম মেমরির সাথে Lambda ফাংশন ব্যবহার করেন, তাহলে আপনি ফাংশনের বাইরে রেডিস সংযোগটি আরও ভালভাবে ক্যাশে করবেন।
হিমায়িত ধারক => জম্বি সংযোগ
কিছু AWS Lambda সেটআপে সংযোগের উল্লেখযোগ্য ওভারহেড থাকতে পারে তা উপলব্ধি করার পরে, আমরা reusing connection
উপর আরও পরীক্ষা করার সিদ্ধান্ত নিয়েছি। AWS Lambda-তে। আমরা আরেকটি সমস্যা সনাক্ত করেছি। এটি একটি এজ কেস ছিল কেউ এখনও রিপোর্ট করেনি৷
এটি কীভাবে ঘটে তা এখানে টাইমলাইন:
STEP1 - টাইমার-0 সেকেন্ড: আমরা ল্যাম্বডা ফাংশনের বাইরে সংযোগ ক্যাশে করে একটি অনুরোধ পাঠাই।
if (typeof client === "undefined") {
var client = new Redis("REDIS_URL");
}
module.exports.hello = async (event) => {
let response = await client.get("foo");
return { response: response + "-" + time };
};
STEP2 - টাইমার-5 সেকেন্ড: AWS অল্প সময়ের পরে কন্টেইনারটি হিমায়িত করে।
STEP3 - সময়-60sec: নিষ্ক্রিয় সংযোগের জন্য Upstash-এর 60 সেকেন্ডের সময়সীমা রয়েছে। সুতরাং এটি সংযোগটি মেরে ফেলে, কিন্তু ক্লায়েন্টের কাছ থেকে ACK পেতে পারে না কারণ এটি হিমায়িত হয়। সুতরাং সার্ভার সংযোগ FIN_WAIT_2 অবস্থায় যায়৷
৷STEP4 - সময়-90 সেকেন্ড: Upstash সার্ভার FIN_WAIT_2 অবস্থা থেকে প্রস্থান করে সংযোগটিকে সম্পূর্ণরূপে মেরে ফেলে৷
৷STEP5 - সময়-95 সেকেন্ড: ক্লায়েন্ট একই অনুরোধ পাঠায় এবং ETIMEDOUT ব্যতিক্রম পায়। কারণ ক্লায়েন্ট ধরে নেয় কানেকশন খোলা কিন্তু তা নয়। 🤦🏻 🤦🏻 🤦🏻
STEP6 - সময়-396sec: শেষ অনুরোধের 5 মিনিট পরে, AWS কন্টেইনারটিকে সম্পূর্ণভাবে মেরে ফেলে।
STEP7 - সময়-400sec: ক্লায়েন্ট এইবার একই অনুরোধ পাঠায় এটি ভাল কাজ করে কারণ কন্টেইনারটি স্ক্র্যাচ থেকে তৈরি করা হয়েছে তাই আরম্ভ করার পদক্ষেপটি এড়িয়ে যাওয়া হয় না। একটি নতুন সংযোগ তৈরি হয়েছে৷
৷আপনি উপরে দেখতে পাচ্ছেন, AWS একটি ধারক গলায় এবং সংযোগটি পুনরায় ব্যবহার করে। কিন্তু সার্ভারের দিক থেকে সংযোগ বন্ধ করা হয়েছে এবং ফাংশনটি হিমায়িত হওয়ায় এটি যোগাযোগ করা যায়নি। সুতরাং Upstash একটি নিষ্ক্রিয় সংযোগ উচ্ছেদ করা এবং AWS একটি নিষ্ক্রিয় ফাংশন পরিচালনার মধ্যে একটি সিঙ্ক্রোনাইজেশন সমস্যা রয়েছে৷ তাই AWS একটি ফাংশন বন্ধ করার পরেই যদি আমরা একটি নিষ্ক্রিয় সংযোগ মেরে ফেলি তাহলে কোনো সমস্যা হবে না৷
আমরা Upstash কানেকশন টাইমআউট 310 সেকেন্ডে পরিবর্তন করেছি এই অনুমান করে যে AWS 300 সেকেন্ডের মধ্যে একটি নিষ্ক্রিয় ফাংশন বন্ধ করে দেয়। এই পরিবর্তনের পরে, সমস্যা অদৃশ্য হয়ে গেছে। এখানে সমস্যা হল AWS স্বচ্ছ নয় যখন তারা নিষ্ক্রিয় ফাংশন বন্ধ করে দেয়। তাই আমাদের পরীক্ষা চালিয়ে যেতে হবে এবং সমস্যাটি আবার ঘটে কিনা তা সনাক্ত করার চেষ্টা করতে হবে।
এই সমস্যাটি সার্ভারলেস-মাইএসকিউএল লাইব্রেরিতে দেখা সমস্যার মতোই। মন্তব্যগুলিতে, ETIMEDOUT ব্যতিক্রমে অনুরোধটি পুনরায় চেষ্টা করার পরামর্শ দেওয়া হয়েছে৷ কিন্তু পুনরায় চেষ্টা করার দুটি অসুবিধা আছে। প্রথমে আপনি একটি লেখার অনুরোধ পুনরায় চেষ্টা করতে পারেন যা প্রক্রিয়া করা হয়েছে এবং একটি বাস্তব নেটওয়ার্ক সমস্যার সাথে সময় শেষ হয়ে গেছে। দ্বিতীয় সমস্যা হল ব্যর্থ অনুরোধের অতিরিক্ত বিলম্ব।
GraphQL খুব সাহায্য করে
সংযোগহীন এপিআই থাকার জন্য সংযোগ সমস্যা থেকে মুক্তি পাওয়ার একটি উপায়। Upstash Redis প্রোটোকল ছাড়াও GraphQL API সমর্থন করে। GraphQL হল HTTP ভিত্তিক তাই এতে সংযোগ সীমার সমস্যা নেই। সমর্থিত কমান্ডের জন্য ডক্স চেক করুন। সতর্ক থাকুন যে GraphQL API এর Redis প্রোটোকলের উপরে একটি লেটেন্সি ওভারহেড (প্রায় 5msec) আছে৷
উপসংহার
আমরা সার্ভারহীন অ্যাপ্লিকেশনের জন্য একটি মসৃণ অভিজ্ঞতার জন্য Upstash ডাটাবেস কাস্টমাইজ করি। আমাদের নতুন সার্ভার-সাইড অ্যালগরিদম নিষ্ক্রিয় সংযোগগুলি সরিয়ে দেয় যা AWS Lambda প্রচুর পরিমাণে তৈরি করে। আপনি Lambda ফাংশনের ভিতরে আপনার Redis ক্লায়েন্ট খোলা/বন্ধ করে সংযোগের সংখ্যা কমিয়ে আনতে পারেন কিন্তু আপনার ফাংশন মেমরি 1GB-এর কম হলে এটির ওভারহেড লেটেন্সি থাকতে পারে।
উপসংহার হিসাবে, সার্ভারহীন ব্যবহারের ক্ষেত্রে আমাদের সুপারিশ:
- যদি আপনার ব্যবহারের ক্ষেত্রে লেটেন্সি সংবেদনশীল হয় (যেমন আপনার জন্য 6msec বড়) তাহলে Redis ক্লায়েন্ট পুনরায় ব্যবহার করুন।
- যদি আপনি অনেক বেশি সংখ্যক সমসাময়িক ক্লায়েন্ট (1000 এর বেশি) অনুভব করেন তাহলে Redis ক্লায়েন্ট পুনরায় ব্যবহার করুন।
- যদি আপনার ব্যবহারের ক্ষেত্রে লেটেন্সি সংবেদনশীল না হয় তাহলে ফাংশনের ভিতরে Redis ক্লায়েন্ট খুলুন/বন্ধ করুন।
- যদি আপনার ফাংশনে কমপক্ষে 1GB মেমরি থাকে তাহলে ফাংশনের ভিতরে Redis ক্লায়েন্ট খুলুন/বন্ধ করুন।
টুইটার বা ডিসকর্ডে আপনার প্রতিক্রিয়া আমাদের জানান।