最初のバックエンド採用前に行うGo負荷テスト
Go負荷テストで現実的なトラフィックを再現し、p95レイテンシ、DB負荷、メモリ、失敗率を測ってから採用を判断します。

月間アクティブユーザー1万人は、キャパシティ要件ではありません。請求や分析に使う数字です。リクエストが軽く、1か月を通して分散していれば、Goサービスはそれ以上のユーザーを処理できます。一方、各セッションが遅いクエリ、アップロード、外部サービスへの呼び出しを大量に発生させるなら、数百人でも停止します。
確認すべきなのは、生成されたバックエンドが予想ピークトラフィックで定めたサービス目標を満たし、成長と通常の障害に備える余裕を残せるかです。バックエンドエンジニアを採用する前でも答えは出せます。ただし、負荷テストが実際の製品に似ており、API、Goランタイム、PostgreSQLの動きを同時に記録する必要があります。平均レイテンシが緑色のグラフだけでは、ほとんど何も証明できません。
生成されたGoバックエンドが月間アクティブユーザー1万人に対応できると創業者へ伝える前に、私はこのテストを求めます。再現可能な合否を出し、最初のボトルネックを示し、キャパシティの問題と正しさの問題を切り分けるテストです。
月間ユーザーをピーク時のリクエストに変換する
負荷レベルを選ぶ前に、ユーザー予測を1秒あたりのリクエスト数へ変換します。月間アクティブユーザー数は、バックエンドを動かす二つの変数を隠します。最も混む時間帯に何セッションが到着するかと、各セッションがどれだけの処理を生むかです。
限定ベータのトラフィックがあるなら、観測データから始めます。最も混雑した15分間のセッション数、セッションあたりのリクエスト数、ルート構成を数えてください。まだトラフィックがなければ、誰でも異議を唱えられる形で仮定を書きます。たとえば、1万人が月8回のセッションを作り、1セッションでAPIを15回呼び、日次トラフィックの20パーセントが最も混む1時間に集中するとします。通常の繁忙日なら約8リクエスト毎秒です。ローンチ、通知、給与処理の締め日、共通のタイムゾーンによって、実際のピークは数倍になり得ます。
この計算を見せかけの精密さに変えてはいけません。三つのテスト水準を決めるために使います。
- 予想ピーク: 現時点で予測する最大負荷。
- 成長ピーク: 事業側により良い予測がなければ、予想ピークの2倍。
- ストレス水準: サービス目標が失敗するか、リソースが飽和するまでトラフィックを増やす。
予想ピークと成長ピークのテストは、計画中のローンチに余裕があるかを答えます。ストレステストは、最初に何が壊れ、ユーザーにどう見えるかを示します。余剰処理を素早く拒否するサービスは、DB接続をすべて消費して無関係なルートまで止めるサービスより運用しやすいため、この答えは重要です。
キャパシティテストにはオープンワークロードモデルを使います。過去のリクエストが遅くなっても、固定の到着率で新しいリクエストを開始するモデルです。固定数の仮想ユーザーを使うクローズドモデルは、崩壊を隠すことがあります。レスポンスが遅くなると仮想ユーザーは新しいリクエストを送る回数を減らすため、サービスが苦しむ瞬間に提示負荷も下がるからです。Grafanaのk6ドキュメントは到着率executorでこの違いを表し、生成側が予定処理を開始できない場合はdropped_iterationsを報告します。欠落したiterationはサーバーの成功ではなく、テスト生成側の失敗です。
ウォームアップ後、各安定水準を少なくとも30分間維持します。5分間のテストでは、接続の入れ替わり、ガベージコレクション周期、キャッシュ追い出し、バックグラウンドジョブ、徐々に増えるメモリを見逃します。短いテストに合格したら、予想ピークで2時間のソークテストも行います。
小さな計算表に変換式を書き、すべての単位を残します。月間ユーザー数にユーザーあたりのセッション数とセッションあたりのリクエスト数を掛けると、月間リクエスト数になります。稼働日と最繁忙時間にトラフィックを割り当ててから除算してください。ユーザー分析に含まれない再試行、バックグラウンドジョブ、webhook、ポーリングも加えます。10秒ごとにポーリングするフロントエンドは、ページを開いたクリックより多くのAPI処理を生むことがあります。
安定ピークと瞬間的な急増は別にモデル化します。通知後のログイン、インポート完了、短い停止後のクライアント再試行は、処理を1分間に集中させます。予想する急増率へ素早く到達し、キューが埋まるまで維持し、その後通常へ戻る段階を加えます。サービスは、増え続ける未処理や手動再起動なしで回復しなければなりません。回復時間も結果に残します。短い急増でプールやワーカーが詰まるなら、安定テストに合格しても安全ではありません。
最終レートに根拠のない安全係数を掛けて現実的と呼んではいけません。予測誤差、予定キャンペーン、1台のレプリカ停止、キャパシティ追加にかかる時間など、事業上の不確実性に余裕を結び付けます。依存する仮定はすべてテストしてください。計算表を結果と一緒に保管します。どの予測と急増仮定が目標を作ったか誰も覚えていなければ、合格結果の意味が失われます。
トラフィック構成は実際のセッションに似せる
現実的な負荷テストは、ルート頻度、payloadサイズ、認証、データ分布、操作の間隔、書き込み競合を保ちます。/healthを毎秒100回呼んでも、ヘルスハンドラを測るだけでアプリケーションは測れません。
可能ならアクセスログから構成を作ります。生のURLではなく業務操作でルートをまとめてください。/projects/123と/projects/456は同じ形です。初期SaaSの一例なら、一覧と詳細の読み取りに45パーセント、検索に20パーセント、作成や更新に15パーセント、ログインとトークン更新に10パーセント、エクスポートなどの重い処理に10パーセントを割り当てます。実際の数値はこの例ではなく、製品フローから決めます。
多数のテストアカウントとレコードを使います。一つのアカウントを繰り返すと、現実にはないホットキャッシュができ、同じ行への更新が直列化し、本物のトラフィックなら分散するレート制限に当たることがあります。小規模、中規模、大規模のテナントを用意します。存在しないレコード、不正入力、認可失敗も含めてください。エラーパスは成功パスと異なるDBクエリやレスポンス割り当てを行うことがあります。
大容量アップロードや長いエクスポートに別のサービス目標があるなら、専用シナリオへ分けます。それでも通常トラフィックと同時に実行してください。そうしないと、エクスポートがプールを占有し、単純な設定ページが後ろで待つという、ユーザーが気付く障害を見逃します。
最終キャパシティテストでPostgreSQL、オブジェクトストレージ、キュー、外部サービスをモックにしないでください。モックはハンドラコストの分離には役立ちますが、キャパシティを決める可能性が高い依存先を消します。本番と同じインスタンスサイズ、DB設定、インデックス、接続上限、ネットワーク経路を持つステージングでテストします。同じseed行を千件並べるより、本番に似た形へ整えたデータが有効です。
APIが通常キャッシュされないなら、コンテンツ配信キャッシュを通してテストしないでください。本番でキャッシュを使うなら、実物を経路に残します。バックエンドを忙しく見せることが目的ではありません。ユーザーリクエストが実際に作る処理を再現することが目的です。
実行可能なk6テストに契約を記述する
thresholdとトラフィック段階をバージョン管理し、テスト結果を後から解釈するスクリーンショットにしないでください。k6はthresholdを合否基準として扱い、失敗すると非ゼロの終了コードを返します。そのため、リリース検査に使えます。
次の骨組みは到着率で混合セッションを実行し、レスポンスの意味を検査し、通常の読み取りと重いエクスポートに別々のレイテンシ上限を置きます。ルート、payload、目標値は製品で合意したものへ置き換えてください。失敗した検査をユーザー操作に結び付けられるよう、コードは意図的に単純にしています。
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);
}
サンプルの目標値は出発点であり、一般的な約束ではありません。ユーザーが許容できる待ち時間と製品要件から、ルート分類ごとにp95を決めます。非同期エクスポートと入力補完を、同じ300 msの全体thresholdで評価してはいけません。
アプリケーションをホストしていないマシンからスクリプトを実行します。生成側にCPUの余裕があり、iterationの欠落がないことを確認してください。正確なcommit、環境設定、データsnapshotの識別子、コマンド、生の出力を保存します。これらがなければ、後の比較は記憶と楽観に頼ることになります。
長時間実行を信頼する前に生成側を校正します。DB処理のない小さなハンドラへ向け、予定テストを超える到着率に上げ、自身のCPU、socket、ネットワークを使い切らず維持できるか確認します。k6が仮想ユーザーを追加したり、iteration欠落を報告したりするなら、負荷マシンが限界かもしれません。1台で必要な処理を提示できない場合だけ複数台に分散し、サーバーとクライアントのグラフが一致するよう時計を同期します。
各実行に静かな基準を用意します。本番ピークでも動くものを除き、migration、データimport、無関係なステージングジョブを止めます。その後、実際のバックグラウンド処理を有効にして2回目を実行します。この組み合わせが、API単体の容量とユーザーが受ける運用上の容量を示します。静かな実行だけが合格するなら、ローンチ計画は架空のステージング状態に依存しています。
一つのユーザー行動だけを実行する別スクリプトを作り、負荷を加える前に仮想ユーザー1人で動かします。すべてのレスポンス、作成レコード、クリーンアップを調べます。不正なtoken、常にtrueになる検査、最初のiteration後に衝突するテストデータを発見できます。エラーページを試していたり、同じキャッシュオブジェクトだけを読んでいたりするなら、キャパシティ結果に意味はありません。
p95にはルート単位の文脈が必要
平均値は遅い少数を隠すためp95を使いますが、単独では読みません。p95が800 msなら、20件に1件は少なくともその時間がかかります。複数リクエストを使うページは継続的に遅く感じられます。サンプルが少ないルートではpercentileも不安定になるため、リクエスト数を併記します。
名前を付けた業務操作ごとにp50、p95、p99、最大値、throughput、error率を記録します。p50は通常の挙動、p95は実用的なサービス基準、p99は単一の最大値に支配されず末尾を示します。status code別に結果を分けてください。高速な500レスポンスがレイテンシを良く見せてはいけません。
クライアントから見た時間だけでなく、サーバー側ハンドラ時間も測ります。差には接続確立、proxy、ネットワーク時間、レスポンス転送が含まれます。クライアントp95が上がり、ハンドラp95が一定なら、ハンドラの外を調べます。両方が上がり、DB待ち時間も増えるなら、接続かクエリを待っている可能性が高いです。
ウォームアップと安定状態を分けます。配備済みGo binaryでコンパイルは起きませんが、冷たいcache、新規DB接続、遅延初期化、自動スケールが最初の数分を歪めます。ユーザーも冷えた状態を経験するため、削除せず別の結果として残します。
平均値はリソース計算には役立ちます。DB合計時間を呼び出し数で割ると、ほどほどに遅く非常に頻繁なクエリを見つけられます。ただしpercentileのサービス目標は置き換えられません。現場ではレイテンシとキャパシティがよく混同されます。レイテンシは完了した処理時間、キャパシティはキューやエラーを増やさず維持できる提示処理量です。達成リクエスト率が低い状態の良好なレイテンシは、キャパシティを証明しません。
実行前に失敗を定義します。重要ルートがp95目標を外す、予期しないHTTP失敗が合意率を超える、業務検査が失敗する、予定iterationが欠落する、リソースが飽和し続ける場合、私はローンチテストを不合格にします。5基準中4基準の合格は、診断データを得た不合格です。
接続待ちが隠れたDBキューを明らかにする
アプリケーションレイテンシだけではPostgreSQLが遅いのか、アプリケーションが接続を待つのか分からないため、テスト前にdatabase/sqlを計測します。GoのDB.Stats()はOpenConnections、InUse、Idle、WaitCount、WaitDurationと切断counterを返します。数秒ごとにメトリクスシステムへ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())
}
}
}
安定区間で累積counterの変化を計算します。WaitCountの増加は、リクエストが空き接続を待ったことを意味します。WaitDurationの変化をWaitCountの変化で割ると、その区間の平均プール待ち時間になります。InUseとMaxOpenConnectionsを同じグラフにし、上限で横ばいのまま待ちが増えるならプール飽和です。
グラフを良く見せるためにSetMaxOpenConnsを増やしてはいけません。よく使われるこの対策はキューをPostgreSQLへ移し、競合、メモリ使用量、クエリレイテンシを増やすことがあります。まず接続が占有される理由を調べます。遅いクエリ、ネットワーク呼び出し中も開いたtransaction、1行ずつの読み取り、Rows.Close()の呼び忘れなどです。その後、全アプリケーションレプリカとworkerを含むDB接続予算内でプールを設定します。
Goドキュメントでは、SetMaxOpenConnsが正でない場合、プールは無制限です。複数レプリカが同時に接続できる環境で、これは危険な本番既定値です。明示的な上限を置き、idleと寿命を意図的に設定し、migration、管理、バックグラウンド処理の接続を残してください。
transaction時間も別に追跡します。ハンドラが200 msで返っても、遅延cleanupや漏れたtransactionが接続を長く保持することがあります。プールメトリクスは圧力を示し、traceやtransaction timingは原因を示します。
遅いクエリの証拠はPostgreSQLから得る
テスト環境でpg_stat_statementsを有効にし、各実行の前後にsnapshotを取ります。PostgreSQLドキュメントは、正規化されたstatementの計画と実行統計を追跡する機能と説明しています。viewには呼び出し、行数、合計と平均実行時間、block活動、一時block活動が含まれます。一つのtraceに出たクエリから推測するより、はるかに良い証拠です。
viewは累積なので、snapshot間の差を使います。他の処理の証拠を消すため、分離されたテストDB以外ではresetしません。次のクエリは、クリーンなテスト区間で合計実行時間が大きい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;
合計時間は、頻繁でDB処理を支配するクエリを見つけます。平均時間は個別に遅いstatementを見つけます。pg_stat_statementsは呼び出しを集約するため、どちらもクエリp95を提供しません。特定statementの末尾レイテンシが重要なら、tracingやduration histogramを使います。遅いクエリという言葉は、高い平均時間、高い末尾時間、頻度による巨大な合計コストを混同しがちです。それぞれ直し方が異なります。
主要statementには、時間計測する負荷テストの外で、安全かつ代表的なparameterを使いEXPLAIN (ANALYZE, BUFFERS)を実行します。ANALYZEはstatementを実行するため、データを変えるstatementはrollbackするtransactionで囲むか、破棄できるコピーで調べます。推定行数と実行行数の大差、繰り返しloop、大きく選択的なtableのsequential scan、diskへあふれるsort、多数のshared block readを探します。
生成コードはseed dataでは問題なく見えるN+1を作りがちです。25件のprojectを取得し、各projectについてownerクエリとcountクエリを実行します。毎秒10リクエストなら、他のルートが動く前にこのendpointだけで毎秒500を超えるDB statementを生みます。join、まとめたWHERE id = ANY($1)、事前計算したcountが修正になります。プール上限を上げても無駄は残ります。
lock待ちも記録します。単独では速いクエリでも、同じtenant、account、sequenceへの同時更新で止まります。書き込みシナリオだけレイテンシが跳ねるなら、反射的にindexを足さず、active waitとtransaction境界を調べます。
一定負荷でメモリが安定する必要がある
単一ピークではなく、時間に沿った形でメモリを評価します。ウォームアップ中に増え、安定水準の周りを上下するGoプロセスと、2時間のソーク中にGC後baselineが上がり続けるプロセスは異なります。
process resident memory、Go heap allocation、heap object、goroutine数、GC頻度、pause時間、allocation rateを記録します。OSはGo heapだけでなくcontainer limitに基づいてprocessを停止するため、container memoryも重要です。同じ負荷でGC後のメモリを比較すると、通常ののこぎり波を減らしてretentionを見られます。
標準Go runtime metricsまたは保護したprofiling endpointをステージングで公開します。ソーク開始付近と終了付近でheap profileを取得し、go tool pprofで保持allocation siteを比較します。goroutine profileも取得してください。増え続けるgoroutineはchannelで止まったリクエスト、閉じていないresponse body、cancelなしで始めたバックグラウンド処理を示すことがあります。
GOMEMLIMITをcontainer limitと同じ値にしないでください。プロセスはgoroutine stack、executable mapping、DB driver buffer、heap外allocationにもメモリを使います。余裕を残し、最大payloadで証明します。小さなJSONだけのテストでは、20 MBのuploadをメモリへ読むendpointについてほとんど分かりません。
扱いにくいケースを実行します。許容最大request body、大きなquery result、client cancel、timeout、繰り返しexportです。処理後にメモリが戻るか確認します。重いGCはメモリを上限以下に保ちながらレイテンシを壊すため、CPUも監視します。
実用的な合格条件は上限と傾向を組み合わせます。成長ピークでresident memoryがdeployment limitより十分低いこと、その後ソーク中にGC後baselineとgoroutine数が増えなくなることを求めます。普遍的な安全率はありません。platformのrestart動作、トラフィック変動、他レプリカがrestartを吸収できるかに合わせて余裕を決めます。
失敗には誤答と過負荷時の動作も含める
transport failure、HTTP status failure、timeout、panic、誤ったレスポンスを分けて数えます。http_req_failedはk6 callbackに従ってHTTP失敗を捕捉しますが、空のlist、二重請求、欠落recordを含む200も失敗です。サンプルが内容検査からbusiness_errorsを出す理由です。
不正入力や意図的なrate limitなど、予想する拒否にはtagを付け、予期しないerror率を汚さないようにします。その契約も検査してください。正しいstatus、制限されたbody、素早い拒否です。過負荷のsystemが503を返すまで30秒かけてはいけません。
server logでpanic recovery、context deadline error、接続取得遅延、PostgreSQL serialization failure、cancelled queryを探します。識別子で数千分類ができないよう、全文ではなく安定した原因でerrorをまとめます。最初の発生と高レイテンシ末尾の代表traceを保存します。
クリーンなキャパシティテスト後に、一つの劣化テストを行います。利用可能なDB接続を減らす、外部依存先に制御した遅延を加える、トラフィック中にアプリケーションレプリカを1台restartするなどです。分離したテスト環境だけで行ってください。timeout、cancel、health checkが障害を封じ、キューが全リソースを消費しないことを確認します。
HTTPサーバーを明示的に設定します。Goのnet/httpドキュメントでは、ReadTimeout、WriteTimeout、IdleTimeoutのゼロまたは負の値は、fieldによってtimeoutなしを意味します。生成サービスは既定値のままhttp.ListenAndServeを呼び、判断をしていないことがあります。専用http.Server、リクエスト単位のdeadline、制限したbody sizeが、遅いclientや停止した依存先による無期限のリソース占有を防ぎます。
実行後に正しさを確認します。作成recordを数え、client retry時のidempotencyを確かめ、バックグラウンドジョブが1回だけ完了し、失敗リクエストの部分状態が残らないことを確認します。私の現場では、負荷テストが巧妙なcode reviewより多くの二重処理bugを見つけました。
採用判断は最初の限界から決まる
予想ピークと成長テストに繰り返し合格し、ソークで安定したメモリbaselineに達し、DBキューが制御され、チームが最初のストレス失敗を説明できるなら、バックエンドエンジニアなしでローンチできます。偶然の緑色結果は証拠ではありません。同じcommitを少なくとも3回実行し、大きなばらつきを調べます。
各実行について短い結果記録を残します。
- commitと環境。レプリカサイズとDB設定を含む。
- dataset規模、トラフィック構成、到着率、テスト時間。
- ルート別p95とp99、達成throughput、業務失敗、欠落iteration。
- CPUとメモリのピーク、GC後メモリ傾向、goroutine傾向。
- プール待ち、合計時間上位SQL、lock待ち、観測した限界点。
増加するプール待ち、保持メモリ、lock競合、不整合な書き込みを誰も説明できないなら、ローンチ前にバックエンド支援を採用または契約します。テストを実行できる唯一の人が生成コードを安全に変更できない場合も採用します。これは1秒あたりのリクエスト閾値ではなく、担当者の空白です。
テスト失敗だけで正社員採用が必要とは限りません。index不足、N+1クエリ、上限のないexportは限定的な修正かもしれません。transaction設計、observability、cancel、deployment動作にまたがる失敗は継続的なengineeringを示します。違いは、一つの欠陥を見つけたのか、system動作の担当者がいないと分かったのかです。
Koder.aiはGoバックエンドを生成してsourceをexportし、deployし、rollback用snapshotを保持できます。しかし生成はキャパシティ計画を不要にしません。負荷スクリプトとobservability変更をsourceと一緒に保ち、重要なバックエンド変更が同じ契約に合格するようにします。
月間1万人が安全だと事業へ約束しないでください。測定した到着率、ルート構成、レイテンシ目標、error budget、リソース範囲を約束します。製品が変わったら入力を変え、もう一度テストします。
よくある質問
Goバックエンドは月間アクティブユーザー1万人に対応できますか?
対応できる場合は多いものの、月間ユーザー数はバックエンド負荷を表しません。予測をピーク時の毎秒リクエストとルート構成へ変換し、レイテンシ、エラー、DB、メモリの明確な上限で試してください。
月間ユーザー1万人は毎秒何リクエストですか?
固定の換算式はありません。ユーザーあたりのセッション数、セッションあたりのリクエスト数、最繁忙時間の割合、ユーザーが同時に来るイベントが必要です。
Go APIで許容できるp95レイテンシはいくつですか?
言語やframeworkではなくユーザー操作ごとに決めます。対話的な読み取りは数百ms、受付後のバックグラウンド処理は別目標にできますが、結果を見る前に数値を選ぶ必要があります。
バックエンド負荷テストは何分実行すべきですか?
ウォームアップ後に各安定水準を少なくとも30分維持し、予想ピークでさらに長いソークを行います。短いテストは接続入れ替え、保持メモリ、バックグラウンド処理、キューの成長を見逃します。
仮想ユーザーと到着率のどちらを使うべきですか?
キャパシティを示すなら到着率モデルを使います。サービスが遅くても処理を提示し続けるためです。固定ユーザーモデルは遅延時にリクエスト率を下げ、キューの成長を隠すことがあります。
GoのDB接続プール枯渇はどう検出しますか?
DB.Stats()をexportし、InUse、OpenConnections、WaitCount、WaitDurationを監視します。使用中接続が設定上限に張り付いて待ちcounterが増えるなら、リクエストがプール待ちです。
待ちが出たらGoのSQLプールを増やすべきですか?
接続が占有される理由とPostgreSQLの余力が分かるまでは増やしません。大きなプールはキューをDBへ移し、競合を悪化させることがあります。
負荷テスト中に遅いPostgreSQLクエリをどう探しますか?
pg_stat_statementsの前後snapshotを取り、差分を合計と平均実行時間で並べます。集約viewにはクエリ別p95がないため、末尾レイテンシにはtraceやhistogramを使います。
Goサービスのメモリリークをどう判断しますか?
安定ソークを実行し、同程度の負荷でGC後のメモリを比較します。heap objectやgoroutineと一緒にbaselineが上がり続けるなら、profile比較が必要です。
スタートアップはいつバックエンドエンジニアを採用すべきですか?
性能失敗がDB設計、observability、並行処理、正しさ、運用の継続的な担当を必要とするときです。単一index修正は正社員を要しない場合がありますが、負荷時の説明不能な挙動は担当者を必要とします。