One self-hosted console to run your entire business — commerce, ERP, HRM, CRM & manufacturing

হাই-ভলিউম সিস্টেমের observability: কাজে লাগার মতো log, metric আর trace

একটি request ID, OpenSearch-এ স্ট্রাকচার্ড log, চার golden signal, sampling করা trace আর ব্যবহারকারীর ক্ষতির সঙ্গে বাঁধা alert: ধীর লেয়ার মিনিটে খুঁজে পাওয়ার ছোট সেট, সঙ্গে load test আর ইভেন্টের দিনে চোখ রাখার উপায়।

Author

Anichur Rahaman

6 দিন আগে12 min read1 views
হাই-ভলিউম সিস্টেমের observability: কাজে লাগার মতো log, metric আর trace

"গ্রাহকরা বলছেন checkout আটকে যাচ্ছে।" release-এর সকাল ১০টা ১ মিনিটে একটি টিকিটিং সাইটের platform লিডের কাছে সাপোর্ট ডেস্কের এই বার্তা এসে পৌঁছাল। বিক্রি শুরু হয়েছে ১০টায়, প্রায় এক হাজার মানুষ একই মিনিটে ক্লিক করেছে, আর পরিকল্পনা বলছিল প্রতি সেকেন্ডে ৪৪০ request। dashboard-এ CPU ৪০%, লাল কিছুই নেই।

কোন লেয়ারটা ধীর? একটা route, একটা নোড, নাকি একজন গ্রাহক? কনসোল আর আন্দাজের ভরসায় থাকলে পরের দশ মিনিট কেটে যায় তর্কে। একটি request ID আর বাছাই করা কয়েকটি সংখ্যা থাকলে কাটে একটাই সার্চে। (দৃশ্যটি কল্পিত উদাহরণ, কোনো বাস্তব ঘটনা নয়।)

এর জন্য বড় platform লাগে না। লাগে একটি request ID, স্ট্রাকচার্ড log, চার golden signal আর ব্যবহারকারীর ক্ষতির সঙ্গে বাঁধা alert। একটি নির্ধারিত স্পাইকের জন্য, যেখানে একই মিনিটে প্রায় এক হাজার ব্যবহারকারী আসবে, আমি একটি platform তৈরি করেছিলাম। server ঠিকই ছিল; প্রায় বিপদে ফেলেছিল এটাই যে কোন লেয়ার ধীর, কিংবা ভয়ের সংখ্যাটা সত্যি কি না, তা কেউ দ্রুত বলতে পারছিল না। এই লেখায় সেই প্রশ্নগুলোর উত্তর দেওয়া ছোট সেটটা গড়ে তুলব, তারপর দেখাব তাই দিয়ে load test আর event-এর দিন কীভাবে চোখে রাখা যায়।

এটি "হাই-ভলিউম system-এর ইঞ্জিনিয়ারিং" সিরিজের পাঁচ পর্বের চতুর্থ পর্ব। প্রথম পর্বে আছে আসলে কী স্কেল করে, দ্বিতীয় পর্বে queue আর worker, তৃতীয় পর্বে data টিয়ার। এই পর্বের বিষয়: পুরো ব্যবস্থাটা চোখে দেখা।

এক অনুচ্ছেদে observability

Monitoring বলে দেয় কিছু একটা গড়বড় হয়েছে। Observability আপনাকে সুযোগ দেয় নতুন কোড না চালিয়েই জিজ্ঞেস করতে: কেন? এর ভিত্তি তিন ধরনের data, যাকে সিগন্যাল বলা হয়:

  • Log: প্রতিটি ঘটনার জন্য একটি রেকর্ড, বিস্তারিত সহ। "এই request-এর সঙ্গে ঠিক কী ঘটল?" প্রশ্নের জন্য সবচেয়ে ভালো।
  • Metric: সময়ের সঙ্গে নেওয়া সংখ্যা। "পরিস্থিতি কি খারাপ হচ্ছে, আর কত দ্রুত?" প্রশ্নের জন্য সবচেয়ে ভালো।
  • Trace: একটি request প্রতিটি সার্ভিসের ভেতর দিয়ে যে পথে গেল, প্রতি ধাপে কত সময় লাগল সহ। "সময়টা গেল কোথায়?" প্রশ্নের জন্য সবচেয়ে ভালো।

প্রথম দিনেই তিনটি লাগবে না, দামি platform-ও লাগবে না। দরকার শুধু তিনটির মধ্যে যোগসূত্র, যাতে লাল হয়ে ওঠা একটা metric থেকে একটা ধীর request-এর log আর trace-এ সরাসরি যাওয়া যায়। এই যোগসূত্রটাই request ID।

শুরু করুন এমন request ID দিয়ে, যা প্রতিটি ধাপ পেরিয়ে যায়

আমার জানা সবচেয়ে সস্তা উন্নতি হলো একটাই শনাক্তকারী, যা এজ থেকে শেষ queue-জব পর্যন্ত request-এর সঙ্গে চলে। আমি যে সেটআপ চালিয়েছি, সেখানে nginx আগত X-Request-Id হেডার মেনে নেয়, না থাকলে নিজে তৈরি করে, PHP-FPM-কে পাঠায়, আর দুজনেই নিজেদের log-এ সেটা লেখে। অ্যাপ্লিকেশন তারপর প্রতিটি log লাইনে ID যোগ করে এবং যে জব ডিসপ্যাচ করে তার payload-এও কপি করে দেয়। ফলে ব্যাকগ্রাউন্ড কাজ থেকে সেই ক্লিক পর্যন্ত ফিরে যাওয়া যায়, যে ক্লিক থেকে কাজটা এসেছিল।

একই ID response হেডারেও ফেরত দিন। কোনো গ্রাহক বা সাপোর্ট agent সমস্যা জানালে ওই একটি স্ট্রিং দিয়েই পুরো ঘটনা বের হয়ে আসে।

CDN, লোড ব্যালান্সার, nginx, PHP-FPM, অ্যাপ্লিকেশন ও queue জব পেরিয়ে একটি request ID ধরে OpenSearch-এ একটিমাত্র সার্চে পুরো যাত্রা দেখার ডায়াগ্রাম
প্রতিটি লেয়ার একই request ID লিখলে পাঁচটি আলাদা log ফাইল একটি টাইমলাইনে পরিণত হয়।

ID কোথায় কোথায় থাকতে হবে

লেয়ারকী করবেন
এজ / CDNআগত ID এগিয়ে দিন, না হলে পরের লেয়ারকে তৈরি করতে দিন
হোস্ট nginx ও কন্টেইনার nginxআগত ID ব্যবহার করুন, না থাকলে তৈরি করুন, access log-এ লিখুন
PHP-FPM ও appহেডার থেকে পড়ুন, প্রতিটি log লাইনে শেয়ার্ড কনটেক্সট হিসেবে যোগ করুন
queue জবজবের payload-এ রাখুন, জব চলার সময় ফিরিয়ে আনুন
বাইরে যাওয়া HTTP কলসামনে পাঠিয়ে দিন, যাতে পার্টনার ও অভ্যন্তরীণ সার্ভিসও লিখে রাখতে পারে
Responseহেডার হিসেবে ফেরত দিন, যাতে সাপোর্ট এটি চেয়ে নিতে পারে

স্ট্রাকচার্ড log থেকে ELK বা OpenSearch স্ট্যাক

সাধারণ টেক্সট log মানুষ একটা একটা করে পড়ার জন্য লেখা। হাই-ভলিউমে আপনাকে সেগুলো খুঁজতে, গুনতে ও ছাঁকতে হয়, তাই চাই প্রতি লাইনে একটি JSON অবজেক্ট, নামসহ ফিল্ড নিয়ে। শুরুতে অল্প কয়েকটা স্থির ফিল্ডই যথেষ্ট।

ফিল্ডকেন এটি জায়গা পাওয়ার যোগ্য
timeক্রম নির্ধারণ আর metric-এর সঙ্গে মেলানো
request_idসব লেয়ারকে জোড়া দেওয়া সুতো
route বা path (query string ছাড়া)ধীর request কাঁচা URL দিয়ে নয়, endpoint ধরে গুছিয়ে দেখা
statusendpoint-ভিত্তিক এরর রেট
durationp95 আর p99 হিসাবের কাঁচামাল
upstream time"nginx ধীর" আর "PHP ধীর" আলাদা করে
user বা tenant id (অভ্যন্তরীণ ID, কখনো email নয়)সমস্যাটা কোনো একজন গ্রাহকের কি না, তা বোঝা

নামের কথা: ELK হলো তিনটি টুলের পুরোনো ডাকনাম: Elasticsearch, Logstash আর Kibana। পরে Elastic Stack-এ যুক্ত হয়েছে Beats নামের হালকা শিপার। OpenSearch শুরু হয় ২০২১ সালে, AWS-এর বানানো Elasticsearch ও Kibana-র Apache-লাইসেন্সড ফর্ক হিসেবে, আর ২০২৪ সালের সেপ্টেম্বরে এটি Linux Foundation-এর অধীন OpenSearch Software Foundation-এ চলে যায়। দুটি এখন আর হুবহু এক নয়, তবে log-এর কাজের ধারা একই: পাঠাও, index করো, খোঁজো, চার্ট বানাও। তাই মানুষ বলে "ELK-compatible"।

বড় cluster লাগে না। আমার সেটআপে ২ GB মেমরি আর ১ vCPU-র ছোট একটি OpenSearch নোডই একটি তীক্ষ্ণ স্পাইক সামলানো platform-এর log ধরে রেখেছিল। এটা একটি নির্দিষ্ট সেটআপ, বেঞ্চমার্ক নয়, তবে খরচটা যে ছোট, তা বোঝা যায়: প্রথম যে বিপর্যয়টা ঘণ্টার বদলে মিনিটে ধরা পড়বে, সেটার তুলনায় এটা কিছুই না।

যা log করবেন না

Log কপি হয়, index হয়, মাসের পর মাস জমা থাকে, তাই এটি data সুরক্ষার ঝুঁকি। অ্যাপ্লিকেশন থেকে বের হওয়ার আগেই password, token, কার্ডের তথ্য আর পুরো ব্যক্তিগত বিবরণ মুছে দিন। নাম, email বা ফোন নম্বরের বদলে অভ্যন্তরীণ ID লিখুন। Path থেকে query string বাদ দিন, কারণ সেখানে প্রায়ই token থাকে।

Retention আর index লাইফসাইকেল

দিন ধরে index ঘুরিয়ে স্বয়ংক্রিয় লাইফসাইকেল রাখলে ছোট নোডও সুস্থ থাকে: সাম্প্রতিক দিনগুলো খোঁজার জন্য খোলা থাকে, পুরোনো index সংকুচিত হয় বা সরে যায়, আর retention সীমা পেরোনোগুলো মুছে যায়। সীমাটা অনুমানে ঠিক করবেন না; গোপনীয়তা ও নিরাপত্তার দায়িত্বে যাঁরা আছেন, তাঁদের সঙ্গে বসে ঠিক করুন। বিস্তারিত log ত্রিশ দিন রাখা একটি প্রচলিত শুরু, তবে সঠিক উত্তর নির্ভর করে আপনার নিয়মকানুনের ওপর।

Metric: চার golden signal

Google-এর Site Reliability Engineering বইয়ের distributed system monitoring অধ্যায়ে চারটি সিগন্যালের কথা আছে, যা ব্যবহারকারী-মুখী প্রায় যেকোনো সার্ভিসকে ঢেকে ফেলে। মাত্র চারটি জিনিস মাপতে পারলে এগুলোই মাপুন।

সিগন্যালকোন প্রশ্নের উত্তর দেয়ওয়েব ও worker platform-এ কী মাপবেন
Latencyrequest-এ কত সময় লাগছে?প্রতি route-এ p50, p95, p99; ব্যর্থ request আলাদা করে দেখুন
Trafficচাহিদা কতটা?এজে প্রতি সেকেন্ডে request, প্রতি সেকেন্ডে ডিসপ্যাচ হওয়া জব
Errorsকত অংশ ব্যর্থ হচ্ছে?5xx রেট, timeout, ব্যর্থ জব
Saturationসবচেয়ে সংকীর্ণ রিসোর্সটা কতটা ভরা?FPM-এর ব্যস্ত worker, queue depth ও age, database connection, Redis মেমরি

দুটি সতর্কতা। প্রথমত, গড় ধরে কখনো alert বা পরিকল্পনা করবেন না: সুস্থ দেখানো গড় ভয়ংকর লেজ লুকিয়ে রাখে। server-রেন্ডারড একটি frontend-এ আমার চালানো এক load test-এ একটি প্রসেস প্রতি সেকেন্ডে মোটামুটি ২২০ থেকে ২৫০ request-এ সম্পৃক্ত হয়ে গিয়েছিল, p95 প্রায় ২৬০ ms থেকে লাফিয়ে প্রায় ৩ সেকেন্ডে উঠেছিল, অথচ হোস্টের কোর তখনো অলস। গড় কিছুক্ষণ গ্রহণযোগ্যই দেখাত। সত্যিটা বলেছে percentile।

দ্বিতীয়ত, বিপদ শুরু হয় saturation থেকে, আর সেটা প্রায় কখনোই CPU নয়। এই ধরনের platform-এ আমি প্রথমে দেখি:

  • PHP-FPM-এর ব্যস্ত worker, পুলের সর্বোচ্চ সীমার তুলনায়। সীমা ছুঁলে request nginx-এ সারি দিয়ে দাঁড়ায়।
  • প্রতিটি queue-র queue depth আর queue age। Depth বলে কতটা অপেক্ষায়; age বলে আপনি ইতিমধ্যে কতটা পিছিয়ে।
  • database connection আর write throughput, শুধু CPU নয়।
  • ভূমিকা ধরে (cache, session, queue) Redis মেমরি আর eviction।
  • কন্টেইনারপ্রতি মেমরি, কারণ out-of-memory kill বাইরে থেকে দেখতে এলোমেলো এররের মতো।

OpenTelemetry দিয়ে tracing

Log আর metric বলে request ধীর ছিল। Trace বলে সেটা nginx-এ ৪০ ms, PHP-তে ৯০ ms, একটি query-র জন্য অপেক্ষায় ১.২ সেকেন্ড আর Redis-এ ৩০ ms কাটিয়েছে। frontend টিয়ার, backend টিয়ার আর worker আছে এমন system-এ দোষী ধাপটা ধরার এটাই সবচেয়ে দ্রুত উপায়।

এই data তৈরির vendor-নিরপেক্ষ স্ট্যান্ডার্ড হলো OpenTelemetry। এর specification status পেজ অনুযায়ী tracing ও logs সিগন্যাল stable, metrics-এর API, protocol ও data model stable, তবে আলাদা আলাদা ভাষার SDK-এর পরিপক্বতা এক নয়। PHP প্রজেক্ট trace, metric ও log তিনটিই পাওয়া যায় বলে ডকুমেন্ট করেছে, সঙ্গে স্বয়ংক্রিয় instrumentation-এর জন্য ঐচ্ছিক একটি extension। কোনো সিদ্ধান্ত নেওয়ার আগে আপনি যে লাইব্রেরিগুলো ব্যবহার করবেন, তাদের নিজস্ব অবস্থা যাচাই করে নিন।

আমার পরামর্শ, এই ক্রমে শুরু করুন। আগে নিশ্চিত করুন request ID আছে। তারপর শুধু critical path trace করুন, যেমন checkout বা সাবমিশন, আর তাতে sampling রাখুন: হাই-ভলিউমে প্রতিটি trace রাখতে যা খরচ হয়, শেখা যায় তার চেয়ে কম। এরর আর ধীর request-এর সব trace রাখুন, বাকিদের সামান্য অংশ। Trace ID-ও log লাইনে request ID-র পাশে বসিয়ে দিন, যাতে দুই টুল একে অপরের দিকে link করতে পারে।

SLO আর অর্থপূর্ণ alert

একটি alert-এর একটাই প্রশ্নের উত্তর দেওয়া উচিত: কাউকে কি এখনই কিছু করতে হবে? CPU ৮৫% হলে সাধারণত না। "২% ব্যবহারকারীর checkout ব্যর্থ হচ্ছে" হলে সব সময় হ্যাঁ। Service level objective এটাকে মূর্ত করে: যেমন "৩০ দিনে ৯৯% checkout request ২ সেকেন্ডের মধ্যে সফল হবে", আর বাকিটুকুর জন্য একটি error budget।

যার জন্য মানুষকে ডাকবেন (page)যার জন্য টিকিট বা চ্যাট যথেষ্ট
Checkout error rate SLO burn threshold ছাড়িয়েছেডিস্ক ধীরে ধীরে ভরছে
critical route-এর p95 কয়েক মিনিট ধরে লক্ষ্যের ওপরেএকটি নোডে CPU বেশি, ব্যবহারকারী অপ্রভাবিত
submissions queue-র age বাড়ছেএকটি জব retry করে পাস করেছে
ব্যর্থ জব দ্রুত বাড়ছেতিন সপ্তাহ পরে সার্টিফিকেটের মেয়াদ শেষ

একটা হিসাব দেখলেই বোঝা যায় কেন। ধরুন ৩০ দিনে checkout-এ আসে ৩০,০০,০০০ request, আর লক্ষ্য ৯৯% সাফল্য; তাহলে error budget ৩০,০০০টি ব্যর্থ বা অতি ধীর request। প্রতি সেকেন্ডে ৪৪০ request হারে দশ মিনিটের স্পাইকে আপনি সামলাচ্ছেন ২,৬৪,০০০ request। তার ২% ব্যর্থ হলে ব্যর্থ হয় ৫,২৮০টি: পুরো মাসের budget-এর প্রায় ১৮% দশ মিনিটেই শেষ। CPU যা-ই বলুক, এটা page করার মতো ঘটনা। (কল্পিত সংখ্যা।)

অকারণে কাউকে জাগিয়ে তোলা প্রতিটি alert টিমকে শেখায় পরেরটা উপেক্ষা করতে। প্রতিটি event-এর পর alert পর্যালোচনা করুন, গোলমেলেগুলো মুছুন বা নামিয়ে দিন।

event-ডের dashboard

নির্ধারিত স্পাইকের জন্য আমি একটাই screen বানাই। ওপরে critical route-এর golden signal, নিচে saturation-এর সংখ্যা, পাশে queue depth আর age, আর লক্ষ্য request রেটের একটি রেখা। আর কিছু না। যে dashboard স্ক্রল করতে হয়, চূড়ান্ত মিনিটে কেউ তা পড়বে না।

latency, traffic, errors ও saturation panel এবং queue depth ও age, database connection ও Redis মেমরি-সহ event-ডের dashboard-এর ওয়্যারফ্রেম
চূড়ান্ত মিনিটের জন্য একটি screen: আগে চার golden signal, তারপর যে রিসোর্সগুলো ফুরিয়ে যায়।

প্রতিটি চার্টে deploy marker বসান। "রহস্যময় regression"-এর অর্ধেকই দশ মিনিট আগে নামা কোনো release।

Load test পর্যবেক্ষণ

Load test আপনার পাওয়া সবচেয়ে সস্তা রিহার্সাল, আর ভেতরটা দেখতে না পারলে সেটা বৃথা। একটি URL-এ আঘাত না করে আগের কোনো পিকের আসল request মিশ্রণ রিপ্লে করুন। আমার test-এ আমি k6 ব্যবহার করেছি abort threshold-সহ: p95 ৩ সেকেন্ড ছাড়ালে, কিংবা যেকোনো 5xx বা timeout হলে থামিয়ে দাও। ছোট একটি guard স্ক্রিপ্ট CPU সীমা ছুঁলেই রান বন্ধ করে দিত, যাতে test কখনো আউটেজ হয়ে না ওঠে।

  1. প্রথম রানের আগেই abort threshold ঠিক করে লিখে রাখুন।
  2. event-এর দিনে যে dashboard ব্যবহার করবেন, সেটাই চালান; আলাদা test-ভিউ নয়।
  3. test-এর traffic-এ একটি হেডার বসান, যাতে log-এ আলাদা করে ছাঁকা যায়।
  4. নিজের metric cloud প্রোভাইডারের metric-এর সঙ্গে মেলান। আমার রানগুলোতে কয়েক শতাংশের মধ্যে মিলেছে; না মিললে একটা ভুল আছে।
  5. প্রথম যে রিসোর্স সম্পৃক্ত হয় তা খুঁজে ঠিক করুন, তারপর লক্ষ্য রেটে আবার চালান।
  6. ফলাফল কোডের পাশেই রাখুন, যাতে পরের event একটি বেসলাইন থেকে শুরু হয়।

ভয়ের alert-এ হাত দেওয়ার আগে যাচাই করুন

চাপের মুখে যা লাল, তার ওপরই ঝাঁপিয়ে পড়তে ইচ্ছে করে। আগে যাচাই করুন। কিছু কনসোল কন্টেইনারের নাম ভুল দেখায়, আর "100% CPU" alert আপনার রিস্টার্ট করতে যাওয়া প্রসেস থেকে ভিন্ন কোনো প্রসেসের দিকে ইঙ্গিত করতে পারে। আসল প্রসেস লিস্ট দেখুন, দ্বিতীয় সূত্রের সঙ্গে মিলিয়ে নিন, তারপর সিদ্ধান্ত নিন।

ফ্লোচার্ট: alert বাজল; ব্যবহারকারী-মুখী কোনো সিগন্যাল সায় না দিলে আসল প্রসেস ও দ্বিতীয় সূত্র যাচাই করুন; সায় দিলে গত ১৫ মিনিটে deploy হয়ে থাকলে রোলব্যাক, নইলে প্রথম সম্পৃক্ত রিসোর্স খুঁজে ঠিক করে dashboard আবার দেখুন
লাল alert এরপর কোথায় যায়: কাজের বড় অংশ হলো ঠিক করা সেটা সত্যি কি না।

নিজের ব্যাকগ্রাউন্ড কাজের ক্ষেত্রেও একই কথা। Health check আর scheduler-ও রিসোর্স খায়। একবার দেখেছি, প্রতিবার একটি প্রসেস চালু করা health check লোডের মধ্যে অনাথ প্রসেস রেখে গিয়ে একটি database কন্টেইনারকে অস্থির করে তুলেছিল। monitoring-এর নিজের metric রাখা অতিসতর্কতা নয়।

আবার ১০টা ১ মিনিটে ফিরি। এই ব্যবস্থা থাকলে সাপোর্টের মেসেজ আসে request ID সহ। একটি সার্চেই দেখা যায় nginx PHP-র জন্য ১.৩১ সেকেন্ড অপেক্ষা করেছে, আর app একই request-এ একটি ধীর query log করেছে। dashboard-ও সায় দেয়: ৬০টির মধ্যে ৪৮টি FPM worker ব্যস্ত, p95 বাড়ছে। একমাত্র লাল CPU alert-টা অন্য একটি কন্টেইনারের, তাই কেউ কিছু রিস্টার্ট করে না। লিড কারণ পায় দশ মিনিটের বদলে প্রায় দুই মিনিটে, আর সমাধান যায় ঠিক জায়গায়। (এটিও কল্পিত।)

এই সিরিজের শেষ পর্বে থাকছে সচল থাকার অন্য অর্ধেক: এজ সুরক্ষিত করা আর zero-downtime-এ পরিবর্তন deploy করা।

মূল কথাগুলো

  • এজে একটি request ID বসান এবং nginx, PHP-FPM, app-এর log আর queue জবের ভেতর দিয়ে সেটা বয়ে নিন। এটাই সবচেয়ে সস্তা বড় লাভ।
  • অল্প কয়েকটি স্থির ফিল্ডসহ স্ট্রাকচার্ড JSON log লিখুন, ব্যক্তিগত তথ্য মুছে দিন, আর retention সীমা ভেবেচিন্তে ঠিক করুন।
  • চার golden signal মাপুন, গড়ের বদলে percentile ব্যবহার করুন, আর saturation দেখুন: worker, queue age, connection, মেমরি।
  • OpenTelemetry tracing শুধু critical path-এ নিন, sampling সহ, আর trace ID-কে log-এর সঙ্গে জুড়ুন।
  • মানুষকে ডাকুন শুধু SLO-র সঙ্গে বাঁধা ব্যবহারকারী-প্রভাবের জন্য, আর প্রতিটি event-এর পর গোলমেলে alert ছাঁটুন।
  • Abort threshold রেখে রিহার্সাল করুন, প্রোভাইডারের metric-এর সঙ্গে মেলান, আর ভয়ের alert-এ কিছু করার আগে যাচাই করুন।

আনিছুর রহমান একজন সফটওয়্যার আর্কিটেক্ট এবং StoreConsole-এর নির্মাতা। বাড়তে থাকা ব্যবসার জন্য তিনি কমার্স ও ERP সিস্টেম ডিজাইন করেন — বিশেষ মনোযোগ event-driven আর্কিটেকচার, ডেটার নির্ভুলতা আর নিজের সার্ভারে চালানো সিস্টেমের ওপর।

About the Author

Anichur Rahaman

Continue Reading