7 মিনিট

প্রথম ব্যাকএন্ড নিয়োগের আগে Go লোড টেস্টিং

বাস্তব ট্রাফিক মডেল করে p95 লেটেন্সি, ডেটাবেস চাপ, মেমরি ও ত্রুটি মাপতে Go লোড টেস্টিং ব্যবহার করুন, তারপর নিয়োগের সিদ্ধান্ত নিন।

প্রথম ব্যাকএন্ড নিয়োগের আগে Go লোড টেস্টিং

মাসে দশ হাজার সক্রিয় ব্যবহারকারী কোনো ক্যাপাসিটি চাহিদা নয়। এটি বিলিং বা অ্যানালিটিক্সের একটি সংখ্যা। অনুরোধ হালকা এবং পুরো মাসে ছড়ানো থাকলে একটি Go সার্ভিস এর চেয়ে অনেক বেশি ব্যবহারকারী সামলাতে পারে। আবার প্রতিটি সেশন যদি ধীর কোয়েরি, আপলোড ও তৃতীয় পক্ষের কলের ঝাঁক তৈরি করে, তবে কয়েকশ ব্যবহারকারীতেই সেটি ভেঙে পড়তে পারে।

কাজের প্রশ্নটি হলো, প্রত্যাশিত সর্বোচ্চ ট্রাফিকে তৈরি করা ব্যাকএন্ড নির্ধারিত সার্ভিস লক্ষ্য পূরণ করে কি না, এবং বৃদ্ধি ও সাধারণ একটি ব্যর্থতার জন্য যথেষ্ট জায়গা থাকে কি না। ব্যাকএন্ড ইঞ্জিনিয়ার নিয়োগের আগেই এর উত্তর পাওয়া যায়, তবে লোড টেস্টকে আপনার পণ্যের মতো হতে হবে এবং একই সময়ে API, Go রানটাইম ও PostgreSQL কী করছে তা রেকর্ড করতে হবে। গড় লেটেন্সির সবুজ গ্রাফ প্রায় কিছুই প্রমাণ করে না।

কোনো প্রতিষ্ঠাতাকে তৈরি করা Go ব্যাকএন্ড মাসে 10,000 সক্রিয় ব্যবহারকারীর জন্য প্রস্তুত বলার আগে আমি এই পরীক্ষাটিই চাইব। এটি পুনরাবৃত্তিযোগ্য পাস বা ফেল ফল দেয়, প্রথম বাধাটি প্রকাশ করে এবং ক্যাপাসিটি সমস্যাকে সঠিকতা সমস্যা থেকে আলাদা করে।

মাসিক ব্যবহারকারীকে সর্বোচ্চ সময়ের অনুরোধে বদলাতে হবে

লোডের মাত্রা বাছার আগে ব্যবহারকারী পূর্বাভাসকে প্রতি সেকেন্ডের অনুরোধে রূপান্তর করুন। মাসিক সক্রিয় ব্যবহারকারী ব্যাকএন্ড চালানো দুটি চলক লুকিয়ে রাখে: ব্যস্ততম সময়ে কত সেশন আসে এবং প্রতিটি সেশন কত কাজ তৈরি করে।

ব্যক্তিগত বেটার ট্রাফিক থাকলে পর্যবেক্ষিত ডেটা দিয়ে শুরু করুন। সবচেয়ে ব্যস্ত 15 মিনিটের সেশন, প্রতি সেশনের অনুরোধ এবং রুটের অনুপাত গুনুন। এখনো ট্রাফিক না থাকলে অনুমানগুলো লিখুন, যাতে সবাই সেগুলো নিয়ে প্রশ্ন তুলতে পারে। ধরুন 10,000 সক্রিয় ব্যবহারকারী মাসে আটটি সেশন করেন, প্রতি সেশনে 15টি API অনুরোধ হয় এবং দৈনিক ট্রাফিকের 20 শতাংশ ব্যস্ততম ঘণ্টায় আসে। একটি গড় ব্যস্ত দিনে এতে প্রায় 8টি অনুরোধ প্রতি সেকেন্ড হয়। লঞ্চ, নোটিফিকেশন, বেতন দেওয়ার সময়সীমা বা একই সময় অঞ্চলের ব্যবহারকারী প্রকৃত সর্বোচ্চ হারকে কয়েক গুণ বাড়াতে পারে।

এই হিসাবকে ভুয়া নির্ভুলতায় পরিণত করবেন না। তিনটি পরীক্ষার মাত্রা নির্ধারণে এটি ব্যবহার করুন:

  • প্রত্যাশিত সর্বোচ্চ: বর্তমানে অনুমান করা ব্যস্ততম লোড।
  • বৃদ্ধির সর্বোচ্চ: ব্যবসার আরও ভালো পূর্বাভাস না থাকলে প্রত্যাশিত সর্বোচ্চের দ্বিগুণ।
  • চাপের মাত্রা: সার্ভিস লক্ষ্য ব্যর্থ হওয়া বা কোনো রিসোর্স পূর্ণ হওয়া পর্যন্ত ট্রাফিক বাড়ান।

প্রত্যাশিত ও বৃদ্ধির পরীক্ষা পরিকল্পিত লঞ্চে জায়গা আছে কি না জানায়। চাপের পরীক্ষা বলে কোনটি আগে ভাঙে এবং ব্যবহারকারীর কাছে ব্যর্থতা কেমন দেখায়। শেষ উত্তরটি গুরুত্বপূর্ণ, কারণ বাড়তি কাজ দ্রুত প্রত্যাখ্যান করা সার্ভিস চালানো সহজ। সব ডেটাবেস কানেকশন নিয়ে অসংশ্লিষ্ট রুট আটকে দেওয়া সার্ভিস চালানো কঠিন।

ক্যাপাসিটি পরীক্ষায় ওপেন ওয়ার্কলোড মডেল ব্যবহার করুন। আগের অনুরোধ ধীর হলেও ওপেন মডেল নির্দিষ্ট আগমন হারে নতুন অনুরোধ শুরু করে। নির্দিষ্ট ভার্চুয়াল ব্যবহারকারীসহ ক্লোজড মডেল প্রায়ই পতন লুকায়: উত্তর ধীর হলে ওই ব্যবহারকারীরা কম নতুন অনুরোধ পাঠায়, তাই সার্ভিস সমস্যায় পড়ার ঠিক সময়েই দেওয়া লোড কমে যায়। Grafana-এর k6 নথি আগমন-হার এক্সিকিউটরের মাধ্যমে এই পার্থক্য বোঝায় এবং জেনারেটর নির্ধারিত কাজ শুরু করতে না পারলে dropped_iterations জানায়। বাদ পড়া iteration-কে টেস্ট জেনারেটরের ব্যর্থতা ধরুন, সার্ভারের সাফল্য নয়।

ওয়ার্ম-আপের পর প্রতিটি স্থির মাত্রা অন্তত 30 মিনিট চালান। পাঁচ মিনিটের পরীক্ষা কানেকশন বদল, গার্বেজ কালেকশন চক্র, ক্যাশ অপসারণ, ব্যাকগ্রাউন্ড জব ও ধীরে মেমরি বাড়া মিস করে। ছোট পরীক্ষা পাস করলে প্রত্যাশিত সর্বোচ্চে আলাদা দুই ঘণ্টার soak test চালান।

ছোট একটি ওয়ার্কশিটে রূপান্তরটি লিখুন এবং প্রতিটি একক দেখান। মাসিক ব্যবহারকারী, প্রতি ব্যবহারকারীর সেশন ও প্রতি সেশনের অনুরোধ গুণ করলে মাসিক অনুরোধ পাওয়া যায়। ট্রাফিককে কাজের দিন ও ব্যস্ততম ঘণ্টায় ভাগ করার পরেই ভাগ করুন। এরপর retry ট্রাফিক, ব্যাকগ্রাউন্ড জব, webhook এবং user analytics-এ না ধরা polling যোগ করুন। প্রতি দশ সেকেন্ডে polling করা frontend, পৃষ্ঠা খোলা click-এর চেয়েও বেশি API কাজ তৈরি করতে পারে।

স্থির সর্বোচ্চ থেকে burst আলাদাভাবে মডেল করুন। নোটিফিকেশনের পর login, import শেষ হওয়া বা ছোট outage-এর পর client retry এক মিনিটে কাজ জমাতে পারে। এমন burst stage যোগ করুন যা দ্রুত প্রত্যাশিত burst rate-এ যায়, queue পূর্ণ হওয়ার মতো সময় ধরে থাকে এবং পরে স্বাভাবিকে ফেরে। বাড়তে থাকা backlog বা manual restart ছাড়া সার্ভিসকে ফিরতে হবে। recovery time ফলাফলে রাখুন। অল্প burst-এর পর pool বা worker আটকে থাকলে steady test পাস করেও system অনিরাপদ থাকে।

চূড়ান্ত হারকে ইচ্ছামতো safety factor দিয়ে গুণ করে বাস্তব বলবেন না। margin-কে business uncertainty-র সঙ্গে বাঁধুন: forecast error, planned campaign, একটি replica বন্ধ থাকা বা capacity যোগ করতে লাগা সময়। যে অনুমানের ওপর নির্ভর করবেন, প্রতিটি পরীক্ষা করুন। ফলাফলের সঙ্গে worksheet রাখুন, কারণ কোন forecast ও burst assumption target বানিয়েছিল তা কেউ মনে না রাখলে pass করা run-এর অর্থ থাকে না।

ট্রাফিকের অনুপাতকে বাস্তব সেশনের মতো হতে হবে

বাস্তবসম্মত লোড টেস্ট রুটের ঘনত্ব, payload size, authentication, data distribution, think time ও write contention ধরে রাখে। /health-এ প্রতি সেকেন্ডে 100টি hit করলে health handler মাপে, application নয়।

সম্ভব হলে access log থেকে অনুপাত বানান। raw URL-এর বদলে business action অনুযায়ী route group করুন, কারণ /projects/123/projects/456 একই shape। শুরুর SaaS পণ্যের একটি সম্ভাব্য mix হতে পারে list ও detail read-এ 45 শতাংশ, search-এ 20 শতাংশ, create বা update-এ 15 শতাংশ, login ও token refresh-এ 10 শতাংশ এবং export বা অন্য ভারী কাজে 10 শতাংশ। আপনার সংখ্যা product flow থেকে আসবে, এই উদাহরণ থেকে নয়।

অনেক test account ও record ব্যবহার করুন। একটি account পুনরায় ব্যবহার করলে অবাস্তব hot cache তৈরি হতে পারে, এক row-এর update ধারাবাহিক হতে পারে বা প্রকৃত traffic ছড়িয়ে দিত এমন rate limit চালু হতে পারে। ছোট, মাঝারি ও বড় tenant seed করুন। missing record, invalid input ও authorization failure রাখুন, কারণ error path সফল path-এর চেয়ে আলাদাভাবে database query বা response body allocate করতে পারে।

বড় upload ও দীর্ঘ export-এর service target আলাদা হলে নিজস্ব scenario-তে রাখুন। তবু স্বাভাবিক traffic-এর সঙ্গে একসঙ্গে চালান। না হলে ব্যবহারকারী যে ঘটনাটি টের পান সেটিই বাদ যাবে: এক ধরনের export pool দখল করে আর একটি সাধারণ settings page তার পেছনে অপেক্ষা করে।

চূড়ান্ত capacity run-এ PostgreSQL, object storage, queue বা outbound service mock করবেন না। handler cost আলাদা করতে mock কাজে লাগে, কিন্তু capacity নির্ধারণের সম্ভাব্য dependency সরিয়ে দেয়। production-এর মতো instance size, database setting, index, connection limit ও network path-সহ staging stack-এ test চালান। একই রকম হাজার seed row-এর চেয়ে পরিষ্কার করা production-shaped data ভালো।

API সাধারণত cache না হলে content delivery cache-এর ভেতর দিয়ে test করবেন না। production সত্যি cache ব্যবহার করলে সেটি path-এ রাখুন। উদ্দেশ্য backend-কে ব্যস্ত দেখানো নয়। user request বাস্তবে যে কাজ বানায় তা পুনরায় তৈরি করাই উদ্দেশ্য।

চালানো যায় এমন k6 পরীক্ষা চুক্তি প্রকাশ করবে

threshold ও traffic stage version control-এ রাখুন, যাতে test run পরে ব্যাখ্যা করা screenshot না হয়। k6 threshold-কে pass বা fail মানদণ্ড হিসেবে ধরে এবং fail হলে nonzero code দিয়ে বের হয়। তাই ফল release check-এ ব্যবহার করা যায়।

নিচের skeleton arrival rate-এ mixed session চালায়, response semantics যাচাই করে এবং সাধারণ read ও ভারী export-এর জন্য আলাদা latency limit রাখে। route, payload ও target-কে পণ্যের জন্য সম্মত মান দিয়ে বদলান। code ইচ্ছা করেই সহজ, যাতে failed check সরাসরি user action-এর সঙ্গে মেলে।

import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate } from 'k6/metrics';

const businessErrors = new Rate('business_errors');

export const options = {
  scenarios: {
    expected_peak: {
      executor: 'ramping-arrival-rate',
      startRate: 5,
      timeUnit: '1s',
      preAllocatedVUs: 40,
      maxVUs: 200,
      stages: [
        { target: 10, duration: '5m' },
        { target: 10, duration: '30m' },
        { target: 20, duration: '10m' },
        { target: 20, duration: '30m' },
      ],
    },
  },
  thresholds: {
    'http_req_duration{name:project_list}': ['p(95)<300'],
    'http_req_duration{name:project_create}': ['p(95)<500'],
    'http_req_duration{name:export}': ['p(95)<2000'],
    http_req_failed: ['rate<0.01'],
    business_errors: ['rate<0.005'],
    dropped_iterations: ['count==0'],
  },
};

export function setup() {
  const response = http.post(`${__ENV.BASE_URL}/api/login`, JSON.stringify({
    email: __ENV.TEST_EMAIL,
    password: __ENV.TEST_PASSWORD,
  }), { headers: { 'Content-Type': 'application/json' } });
  check(response, { 'login succeeds': r => r.status === 200 });
  return { token: response.json('token') };
}

export default function (data) {
  const headers = { Authorization: `Bearer ${data.token}`, 'Content-Type': 'application/json' };
  const list = http.get(`${__ENV.BASE_URL}/api/projects?limit=25`, {
    headers,
    tags: { name: 'project_list' },
  });
  businessErrors.add(!check(list, {
    'list status is 200': r => r.status === 200,
    'list has items': r => Array.isArray(r.json('items')),
  }));

  if (Math.random() < 0.25) {
    const create = http.post(`${__ENV.BASE_URL}/api/projects`, JSON.stringify({
      name: `load-${__VU}-${__ITER}`,
    }), { headers, tags: { name: 'project_create' } });
    businessErrors.add(!check(create, { 'create status is 201': r => r.status === 201 }));
  }

  if (Math.random() < 0.03) {
    const runExport = http.post(`${__ENV.BASE_URL}/api/exports`, '{}', {
      headers,
      tags: { name: 'export' },
    });
    businessErrors.add(!check(runExport, { 'export accepted': r => r.status === 202 }));
  }

  sleep(Math.random() * 2 + 1);
}

নমুনার লক্ষ্য শুরু করার মান, সবার জন্য প্রতিশ্রুতি নয়। ব্যবহারকারী কত দেরি সহ্য করতে পারেন এবং পণ্যের নিজস্ব চাহিদা দেখে প্রতি route class-এর p95 ঠিক করুন। asynchronous export ও autocomplete request-কে একটি global 300 ms threshold দিয়ে বিচার করবেন না।

application host করে না এমন machine থেকে script চালান। generator-এর spare CPU আছে এবং dropped iteration নেই নিশ্চিত করুন। exact commit, environment configuration, data snapshot identifier, command ও raw test output সংরক্ষণ করুন। এগুলো ছাড়া পরের comparison মূলত স্মৃতি ও আশাবাদ।

দীর্ঘ run বিশ্বাস করার আগে generator calibrate করুন। database work ছাড়া ছোট handler-এ পাঠিয়ে পরিকল্পিত test-এর চেয়ে বেশি arrival rate দিন এবং নিজের CPU, socket বা network শেষ না করে rate ধরে রাখতে পারে কি না দেখুন। k6 virtual user যোগ করলে বা dropped iteration জানালে load machine limit হতে পারে। একটি machine দরকারি কাজ দিতে না পারলেই শুধু একাধিক machine-এ generation ভাগ করুন, এবং server ও client graph মেলাতে clock synchronize করুন।

প্রতিটি run-এর শান্ত baseline রাখুন। production peak-এ না চললে migration, data import ও unrelated staging job বন্ধ করুন। তারপর বাস্তব background work চালু রেখে দ্বিতীয় test schedule করুন। এই জোড়া API-এর clean capacity ও user পাওয়া operating capacity দেখায়। শুধু শান্ত run পাস করলে launch plan একটি মিথ্যা staging অবস্থার ওপর নির্ভর করছে।

load যোগ করার আগে এক user journey-র জন্য দ্বিতীয় script এক virtual user দিয়ে চালান। প্রতিটি response, তৈরি record ও cleanup action দেখুন। এতে খারাপ token, সবসময় true দেওয়া check বা প্রথম iteration-এর পর colliding test data ধরা পড়ে। script error page চালালে বা একই cached object বারবার পড়লে capacity result অর্থহীন।

p95-এর সঙ্গে রুটের প্রসঙ্গ দরকার

average ধীর minority লুকায় বলে p95 ব্যবহার করুন, তবে একা p95 পড়বেন না। p95 800 ms হলে প্রতি বিশটি অনুরোধের একটি অন্তত এত সময় নেয়, ফলে বহু request-সহ page নিয়মিত ধীর লাগতে পারে। খুব কম sample-এর route-এ percentile অস্থির হয়, তাই পাশে request count দিন।

প্রতিটি নাম দেওয়া business action-এর p50, p95, p99, maximum, throughput ও error rate রাখুন। p50 সাধারণ আচরণ দেখায়, p95 ব্যবহারযোগ্য service gate এবং p99 একটি maximum-কে আলোচনার কেন্দ্র না বানিয়ে tail দেখায়। status code অনুযায়ী result ভাগ করুন। দ্রুত 500 response latency-কে ভালো দেখাতে পারবে না।

client-observed duration-এর সঙ্গে server-side handler duration মাপুন। ফাঁকে connection setup, proxy, network time ও response transfer থাকে। client p95 বাড়লেও handler p95 স্থির থাকলে handler-এর বাইরে দেখুন। দুটোই বাড়ে এবং database wait time ওঠে, তাহলে request সম্ভবত connection বা query-র জন্য queue-তে।

warm-up ও steady-state result আলাদা রাখুন। deploy করা Go binary-তে compilation হয় না, কিন্তু cold cache, নতুন database connection, lazy initialization ও autoscaling প্রথম মিনিটগুলো বিকৃত করতে পারে। user cold behavior-ও দেখেন, তাই মুছে না দিয়ে আলাদা result রাখুন।

resource accounting-এ average কাজে লাগে। calls দিয়ে total database time ভাগ করলে মাঝারি ধীর ও খুব frequent query ধরা পড়ে। তবে এটি percentile service target-এর বিকল্প নয়। ক্ষেত্রটি latency ও capacity প্রায়ই গুলিয়ে ফেলে: latency শেষ হওয়া কাজের সময় বলে, capacity বলে queue বা error না বাড়িয়ে service কত offered work ধরে রাখতে পারে। কম achieved request rate-এ ভালো latency capacity প্রমাণ করে না।

run-এর আগে failure নির্ধারণ করুন। কোনো critical route p95 target miss করলে, unexpected HTTP failure agreed rate ছাড়ালে, business check fail করলে, scheduled iteration drop হলে বা resource saturated থাকলে আমি launch test fail করব। পাঁচটির চারটি gate পাস মানে useful diagnostic data-সহ failed run।

কানেকশনের অপেক্ষা লুকানো ডেটাবেস queue দেখায়

generated backend-এর মালিক থাকুন
traffic বাড়লে source export performance instrumentation ও load-test repair আপনার নিয়ন্ত্রণে রাখে।

পরীক্ষার আগে database/sql instrument করুন, কারণ application latency PostgreSQL ধীর নাকি application সেখানে পৌঁছাতে অপেক্ষা করছে তা বলে না। Go-এর DB.Stats() closure counter-সহ OpenConnections, InUse, Idle, WaitCountWaitDuration দেয়। কয়েক সেকেন্ড পরপর metrics system-এ export করুন।

func recordDBStats(ctx context.Context, db *sql.DB, g GaugeSet) {
    ticker := time.NewTicker(5 * time.Second)
    defer ticker.Stop()

    for {
        select {
        case <-ctx.Done():
            return
        case <-ticker.C:
            s := db.Stats()
            g.Set("db_open_connections", float64(s.OpenConnections))
            g.Set("db_in_use_connections", float64(s.InUse))
            g.Set("db_idle_connections", float64(s.Idle))
            g.Set("db_wait_count_total", float64(s.WaitCount))
            g.Set("db_wait_seconds_total", s.WaitDuration.Seconds())
        }
    }
}

steady window-তে cumulative counter-এর পরিবর্তন হিসাব করুন। WaitCount বাড়া মানে request free connection-এর জন্য অপেক্ষা করেছে। WaitDuration-এর পরিবর্তনকে WaitCount-এর পরিবর্তন দিয়ে ভাগ করলে ওই সময়ের average pool wait পাওয়া যায়। InUse-কে MaxOpenConnections-এর সঙ্গে graph করুন। limit-এ flat line ও বাড়তে থাকা wait pool saturation দেখায়।

graph ভালো করতে সঙ্গে সঙ্গে SetMaxOpenConns বাড়াবেন না। জনপ্রিয় এই fix queue-কে PostgreSQL-এ সরায় এবং contention, memory use ও query latency বাড়াতে পারে। আগে দেখুন connection কেন busy থাকে: slow query, network call-এর সময় খোলা transaction, একবারে একটি row পড়া বা ভুলে যাওয়া Rows.Close()। তারপর সব application replica ও worker মিলিয়ে database connection budget-এর মধ্যে pool size দিন।

Go documentation বলে nonpositive SetMaxOpenConns pool-কে unlimited রাখে। অনেক replica একসঙ্গে connection খুলতে পারলে unlimited production default বিপজ্জনক। explicit limit দিন, idle ও lifetime behavior সচেতনভাবে ঠিক করুন এবং migration, administration ও background work-এর জন্য database capacity রাখুন।

transaction duration আলাদাভাবে track করুন। handler 200 ms-এ ফিরলেও deferred cleanup বা leaked transaction আরও বেশি সময় connection ধরে রাখতে পারে। pool metrics চাপ দেখায়, trace বা transaction timing মালিক দেখায়।

ধীর কোয়েরির প্রমাণ PostgreSQL থেকে আসবে

পরীক্ষার environment-এ pg_stat_statements চালু করে প্রতি run-এর আগে ও পরে snapshot নিন। PostgreSQL documentation এটিকে normalized statement-এর planning ও execution statistics tracker বলে। view-তে call, row, total ও mean execution time, block activity ও temporary block activity থাকে। একটি trace-এ দেখা query থেকে অনুমান করার চেয়ে এ প্রমাণ ভালো।

view cumulative বলে snapshot-এর delta ব্যবহার করুন। isolated test database ছাড়া reset করবেন না, কারণ reset অন্য কাজের evidence মুছে দেয়। নিচের query পরিষ্কার test window-তে সবচেয়ে বেশি execution time নেওয়া statement বের করে:

SELECT
  queryid,
  calls,
  round(total_exec_time::numeric, 1) AS total_ms,
  round(mean_exec_time::numeric, 2) AS mean_ms,
  rows,
  shared_blks_read,
  temp_blks_written
FROM pg_stat_statements
WHERE calls > 20
ORDER BY total_exec_time DESC
LIMIT 20;

total time ঘন ঘন চলা ও database work দখল করা query দেখায়। mean time আলাদাভাবে ধীর statement দেখায়। কোনোটিই query p95 দেয় না, কারণ pg_stat_statements call aggregate করে। একটি statement-এর tail latency দরকার হলে trace বা duration histogram ব্যবহার করুন। slow query বলতে high average execution, high tail time বা frequent call থেকে বিশাল total cost বোঝাতে পারে। প্রতিটির fix আলাদা।

timed load test-এর বাইরে safe ও representative parameter দিয়ে top statement-এ EXPLAIN (ANALYZE, BUFFERS) চালান। ANALYZE statement execute করে, তাই data-changing statement rollback করা transaction-এ রাখুন বা disposable copy-তে দেখুন। estimate ও actual row-এর বড় ফারাক, repeated loop, বড় selective table-এ sequential scan, disk-এ spill করা sort ও অনেক shared block read খুঁজুন।

generated code প্রায়ই seed data-তে ঠিক দেখানো N+1 pattern বানায়: 25 project আনে, তারপর প্রতি project-এর জন্য owner query ও count query চালায়। প্রতি সেকেন্ডে দশ request-এ অন্য route চলার আগেই ওই endpoint 500-এর বেশি database statement বানাতে পারে। join, batch WHERE id = ANY($1) বা precomputed count fix হতে পারে। pool limit বাড়ালে অপচয় থেকেই যায়।

lock wait-ও capture করুন। একা দ্রুত query একই tenant, account বা sequence-এ concurrent update-এর সময় আটকে যেতে পারে। শুধু write scenario-তে latency লাফালে reflex হিসেবে index না যোগ করে active wait ও transaction boundary দেখুন।

স্থির লোডে মেমরিকে স্থির হতে হবে

checkpoint নিয়ে code বদলান
pool pressure, query fan-out বা unbounded response memory ঠিক করার আগে snapshot নিন।

একটি peak দিয়ে নয়, সময়ের সঙ্গে shape দেখে memory বিচার করুন। warm-up-এ বাড়ার পর stable level-এর চারপাশে ওঠানামা করা Go process, দুই ঘণ্টার soak জুড়ে post-GC baseline বাড়া process থেকে আলাদা।

process resident memory, Go heap allocation, heap object, goroutine count, garbage collection frequency, pause time ও allocation rate record করুন। container memory জরুরি, কারণ operating system Go heap নয়, limit দেখে process kill করে। একই load-এ garbage collection-এর পর memory compare করুন। এতে সাধারণ sawtooth কমে retention দেখা যায়।

standard Go runtime metric বা protected profiling endpoint staging-এ expose করুন। soak-এর শুরু ও শেষের কাছে heap profile নিয়ে go tool pprof-এ retained allocation site তুলনা করুন। goroutine profile-ও নিন। বাড়তে থাকা goroutine channel-এ আটকা request, বন্ধ না হওয়া response body বা cancellation ছাড়া শুরু background work দেখাতে পারে।

GOMEMLIMIT ঠিক container limit-এ রাখবেন না। process-এর goroutine stack, executable mapping, database driver buffer ও non-heap allocation-এর জন্য memory দরকার। headroom রাখুন এবং বড় payload-এ প্রমাণ করুন। ছোট JSON body-র test, memory-তে 20 MB upload পড়া endpoint সম্পর্কে কমই বলে।

অস্বস্তিকর case চালান: সর্বোচ্চ accepted request body, বড় query result, cancelled client, timeout ও repeated export। কাজ শেষে memory ফিরে আসে নিশ্চিত করুন। CPU-ও দেখুন, কারণ heavy garbage collection memory limit-এর নিচে রেখেও latency নষ্ট করতে পারে।

বাস্তব pass condition ceiling ও trend মিলিয়ে নেয়। growth peak-এ resident memory deployment limit-এর নিরাপদ নিচে থাকতে হবে, তারপর soak-এ post-collection baseline ও goroutine count বাড়া বন্ধ হতে হবে। universal safe percentage নেই। platform restart behavior, traffic variance ও অন্য replica restart নিতে পারবে কি না দেখে headroom বাছুন।

ব্যর্থতার মধ্যে ভুল উত্তর ও overload আচরণ থাকবে

generation-এর আগে capacity plan করুন
code বদলানোর আগে pool limit, timeout ও observable failure behavior planning mode-এ লিখুন।

transport failure, HTTP status failure, timeout, panic ও wrong response আলাদা গুনুন। k6 response callback অনুযায়ী http_req_failed failed HTTP request ধরে, কিন্তু empty list, duplicate charge বা missing record-সহ 200 response-ও failure। এ জন্য sample content check থেকে business_errors দেয়।

invalid input বা intentional rate limit-এর মতো expected rejection tag করুন, যাতে unexpected error rate নোংরা না হয়। তারপর contract assert করুন: status ঠিক, body bounded এবং rejection দ্রুত। overload system-এর 503 দিতে 30 second নেওয়া উচিত নয়।

panic recovery, context deadline error, connection acquisition delay, PostgreSQL serialization failure ও cancelled query-র জন্য server log দেখুন। full message নয়, stable cause দিয়ে error group করুন, যাতে identifier হাজার category না বানায়। প্রথম occurrence ও high-latency tail-এর representative trace রাখুন।

clean capacity test-এর পর একটি degradation test চালান। available database connection কমান, outbound dependency-তে controlled latency যোগ করুন বা traffic চলার সময় একটি application replica restart করুন। শুধু isolated test environment-এ করুন। timeout, cancellation ও health check failure-কে contain করে, queue-কে সব resource নিতে দেয় না, এটি নিশ্চিত করাই লক্ষ্য।

HTTP server স্পষ্টভাবে configure করুন। Go-এর net/http documentation বলে field অনুযায়ী zero বা negative ReadTimeout, WriteTimeoutIdleTimeout মানে timeout না-ও থাকতে পারে। generated service প্রায়ই default-সহ http.ListenAndServe call করে এবং সিদ্ধান্ত নেয় না। custom http.Server, request-scoped deadline ও bounded body size ধীর client ও stuck dependency-কে চিরকাল resource ধরে রাখতে বাধা দেয়।

run-এর পর correctness review করুন। তৈরি record গুনুন, client retry করলে idempotency যাচাই করুন, background job ঠিক একবার শেষ হয়েছে নিশ্চিত করুন এবং failed request-এর partial state রয়ে যায়নি দেখুন। আমার project-এ load test চতুর code review-এর চেয়ে বেশি duplicate-work bug ধরেছে।

প্রথম সীমা থেকেই নিয়োগের সিদ্ধান্ত আসে

expected ও growth test বারবার pass করলে, soak stable memory baseline-এ পৌঁছালে, database queue নিয়ন্ত্রিত থাকলে এবং team প্রথম stress failure বোঝাতে পারলে backend engineer ছাড়া launch করা যায়। একটি ভাগ্যবান green run evidence নয়। একই commit অন্তত তিনবার চালিয়ে বড় variance তদন্ত করুন।

প্রতিটি run-এর ছোট result record রাখুন:

  • replica size ও database configuration-সহ commit ও environment।
  • dataset scale, traffic mix, arrival rate ও test duration।
  • route p95 ও p99, achieved throughput, business failure ও dropped iteration।
  • peak CPU ও memory, post-collection memory trend ও goroutine trend।
  • pool wait, total time অনুযায়ী top SQL, lock wait ও দেখা breaking point।

rising pool wait, retained memory, lock contention বা inconsistent write কেউ বোঝাতে না পারলে launch-এর আগে backend help নিয়োগ বা contract করুন। test চালাতে পারা একমাত্র ব্যক্তি generated code নিরাপদে বদলাতে না পারলেও নিয়োগ করুন। এটি ownership gap, request-per-second threshold নয়।

failed test মানেই full-time hire নয়। missing index, N+1 query বা unbounded export সীমিত repair হতে পারে। transaction design, observability, cancellation ও deployment behavior জুড়ে repeated failure চলমান engineering work দেখায়। পার্থক্য হলো আপনি একটি defect পেয়েছেন, নাকি system behavior-এর কোনো owner নেই বুঝেছেন।

Koder.ai একটি Go backend generate ও export করতে, deploy করতে এবং rollback-এর জন্য snapshot রাখতে পারে, কিন্তু generation capacity planning বাতিল করে না। load script ও observability change source-এর সঙ্গে রাখুন, যাতে গুরুত্বপূর্ণ backend change একই contract pass করে।

business-কে 10,000 monthly user নিরাপদ বলে প্রতিশ্রুতি দেবেন না। measured arrival rate, route mix, latency target, error budget ও resource envelope প্রতিশ্রুতি দিন। product বদলালে input বদলে test আবার চালান।

সাধারণ প্রশ্ন

একটি Go backend কি মাসে 10,000 সক্রিয় user সামলাতে পারে?

প্রায়ই পারে, কিন্তু monthly active user backend load বোঝায় না। forecast-কে peak requests per second ও route mix-এ বদলে explicit latency, error, database ও memory limit-এর বিরুদ্ধে test করুন।

মাসে 10,000 user কত request per second?

স্থির conversion নেই। sessions per user, requests per session, busiest window-এর traffic share এবং user-কে এক সময়ে আনা event জানতে হবে।

Go API-এর গ্রহণযোগ্য p95 latency কত?

language বা framework নয়, user action অনুযায়ী target দিন। interactive read-এ কয়েকশ millisecond লাগতে পারে, accepted background work-এর target আলাদা হতে পারে, তবে result দেখার আগে team-কে সংখ্যা বাছতে হবে।

backend load test কতক্ষণ চলবে?

warm-up-এর পর প্রতিটি steady load অন্তত 30 minute ধরে রাখুন, তারপর expected peak-এ দীর্ঘ soak চালান। short test connection churn, retained memory, background work ও gradual queue growth miss করতে পারে।

virtual user নাকি arrival rate ব্যবহার করব?

capacity claim-এর জন্য arrival-rate model ব্যবহার করুন, কারণ service slow হলেও এটি কাজ দিতে থাকে। fixed virtual-user model slowdown-এর সময় request rate কমিয়ে queue বাড়ার point লুকাতে পারে।

Go database connection pool শেষ হওয়া কীভাবে ধরব?

DB.Stats() export করে InUse, OpenConnections, WaitCountWaitDuration দেখুন। in-use connection configured maximum-এ থেকে wait counter বাড়লে request pool-এর জন্য queue করছে।

request অপেক্ষা করলে Go SQL pool বাড়াব?

connection কেন occupied এবং PostgreSQL-এর capacity কত জানার আগে নয়। বড় pool queue-কে database-এ সরিয়ে contention আরও খারাপ করতে পারে।

load test-এ slow PostgreSQL query কীভাবে পাব?

pg_stat_statements-এর before-and-after snapshot নিয়ে query delta total ও mean execution time অনুযায়ী rank করুন। aggregate view per-query p95 দেয় না, তাই tail latency-র জন্য representative trace বা histogram ব্যবহার করুন।

Go service-এ memory leak আছে কীভাবে বুঝব?

steady soak চালিয়ে একই load-এ garbage collection-এর পর memory compare করুন। heap object বা goroutine-এর সঙ্গে baseline বাড়তে থাকলে heap ও goroutine profile তুলনা করা দরকার।

startup-এর backend engineer কখন নিয়োগ করা উচিত?

performance failure database design, observability, concurrency, correctness বা operations-এ চলমান ownership দরকার দেখালে নিয়োগ করুন। একটি index বা query fix full-time role নাও চাইতে পারে, কিন্তু load-এ unexplained behavior চায়।

Related posts