ট্রাফিক স্পাইক সামলাতে Go মেমরি প্রোফাইলিং: pprof ওয়াকথ্রু
Go মেমরি প্রোফাইলিং আপনাকে হঠাৎ ট্রাফিক স্পাইক সামাল দিতে সাহায্য করে। JSON, DB স্ক্যান, এবং মিডলওয়্যারে অ্যালোকেশন হটস্পট খুঁজে পেতে একটি হাতে কলমে pprof ওয়াকথ্রু।

ট্রাফিক স্পাইক Go সার্ভিসের মেমরিতে কী করে\n\nপ্রোডাকশনে একটি “মেমরি স্পাইক” সাধারণত কোনো এক সিংগেল সংখ্যার বড় হওয়া নয়। আপনি দেখতে পারেন RSS (প্রসেস মেমরি) দ্রুত বেড়ে যায় কিন্তু Go হিপ সামান্যই চলে, অথবা হিপ বাড়ে ও GC চলার সাথে ধারালো ঢেউয়ের মত নেমে আসে। একই সময়ে, লেটেন্সি বাড়তে পারে কারণ রানটাইম বেশি সময় পরিষ্কার করতে ব্যয় করে।\n\nকমন প্যাটার্নস:\n\n- RSS আশা করা থেকে দ্রুত বাড়ে এবং কখনও কখনও স্পাইক শেষে পুরোপুরি নামেনা\n- ইন-ইউজ হিপ বাড়ে, তারপর GC বেশি চালানোর ফলে ধারালো সাইকেলে পড়ে\n- অ্যালোকেশন রেট বাড়ে (বাইট প্রতি সেকেন্ডে অনুমান)\n- GC পজ টাইম এবং GC CPU টাইম বাড়ে, যদিও প্রতিটি পজ ছোট হতে পারে\n- রিকোয়েস্ট লেটেন্সি লাফ দেয় এবং টেইল লেটেন্সি গোলমেলে হয়\n\nট্রাফিক স্পাইক per-request অ্যালোকেশনকে বাড়িয়ে দেয় কারণ ছোট বর্জ্যগুলো লোডের সাথে লিনিয়ারভাবে বাড়ে। যদি একটি রিকোয়েস্ট অতিরিক্ত 50 KB অ্যালোকেট করে (টেম্পরারি JSON বাফার, প্রতি-সারি স্ক্যান অবজেক্ট, মিডলওয়্যার কনটেক্সট ডেটা), তাহলে 2,000 RPS এ আপনি প্রতি সেকেন্ডে প্রায় 100 MB অ্যালোকেট করাচ্ছেন। Go অনেক সামলাতে পারে, কিন্তু GC-কে এখনো সেগুলো ট্রেস এবং ফ্রি করতে হয়। যখন অ্যালোকেশন ক্লিনআপকে ছাড়িয়ে যায়, হিপ টার্গেট বাড়ে, RSS অনুসরণ করে, এবং আপনি মেমরি লিমিটে পৌঁছতে পারেন।\n\nলক্ষণগুলো পরিচিত: অর্কেস্ট্রেটরের OOM কিল, হঠাৎ লেটেন্সি লাফ, GC-এ বেশি সময় খরচ, এবং এমন একটি সার্ভিস যা CPU পিন না থাকলেও “ব্যস্ত” মনে হয়। এছাড়া GC থ্র্যাশ হতে পারে: সার্ভিস চালু থাকে কিন্তু বারবার অ্যালোকেট ও কালেক্ট করে থ্রুপুট পড়ে যায় ঠিক যখন আপনি সবচেয়ে বেশি প্রয়োজন।\n\npprof দ্রুত একটি প্রশ্নের উত্তর দিতে সাহায্য করে: কোন কোডপাথগুলো সবচেয়ে বেশি অ্যালোকেট করছে, এবং সেগুলো কি জরুরি? একটি হিপ প্রোফাইল দেখায় এখন কি ধরে আছে। অ্যালোকেশন-ফোকাসড ভিউ (যেমন alloc_space) দেখায় কি সৃষ্টি হচ্ছে এবং পরে ফেলে দেওয়া হচ্ছে।\n\npprof প্রতিটি RSS বাইট ব্যাখ্যা করবে না। RSS Go হিপ ছাড়াও আরও কিছু অন্তর্ভুক্ত করে (স্ট্যাক, runtime মেটাডাটা, OS মাপিং, cgo অ্যালোকেশন, fragmentation)। pprof Go কোডে অ্যালোকেশন হটস্পট দেখাতে ভালো, কন্টেইনার-লেভেলের নির্দিষ্ট মোট মান প্রমাণ করতে নয়।\n\n## নিরাপদভাবে pprof সেটআপ করুন (ধাপে ধাপে)\n\npprof HTTP এন্ডপয়েন্ট হিসেবে ব্যবহার করা সহজ, কিন্তু এই এন্ডপয়েন্টগুলো আপনার সার্ভিস সম্পর্কে অনেক উন্মোচন করতে পারে। এদের পাবলিক API না ধরে অ্যাডমিন ফিচার হিসেবে ট্রিট করুন।\n\n### 1) pprof এন্ডপয়েন্ট যোগ করুন\n\nGo তে সবচেয়ে সহজ সেটআপ হল pprof আলাদা একটি অ্যাডমিন সার্ভারে চালানো। এতে প্রোফাইলিং রুটগুলো আপনার মেইন রাউটার ও মিডলওয়্যার থেকে আলাদা থাকে।\n\n```go
package main
import ( "log" "net/http" _ "net/http/pprof" )
func main() { go func() { // Admin only: bind to localhost log.Println(http.ListenAndServe("127.0.0.1:6060", nil)) }()
// Your main server starts here...
// http.ListenAndServe(":8080", appHandler)
select {}
}
\nযদি আপনি দ্বিতীয় পোর্ট খুলতে না পারেন, তাহলে মেইন সার্ভারে pprof রুটগুলো মাউন্ট করতে পারেন, কিন্তু এমন করলে তারা সহজে এক্সপোজ হয়ে যেতে পারে। আলাদা অ্যাডমিন পোর্ট নিরাপদ ডিফল্ট।\n\n### 2) ডিপ্লয়ের আগে লকডাউন করুন\n\nসহজ কন্ট্রোলে শুরু করুন। localhost-এ বাউন্ড করা মানে এন্ডপয়েন্টগুলো ইন্টারনেট থেকে পৌঁছনো যাবে না যদি না কেউ বিশেষভাবে সেই পোর্ট এক্সপোজ করে।\n\nএকটি দ্রুত চেকলিস্ট:\n\n- pprof চালান একটি অ্যাডমিন পোর্টে, না যে মেইন ইউজার-ফেসিং পোর্টে\n- প্রোডাকশনে `127.0.0.1` (বা একটি প্রাইভেট ইন্টারফেস) এ বাউন্ড করুন\n- নেটওয়ার্ক এজে allowlist যোগ করুন (VPN, bastion, বা ইন্টারনাল subnet)\n- যদি আপনার এজ auth চাপিয়ে দিতে পারে, auth আবশ্যক করুন (বেসিক auth বা টোকেন)\n- যাচাই করুন যে আপনি যে প্রোফাইলগুলি প্রয়োজন সেগুলো ফেচ করতে পারবেন: heap, allocs, goroutine\n\n### 3) নিরাপদভাবে বিল্ড ও রোল আউট করুন\n\nচেঞ্জটা ছোট রাখুন: pprof যোগ করুন, শিপ করুন, ও কনফার্ম করুন এটা কেবল আপনি আশা করা জায়গা থেকে পৌঁছা যাচ্ছে। যদি স্টেজিং থাকে, সেখানে আগে টেস্ট করে কিছু লোড সিমুলেট করে একটি হিপ ও অ্যালোকস প্রোফাইল ক্যাপচার করুন।\n\nপ্রোডাকশনে ধীরে ধীরে রোল আউট করুন (একটি ইনস্ট্যান্স বা ছোট ট্রাফিক স্লাইস)। যদি pprof ভুল কনফিগার করা থাকে, ব্লাস্ট রেডিয়াস ছোট থাকবে এবং আপনি ঠিক করতে পারবেন।\n\n## স্পাইক চলাকালীন সঠিক প্রোফাইলগুলো ক্যাপচার করুন\n\nস্পাইক চলাকালীন একক স্ন্যাপশট সাধারণত যথেষ্ট নয়। একটি ছোট টাইমলাইন ক্যাপচার করুন: স্পাইক-এর কয়েক মিনিট আগের (বেসলাইন), স্পাইক চলাকালীন (ইমপ্যাক্ট), এবং কয়েক মিনিট পর (রিকভারি)। এটা আসল অ্যালোকেশন বদলকে সাধারণ ওয়ার্ম-আপ থেকে আলাদা করতে সহজ করে।\n\nযদি আপনি কন্ট্রোলড লোড দিয়ে স্পাইক পুনরুৎপাদন করতে পারেন, প্রডাকশনের মতো অনুরোধ মিক্স, পে-লোড সাইজ, এবং কনকারেন্সি মিলিয়ে নিন। ছোট রিকোয়েস্টের স্পাইক বড় JSON রেসপন্সের স্পাইক থেকে খুব ভিন্ন আচরণ করে।\n\nএকটি হিপ প্রোফাইল এবং একটি অ্যালোকেশন-ফোকাসড প্রোফাইল উভয়ই নিন। তারা ভিন্ন প্রশ্নের উত্তর দেয়:\n\n- হিপ (`inuse`) দেখায় এখন কী লাইভ এবং মেমরি ধরে রেখেছে\n- অ্যালোকেশন (`alloc_space` বা `alloc_objects`) দেখায় কী বেশি সৃষ্টি হচ্ছে, যদিও দ্রুত মুক্ত হয়ে যেতে পারে\n\nপ্র্যাকটিক্যাল ক্যাপচার প্যাটার্ন: একবার হিপ প্রোফাইল নিন, তারপর একটি অ্যালোকশন প্রোফাইল নিন, তারপর 30–60 সেকেন্ড পরে আবার নিন। স্পাইক চলাকালীন দুইটি পয়েন্ট দেখে আপনি জানতে পারবেন কোন সন্দেহভাজন পথ স্থায়ী নাকি তীব্রভাবে বাড়ছে।\n\n```bash
# examples: adjust host/port and timing to your setup
curl -o heap_during.pprof "http://127.0.0.1:6060/debug/pprof/heap"
curl -o allocs_30s.pprof "http://127.0.0.1:6060/debug/pprof/allocs?seconds=30"
\npprof ফাইলগুলোর পাশাপাশি কয়েকটি runtime স্ট্যাটস রেকর্ড করুন যাতে আপনি ব্যাখ্যা করতে পারেন GC তখন কী করছিল। হিপ সাইজ, GC-সংখ্যা, এবং পজ টাইম সাধারণত যথেষ্ট। প্রতিটি ক্যাপচারের সময় একটি ছোট লগলাইনও সহায়ক যাতে “অ্যালোকেশন বাড়ল” কে “GC ধারাবাহিকভাবে চলতে শুরু করল” সাথে তুলনা করা যায়।\n\nইনসিডেন্ট নোট রাখুন: বিল্ড ভার্সন (কমিট/ট্যাগ), Go ভার্সন, গুরুত্বপূর্ণ ফ্ল্যাগ, কনফিগ পরিবর্তন, এবং কী ট্রাফিক চলছিল (এন্ডপয়েন্ট, টেন্যান্ট, পে-লোড সাইজ)। প্রোফাইল তুলনা করার সময় এসব ডিটেইল জরুরি হতে পারে।\n\n## হিপ ও অ্যালোকশন প্রোফাইল কিভাবে পড়বেন\n\nহিপ প্রোফাইল ভিউ অনুযায়ী ভিন্ন প্রশ্নের উত্তর দেয়।\n\nInuse space দেখায় যে ক্যাপচার সময় কী এখনও মেমরিতে রয়ে গেছে। লিক, দীর্ঘজীবী ক্যাশে, বা এমন রিকোয়েস্টগুলোর জন্য এটি ব্যবহার করুন যারা অবজেক্ট রেখে দেয়।\n\nAlloc space (টোটাল অ্যালোকেশন) দেখায় কোন জিনিসগুলো সময়ের সাথে সৃষ্টি হয়েছে, যদিও পরে মুক্ত হয়ে যায়। যখন স্পাইক GC কাজ বাড়ায়, লেটেন্সি লাফায়, বা OOM ঘটতে পারে তখন এটি ব্যবহার করুন।\n\nস্যাম্পলিং গুরুত্বপূর্ণ। Go প্রতিটি অ্যালোকেশন রেকর্ড করে না। এটি অ্যালোকেশন স্যাম্পল করে (runtime.MemProfileRate দ্বারা কন্ট্রোল করা), তাই ছোট, ঘনঘন অ্যালোকেশন কম প্রতিনিধিত্ব পেতে পারে এবং সংখ্যাগুলো অনুমান। তবে বড় offenders স্পাইক কন্ডিশনে সাধারণত চোখে পড়ে। ট্রেন্ড ও শীর্ষ অবদানকারীদের দেখুন, নিখুঁত হিসাব নয়।\n\nসবচেয়ে কাজে লাগার মত pprof ভিউগুলো:\n\n- top: দ্রুত দেখায় কে inuse বা alloc-এ ডমিনেট করে (flat এবং cumulative দুটো চেক করুন)\n- list sync.Pool যোগ করুন)\n- প্রতি-রিকোয়েস্ট অবজেক্ট সৃষ্টি কাটুন (উদাহরণ: JSON জন্য মধ্যবর্তী ম্যাপ বানানো এড়ান)\n- একই লোডে পুনরায় প্রোফাইল করে নিশ্চিত করুন ডিফ যেখানে প্রত্যাশা করেন সেখানে ছোট হয়েছে\n\nযদি সংখ্যাগুলো প্রত্যাশিতভাবে চলে, আপনি প্রকৃত কারন পেয়েছেন, কেবল স্কেয়ার্ড রিপোর্ট নয়।\n\n## JSON এনকোডিং-এ অ্যালোকেশন হটস্পট খুঁজে পাওয়া\n\nস্পাইক চলাকালীন JSON কাজ প্রতি অনুরোধে হওয়ার কারণে বড় মেমরি বিল হয়ে উঠতে পারে। JSON হটস্পট সাধারণত অনেক ছোট অ্যালোকেশন হিসাবে দেখা দেয় যা GC-কে কঠোর করে।\n\n### pprof-এ খোঁজার জন্য রেড ফ্ল্যাগস\n\nযদি হিপ বা অ্যালোকেশন ভিউ encoding/json নির্দেশ করে, যা আপনি সেখানে feed করছেন তা ভালভাবে দেখুন। এই প্যাটার্নগুলো সাধারণত অ্যালোকেশন বাড়ায়:\n\n- উত্তর হিসাবে map[string]any (বা []any) ব্যবহার করা, টাইপেড struct-এর বদলে\n- একই অবজেক্ট একাধিকবার marshal করা (যেমন, লগিং-এর জন্য এবং রিটার্নের জন্য আলাদাভাবে)\n- প্রোডাকশনে json.MarshalIndent দিয়ে pretty printing করা\n- marshal করার আগে টেম্পরারি স্ট্রিং তৈরি করে JSON বানানো (fmt.Sprintf, স্ট্রিং কনক্যাট)\n- বড় []byte কে string এ কনভার্ট করা শুধু API মিলানোর জন্য\n\njson.Marshal সবসময় পুরো আউটপুটের জন্য একটি নতুন []byte অ্যালোকেট করে। json.NewEncoder(w).Encode(v) সাধারণত সেই বড় বাফার এড়াতে সাহায্য করে কারণ এটি io.Writer-এ লিখে, কিন্তু এটা এখনও অভ্যন্তরীণভাবে অ্যালোকেট করতে পারে, বিশেষ করে যদি v অনেক any, ম্যাপ বা পয়েন্টার-ওয়েটেড স্ট্রাকচার থাকে।\n\n### দ্রুত ফিক্স ও দ্রুত পরীক্ষাগুলো\n\nপ্রতিটি রেসপন্সের শেপের জন্য টাইপেড struct দিয়ে শুরু করুন। সেগুলো reflection কাজ কমায় এবং per-field interface boxing এড়ায়।\n\nতারপর অপ্রয়োজনীয় প্রতি-রিকোয়েস্ট টেম্পরারিগুলো সরান: bytes.Buffer sync.Pool দিয়ে reuse করুন (সাবধানে), প্রোডাকশনে indent করা বন্ধ করুন, এবং লগের জন্য পুনরায় marshal করা না করুন।\n\nকিছু ছোট পরীক্ষা যা নিশ্চিত করবে JSON সমস্যার মূল কিনা:\n\n- এক হট এন্ডপয়েন্টে map[string]any-এর বদলে struct ব্যবহার করে প্রোফাইল তুলুন\n- Marshal থেকে Encoder-এ বদলে response-এ সরাসরি লিখুন\n- MarshalIndent বা ডিবাগ-স্টাইল ফরম্যাটিং সরিয়ে একই লোডে আবার প্রোফাইল করুন\n- অপরিবর্তিত ক্যাশড রেসপন্সগুলোতে JSON এনকোডিং স্কিপ করে প্রোফাইলে হ্রাস পরিমাপ করুন\n\n## কুয়েরি স্ক্যানিং-এ অ্যালোকেশন হটস্পট খুঁজে পাওয়া\n\nস্পাইক চলাকালীন মেমরি বাড়ার সময় ডাটাবেস রিড এক সাধারণ সাপরাইজ। SQL টাইমে ফোকাস করা সহজ, কিন্তু স্ক্যান ধাপে প্রতি সারির জন্য অনেক অ্যালোকেশন হতে পারে, বিশেষ করে আপনি ফ্লেক্সিবল টাইপে স্ক্যান করলে।\n\nসাধারণ অপরাধীরা:\n\n- interface{} বা map[string]any-এ স্ক্যান করা এবং ড্রাইভারকে টাইপ নির্ধারণ করতে দেয়া\n- প্রতিটি ফিল্ডের জন্য []byte থেকে string এ কনভার্ট করা\n- বড় রেজাল্ট সেটে nullable wrappers (sql.NullString, sql.NullInt64) ব্যবহার করা\n- সবসময় দরকার না এমন বড় text/blob কলাম টেনে আনা\n\nএকটি প্যাটার্ন আছে যা চুপচাপ মেমরি খায়: সারি ডেটা টেম্পরারি ভ্যারিয়েবলে স্ক্যান করে পরে সেটি বাস্তব struct-এ কপি করা (বা প্রতি সারি একটি ম্যাপ বানানো)। যদি আপনি সরাসরি কনক্রিট struct-এ স্ক্যান করতে পারেন, আপনি অতিরিক্ত অ্যালোকেশন ও টাইপ চেক এড়াতে পারবেন।\n\nব্যাচ সাইজ ও পেজিং আপনার মেমরি আকৃতি বদলে দেয়। 10,000 সারি একসাথে একটা slice-এ ফেচ করলে slice growth এবং প্রতিটি সারির জন্য অ্যালোকেশন একবারে হয়ে যায়। যদি হ্যান্ডলার কেবল একটি পেজই প্রয়োজন, কুয়েরিতে পেজিং পুশ করুন এবং পেজ সাইজ স্থিতিশীল রাখুন। যদি আপনাকে অনেক সারি প্রসেস করতে হয়, সেগুলো স্ট্রিম করুন এবং প্রতিটি সারি স্টোর করা থেকে ছোট সারাংশ সংগ্রহ করুন।\n\nবড় টেক্সট ফিল্ডগুলো বিশেষ যত্ন প্রয়োজন। অনেক ড্রাইভার টেক্সট []byte হিসেবে দেয়। তা string-এ কনভার্ট করলে ডেটা কপি হয়, সুতরাং প্রতিটি সারির জন্য করলে অ্যালোকেশন বিস্ফোরিত হতে পারে। যদি কেবল মাঝে মাঝে মানটি দরকার হয়, কনভার্সশন ধীরে করুন বা ঐ এন্ডপয়েন্টের জন্য কম কলাম স্ক্যান করুন।\n\nড্রাইভার না নিজের কোড কোনটা বেশী অ্যালোকেট করছে তা নিশ্চিত করতে, প্রোফাইল কোথায় ডমিনেট করে তা দেখুন:\n\n- যদি ফ্রেমগুলো আপনার মেপিং কোড দেখায়, স্ক্যান টার্গেট ও কনভার্সনে ফোকাস করুন\n- যদি ফ্রেমগুলো database/sql বা ড্রাইভার দেখায়, প্রথমে রো ও কলাম কমান, তারপর ড্রাইভার-স্পেসিফিক অপশন বিবেচনা করুন\n- alloc_space এবং alloc_objects উভয় চেক করুন; অনেক ছোট অ্যালোকেশন কয়েকটা বড় অ্যালোকেশনের চেয়েও খারাপ হতে পারে\n\nউদাহরণ: একটি “list orders” এন্ডপয়েন্ট SELECT * স্ক্যান করে []map[string]any তে রাখে। স্পাইক চলাকালীন প্রতিটি অনুরোধ হাজার হাজার ছোট ম্যাপ ও স্ট্রিং তৈরি করে। কুয়েরি পরিবর্তন করে শুধুমাত্র প্রয়োজনীয় কলাম সিলেক্ট করা এবং []Order{ID int64, Status string, TotalCents int64}-এ স্ক্যান করলে সাধারণত তাত্ক্ষণিকভাবে অ্যালোকেশন পড়ে যায়। একই ধারণা AppMaster থেকে জেনারেট করা Go ব্যাকএন্ডে প্রযোজ্য: হটস্পট প্রায়ই ফলাফল ডেটা কিভাবে শেপ ও স্ক্যান করা হচ্ছে তাতে থাকে, ডাটাবেসে নয়।\n\n## মিডলওয়্যার প্যাটার্নগুলো যা প্রতি অনুরোধে চুপচাপ অ্যালোকেট করে\n\nমিডলওয়্যার সস্তা মনে হয় কারণ এটা “শুধু একটি র্যাপার”, কিন্তু এটি প্রতি অনুরোধে চলে। স্পাইক চলাকালীন ছোট per-request অ্যালোকেশন দ্রুত জমে যায় এবং অ্যালোকেশন রেটে বাড়তি চাপ হিসেবে দেখা যায়।\n\nলগিং মিডলওয়্যার সাধারণ উৎস: স্ট্রিং ফরম্যাট করা, ফিল্ডের ম্যাপ তৈরি, অথবা সুন্দর আউটপুটের জন্য হেডার কপি করা। রিকোয়েস্ট আইডি হেল্পার ID জেনারেট করলে অ্যালোকেশন হতে পারে যখন তা স্ট্রিং-এ রূপান্তর করে কনটেক্সটে লাগানো হয়। এমনকি context.WithValue-ও অ্যালোকেট করতে পারে যদি আপনি প্রতি অনুরোধ নতুন অবজেক্ট (বা নতুন স্ট্রিং) স্টোর করেন।\n\nকমপ্রেশন ও বডি হ্যান্ডলিং আরেকটি ঘনঘন অপরাধী। যদি মিডলওয়্যার পুরো রিকোয়েস্ট বডি পড়ে “পিক” বা ভ্যালিডেশন করতে, আপনি প্রতি রিকোয়েস্ট বড় বাফার পেতে পারেন। Gzip মিডলওয়্যার অনেক অ্যালোকেট করতে পারে যদি এটি প্রতিবার নতুন রিডার ও রাইটার তৈরি করে পুনরায় ব্যবহার না করে।\n\nঅথেনটিকেশন ও সেশন লেয়ারগুলোও অনুরূপ। যদি প্রতি অনুরোধ টোকেন পার্স করা হয়, base64-decoding করা হয়, বা সেশন ব্লবগুলোতে নতুন struct লোড করা হয়, তবে হ্যান্ডলার কাজ কম হলেও ক্রমাগত churn হয়।\n\nট্রেসিং ও মেট্রিক্সও প্রত্যাশার তুলনায় বেশি অ্যালোকেট করতে পারে যখন লেবেল ডায়নামিকভাবে বানানো হয়। রুট নাম, ইউজার এজেন্ট, বা টেন্যান্ট ID-গুলোকে নতুন স্ট্রিংয়ে কনক্যাট করা ক্লাসিক লুকানো খরচ।\n\nযেসব প্যাটার্নগুলো প্রায়ই “হাজার কাটা”-র মত সমস্যায় পড়ায়:\n\n- fmt.Sprintf দিয়ে লগ লাইন বানানো এবং প্রতি রিকোয়েস্ট নতুন map[string]any তৈরি করা\n- লগিং বা সাইনিং-এর জন্য হেডারগুলো নতুন ম্যাপ বা স্লাইসে কপি করা\n- প্রতিবার নতুন gzip বাফার ও রিডার/রাইটার তৈরি করা পরিবর্তে পুল ব্যবহার করা\n- উচ্চ-কার্ডিনালিটি মেট্রিক লেবেল (অনেক ইউনিক স্ট্রিং) তৈরি করা\n- প্রতি অনুরোধ কনটেক্সটে নতুন struct রাখা\n\nমিডলওয়্যার খরচ আলাদা করতে, দুইটি প্রোফাইল তুলুন: একটি ফুল চেইন সক্রিয় রেখে এবং একটি যেখানে মিডলওয়্যার সাময়িকভাবে নিষ্ক্রিয় বা নো-অপ রাখা হয়েছে। একটি সিম্পল টেস্ট হলো একটি হেলথ এন্ডপয়েন্ট যা প্রায়ই allocation-free হওয়া উচিত। যদি /health স্পাইক চলাকালীনও অনেক অ্যালোকেট করে, তাহলে হ্যান্ডলার নয় মিডলওয়্যারই সমস্যার উৎস।\n\nAppMaster দিয়ে জেনারেট করা Go ব্যাকএন্ড থাকলেও একই নিয়ম প্রযোজ্য: ক্রস-কাটিং ফিচারগুলো মেজারেবল রাখুন, এবং per-request অ্যালোকেশনগুলোকে একটি বাজেট হিসেবে অডিট করুন।\n\n## যে ফিক্সগুলো সাধারণত দ্রুত ফল দেয়\n\nএকবার আপনার কাছে হিপ এবং allocs ভিউ থাকলে, এমন পরিবর্তনগুলোকে অগ্রাধিকার দিন যা per-request অ্যালোকেশন কমায়। লক্ষ্য সূক্ষ্ম ট্রিক নয়—হট পাথ কম সংক্ষিপ্ত জীবনধরী অবজেক্ট তৈরী করবে এমনভাবে করা।\n\n### নিরাপদ, সাধারণ জয়গুলো দিয়ে শুরু করুন\n\nআকার প্রেডিক্টেবল হলে প্রি-অলোকেট করুন। যদি একটি এন্ডপয়েন্ট সাধারণত প্রায় 200 আইটেম রিটার্ন করে, আপনার স্লাইস capacity 200 দিয়ে তৈরি করুন যাতে এটি কয়েকবার বাড়ে ও কপি না করে।\n\nহট পাথে স্ট্রিং তৈরি এড়ান। fmt.Sprintf সুবিধাজনক কিন্তু প্রায়ই অ্যালোকেট করে। লগিং-এ structured fields ব্যবহার করুন, এবং যেখানে মানে আছে সেখানে ছোট বাফার reuse করুন।\n\nবড় JSON রেসপন্স জেনারেট করলে স্ট্রীমিং বিবেচনা করুন একসাথে একটি বিশাল []byte বা string তৈরি না করে। একটি সাধারণ স্পাইক প্যাটার্ন: রিকোয়েস্ট আসে, আপনি বড় বডি পড়েন, বড় রেসপন্স বানান, মেমরি লাফ দেয় যতক্ষণ না GC ধরে।\n\nদ্রুত পরিবর্তনগুলো যা সাধারণত আগে/পরে প্রোফাইলে স্পষ্ট দেখা যায়:\n\n- স্লাইস ও ম্যাপ প্রি-অলোকেট করুন যদি সাইজ রেঞ্জ জানা থাকে\n- হ্যান্ডলিং-এ fmt-ভিত্তিক ফরম্যাটিং কমান এবং সস্তা বিকল্প ব্যবহার করুন\n- বড় JSON রেসপন্স স্ট্রিম করুন (সরাসরি response writer-এ encode করুন)\n- একই আকৃতির পুনরায় ব্যবহারযোগ্য অবজেক্টের জন্য sync.Pool ব্যবহার করুন (বাফার, encoder) এবং সেগুলো সঠিকভাবে রিটার্ন করুন\n- রিকোয়েস্ট লিমিট সেট করুন (বডি সাইজ, পে-লোড সাইজ, পেজ সাইজ) যাতে সবচেয়ে খারাপ কেইস সীমাবদ্ধ থাকে\n\n### sync.Pool সাবধানে ব্যবহার করুন\n\nsync.Pool সাহায্য করে যখন আপনি একই জিনিস বারবার অ্যালোকেট করেন, যেমন প্রতি রিকোয়েস্ট একটি bytes.Buffer। কিন্তু এটি ক্ষতি করতে পারে যদি আপনি এমন অবজেক্ট পুল করেন যার সাইজ অননুমেয়, বা ভুলে সেগুলো reset না করেন, ফলে বড় বেকিং অ্যারে জীবিত থাকছে।\n\nএকই ওয়ার্কলোড ব্যবহার করে আগে ও পরে পরিমাপ করুন:\n\n- স্পাইক উইন্ডো চলাকালীন একটি allocs প্রোফাইল ক্যাপচার করুন\n- একবারে একটি পরিবর্তন করুন\n- একই রিকোয়েস্ট মিক্স পুনরায় চালান এবং total allocs/op তুলনা করুন\n- শুধু মেমরি নয়, টেইল লেটেন্সিও দেখুন\n\nAppMaster-এ জেনারেট করা Go ব্যাকএন্ড থাকলে, এই ফিক্সগুলো কাস্টম কোডে, হ্যান্ডলার, ইন্টিগ্রেশন ও মিডলওয়্যার চারপাশে প্রযোজ্য। এটাই এমন জায়গা যেখানে স্পাইক-চালিত অ্যালোকেশনগুলো লুকানো থাকে।\n\n## সাধারণ pprof ভুল ও false alarm গুলো\n\nদিন নষ্ট করার দ্রুততম উপায় হল ভুল জিনিস অপটিমাইজ করা। সার্ভিস ধীর হলে CPU দিয়ে শুরু করুন। যদি OOM করে মারা যায়, হিপ দিয়ে শুরু করুন। যদি সার্ভিস টিকে থাকে কিন্তু GC ধারাবাহিকভাবে কাজ করছে, তাহলে অ্যালোকেশন রেট ও GC আচরণ দেখুন।\n\nআরেকটি ফাঁদ হল শুধু “top” দেখেই কাজ শেষ করা ভাবা। “top” প্রসঙ্গ লুকায়। সবসময় কল স্ট্যাক (বা ফ্লেম গ্রাফ) দেখুন যাতে বোঝা যায় কে allocator কল করেছে। ফিক্স প্রায়শই হট ফাংশনের এক বা দুই ফ্রেম উপরে থাকে।\n\nআর inuse বনাম churn মিশিয়ে ফেলতেও সাবধান থাকুন। একটি রিকোয়েস্ট 5 MB ছোট-জীবিত অবজেক্ট অ্যালোকেট করে, অতিরিক্ত GC ট্রিগার করে, এবং শেষে মাত্র 200 KB ইন-ইউজ রেখে দিতে পারে। যদি আপনি কেবল inuse দেখেন, আপনি churn মিস করবেন। আর যদি কেবল টোটাল অ্যালোকেশন দেখেন, আপনি এমন কিছু অপটিমাইজ করতে শুরু করতে পারেন যা বাস্তুতই থাকে না এবং OOM-ঝুঁকি বাড়ায় না।\n\nকোড পরিবর্তনের আগে দ্রুত স্যানিটি চেকস:\n\n- সঠিক ভিউতে আছেন কি নিশ্চিত করুন: retention-এর জন্য heap inuse, churn-এর জন্য alloc_space/alloc_objects\n- শুধু ফাংশন নাম নয়, স্ট্যাক তুলুন (encoding/json প্রায়শই লক্ষণ)\n- বাস্তবসম্মত ট্রাফিক পুনরুৎপাদন করুন: একই এন্ডপয়েন্ট, পে-লোড সাইজ, হেডার, কনকারেন্সি\n- একটি বেসলাইন ও স্পাইক প্রোফাইল ক্যাপচার করুন, তারপর তাদের diff করুন\n\nঅবাস্তব লোড টেস্ট false alarm দেয়। যদি আপনার টেস্ট ছোট JSON বডি পাঠায় কিন্তু প্রডাকশন 200 KB পাঠায়, আপনি ভুল পাথ অপটিমাইজ করবেন। যদি আপনার টেস্ট একটি ডাটাবেস সারি রিটার্ন করে, আপনি কখনই 500 সারি স্ক্যানিং আচরণ দেখবেন না।\n\nনয়েজের পিছে ছুটবেন না। যদি একটি ফাংশন কেবল স্পাইক প্রোফাইলে উপস্থিত হয় (বেসলাইনে নয়), সেটা শক্তিশালী লিড। যদি দুইটাতেই একই স্তরে থাকে, সেটা নর্মাল ব্যাকগ্রাউন্ড কাজ হতে পারে।\n\n## একটি বাস্তবসম্মত ইনসিডেন্ট ওয়াকথ্রু\n\nসোমবার সকালে একটি প্রচার চলে এবং আপনার Go API 8x স্বাভাবিক ট্রাফিক পায়। প্রথম লক্ষণ ক্র্যাশ নয়। RSS বাড়ে, GC ব্যস্ত হয়, এবং p95 লেটেন্সি লাফ দেয়। সবচেয়ে হট এন্ডপয়েন্ট GET /api/orders কারণ মোবাইল অ্যাপটি প্রতিটি স্ক্রিন ওপেনে এটিকে রিফ্রেশ করে।\n\nআপনি দুইটি স্ন্যাপশট নেন: একটি শান্ত সময় থেকে (বেসলাইন) এবং একটি স্পাইক চলাকালীন। একই ধরনের হিপ প্রোফাইল উভয় সময় নিন যাতে তুলনা ন্যায়সঙ্গত থাকে।\n\nমুহূর্তের কার্যপ্রবাহ:\n\n- একটি বেসলাইন হিপ প্রোফাইল নিন এবং বর্তমান RPS, RSS, ও p95 লেটেন্সি নোট করুন\n- স্পাইক চলাকালীন 1–2 মিনিট উইন্ডোতে আরেকটি হিপ প্রোফাইল এবং একটি অ্যালোকেশন প্রোফাইল নিন\n- দুইটির মধ্যে শীর্ষ অ্যালোকেটরগুলো তুলনা করুন এবং সবচেয়ে বেশি যা বেড়েছে তাতে ফোকাস করুন\n- বড় ফাংশন থেকে শুরু করে কলার পর্যন্ত হেটে হিট করুন যতক্ষণ না আপনার হ্যান্ডলার পাথে পৌঁছান\n- একটি ছোট নিরাপদ পরিবর্তন করুন, এক ইনস্ট্যান্সে রোলআউট করুন, এবং আবার প্রোফাইল করুন\n\nএই কেসে স্পাইক প্রোফাইল দেখাল যে বেশিরভাগ নতুন অ্যালোকেশন JSON এনকোডিং থেকে আসছে। হ্যান্ডলার প্রতিটি সারি map[string]any বানায়, তারপর একটি স্লাইস অফ ম্যাপকে json.Marshal করে। প্রতিটি অনুরোধ অনেক ছোট স্ট্রিং ও interface ভ্যালু তৈরি করে।\n\nসবচেয়ে ছোট নিরাপদ ফিক্স ছিল ম্যাপ তৈরি বন্ধ করা। ডাটাবেস সারি সরাসরি টাইপ করা struct-এ স্ক্যান করে সেই স্লাইস এনকোড করা হল। অন্য কিছু বদলেনি: একই ফিল্ড, একই রেসপন্স শেপ, একই স্ট্যাটাস কোড। একটি ইনস্ট্যান্সে পরিবর্তন রোল করে দেখা গেলে JSON পাথে অ্যালোকেশন পড়ে গেলো, GC সময় কমল, এবং লেটেন্সি স্থিতিশীল হল।\n\nতারপর ধীরে ধীরে রোল আউট করুন এবং মেমরি, GC, ও এরর রেট মনিটর করুন। যদি আপনি AppMaster-এর মতো no-code প্ল্যাটফর্মে সার্ভিস গড়েন, এটি মনে রাখার মতো যে রেসপন্স মডেল টাইপ করা ও ধারাবাহিক রাখা স্পাইক-প্রবণ অ্যালোকেশন এড়াতে সাহায্য করে।\n\n## পরের মেমরি স্পাইক প্রতিরোধের পরবর্তী পদক্ষেপগুলো\n\nএকবার আপনি স্পাইক স্থিতিশীল করলে পরের স্পাইককে বিরক্তিকর না করে ক্লান্তিকর বানান। প্রোফাইলিংকে একটি পুনরাবৃত্তিযোগ্য ড্রিল হিসেবে ট্রিট করুন।\n\nটিমের জন্য একটি সংক্ষিপ্ত রানবুক লিখুন যা তারা ক্লান্ত অবস্থাতেও ফলো করতে পারবে। এতে বলা থাকবে কী ক্যাপচার করতে, কখন ক্যাপচার করতে, এবং কীভাবেKNOWN-GOOD বেসলাইন এর সাথে তুলনা করতে হবে। ব্যবহারিক রাখুন: নির্দিষ্ট কমান্ড, প্রোফাইল কোথায় যাবে, এবং আপনার শীর্ষ অ্যালোকেটরগুলোর “নর্মাল” কী।\n\nঅ্যালোকেশন প্রেশারের জন্য হালকা মোনিটরিং যোগ করুন যাতে OOM-এর আগেই ধরতে পারেন: হিপ সাইজ, GC সাইকেল প্রতি সেকেন্ড, এবং প্রতি অনুরोधে বাইটস অ্যালোকেটেড। “প্রতি অনুরোধ অ্যালোকেশন 30% বেড়েছে” ধরতে পারা-week over week কড়া মেমরি অ্যালার্ম অপেক্ষা করা থেকে বেশি কার্যকর হতে পারে।\n\nCI-তে একটি প্রতিনিধিস্বরূপ এন্ডপয়েন্টে ছোট লোড টেস্ট চালিয়ে চেকগুলো আগে থেকেই করুন। ছোট রেসপন্সে ছোট পরিবর্তনও অ্যালোকেশন দ্বিগুণ করে দিতে পারে যদি তারা অতিরিক্ত কপি ট্রিগার করে, এবং প্রোডাকশনে যাওয়ার আগে এটা ধরা ভাল।\n\nযদি আপনার রিকোয়ারমেন্ট বারবার বদলে, AppMaster (appmaster.io) একটি ব্যবহারযোগ্য উপায় হতে পারে ক্লিয়ান Go ব্যাকএন্ড রি-বিল্ড ও পুনরায় জেনারেট করার, তারপর বাস্তবসম্মত লোডে এক্সপোর্ট করা সোর্স প্রোফাইল করে শিপ করার আগে।
প্রশ্নোত্তর
একটি স্পাইক সাধারণত প্রত্যাশার চেয়ে বেশি অ্যালোকেশন রেট বাড়িয়ে দেয়। ছোট ছোট per-request টেমপরারি অবজেক্টগুলো RPS-এর সাথে লিনিয়ারভাবে জমে যায়, যা GC-কে বারবার চালাতে বাধ্য করে এবং লাইভ হিপ বড় না হলেও মেমরি লাফ ধরে উঠতে পারে।
হিপ মেট্রিকস Go-ম্যানেজড মেমরিকে ট্র্যাক করে, কিন্তু RSS-এ আরও অনেক কিছু থাকে: গোরুটিন স্ট্যাক, runtime মেটাডাটা, OS মাপিং, fragmentation, এবং নন-হিপ অ্যালোকেশন (কিছু cgo ব্যবহারসহ)। স্পাইক চলাকালীন RSS ও heap আলাদা ভাবে বাড়া স্বাভাবিক, তাই পফরফ ব্যবহার করে Go কোডের অ্যালোকেশন হটস্পট খুঁজুন, RSS পুরোপুরি মেলাতে চেষ্টা না করে।
যদি ধারণা থাকে কিছু ধরে রাখা হচ্ছে, তখন হিপ প্রোফাইল দিয়ে শুরু করুন; আর যখন সন্দেহ থাকে বেশি churn হচ্ছে (অসংক্ষিপ্ত অজেক্ট অনেক তৈরি হচ্ছে), তখন allocation-ফোকাসড প্রোফাইল (যেমন allocs/alloc_space) দেখুন। ট্রাফিক স্পাইক হলে churn প্রায়শই বাস্তব সমস্যা কারণ এটা GC CPU সময় ও টেইল লেটেন্সি বাড়ায়।
সবচেয়ে সাধারণ নিরাপদ সেটআপ হল pprof আলাদা একটি অ্যাডমিন-ওনলি সার্ভারে চালানো, সেটা 127.0.0.1-এ বাউন্ড করে এবং কেবল অভ্যন্তরীণ অ্যাক্সেসের মাধ্যমে পৌঁছানো যায়। pprof-কে একটি অ্যাডমিন ইন্টারফেস হিসেবে বিবেচনা করুন কারণ এটি সার্ভিস সম্পর্কিত অভ্যন্তরীণ বিবরণ উন্মোচন করতে পারে।
সংক্ষিপ্ত একটি টাইমলাইন নিন: স্পাইক শুরু হওয়ার কয়েক মিনিট আগের একটি প্রোফাইল (বেসলাইন), স্পাইক চলাকালীন একটি (ইমপ্যাক্ট), এবং পরে একটি (রিকভারি)। এতে কী বদলেছে বোঝা সহজ হয়, নৌই স্বাভাবিক ব্যাকগ্রাউন্ড অ্যালোকেশন চেস করতে হয় না।
ক্যাপচার সময় inuse ব্যবহার করে যা ক্যাপচারের সময় লাইভ মেমরি ধরে আছে তা খুঁজে পান, এবং alloc_space (বা alloc_objects) ব্যবহার করে যা দেখায় কোন জায়গা বেশি সৃষ্টি হচ্ছে। inuse retention-তার জন্য, alloc_space churn-এর জন্য।
যদি encoding/json অ্যালোকেশনে ডমিনেট করে, সাধারণত সমস্যাটি আপনার ডেটা শেপে—কার্যত প্যাকেজ নিজেই নয়। map[string]any-এর বদলে টাইপ করা struct ব্যবহার, json.MarshalIndent এড়ানো, এবং অপ্রয়োজনীয় টেম্পরারি স্ট্রিং নির্মাণ বন্ধ করলে অ্যালোকেশন তত্ক্ষণাত কমে যায়।
অতিরিক্ত মেমরি বেড়ে যাওয়ার কারণগুলো: interface{} বা map[string]any-এ স্ক্যান করা, []byte থেকে string-এ প্রতিটি ফিল্ডের কনভার্ট করা, এবং বেশি রো বা কলাম ফেরত আনা। শুধুমাত্র প্রয়োজনীয় কলাম সিলেক্ট করা, পেজিং করা, এবং কংক্রিট struct-এ সরাসরি স্ক্যান করা সাধারণ উচ্চ-প্রভাব বিশ্লেষণ।
মিডলওয়্যার প্রতি অনুরোধে চলে, তাই ছোট ছোট অ্যালোকেশন লোডে দ্রুত জমে যায়। লগিং (নতুন স্ট্রিং/ম্যাপ তৈরি), ট্রেসিং-এ উচ্চ-কার্ডিনালিটি লেবেল, প্রতি অনুরোধে রিকুয়েস্ট ID তৈরি, gzip রিডার/রাইটার নতুন করে তৈরি করা, এবং context-এ নতুন অবজেক্ট রাখা—এসবই steady churn হিসাবে প্রোফাইলে ধরা পড়ে।
হ্যাঁ। generated বা handwritten যেকোন Go কোডে একই প্রোফাইল-চালিত পদ্ধতি প্রযোজ্য। generated backend সোর্স এক্সপোর্ট করলে আপনি pprof চলতে পারেন, allocating call paths চিহ্নিত করে মডেল, হ্যান্ডলার, ও ক্রস-কাটিং লজিক বদলে পরে স্পাইক আগে যাচাই করতে পারবেন।


