Przejdź do głównej zawartości

Analiza wydajności aplikacji z Claude Code to pętla „najpierw pomiar”, prowadzona z terminala: agent zapisuje powtarzalny baseline, uruchamia prawdziwy profiler albo EXPLAIN ANALYZE, szereguje wąskie gardła z dowodami i poprawia jedno naraz. O tym, czy zmiana przyspieszyła system, rozstrzyga budżet wydajności spisany przed poprawką, a nie relacja agenta.

Ta strona jest dla programisty odpowiedzialnego za serwis, który zwolnił, i dla tech leada, który chce poprawek wydajności z dowodem w komplecie. W poprzednim kwartale API zamówień odpowiadało w około 120 ms, a teraz potrzebuje prawie dwóch sekund. Żaden pojedynczy commit tego nie tłumaczy, a dodatkowe serwery nie pomogły. Poproś agenta, żeby „przyspieszył to”, a przepisze kod, który wygląda na wolny. To rzadko jest kod, który naprawdę jest wolny.

  • Konfigurację uprawnień, w której Claude Code uruchamia profilery i benchmarki, ale nie może edytować benchmarku, którym jest oceniany
  • Plik z baseline’em i budżetem, który ustala próg zaliczenia, zanim zmieni się jakikolwiek kod
  • Polecenia profilujące dla Node.js, Pythona i PostgreSQL oraz prompt, który zamienia ich wynik w uszeregowaną listę hotspotów ze wskazaniem źródła
  • Pięć promptów do skopiowania: baseline, profil, poprawka zapytania, szukanie wycieku, poprawka z dowodem
  • Tabelę dowodów, które potwierdzają przyspieszenie bez czytania całego diffa
  • Typowe awarie, przez które optymalizacja prowadzona przez agenta daje fałszywy wynik, i sposób wyjścia z każdej z nich

Dlaczego agent potrzebuje baseline’u, zanim zacznie optymalizować?

Dział zatytułowany „Dlaczego agent potrzebuje baseline’u, zanim zacznie optymalizować?”

Model czytający kod widzi wzorce: zagnieżdżoną pętlę, SELECT *, wywołanie synchroniczne. Nie widzi, które z nich wykonują się dziesięć tysięcy razy na żądanie, a które raz przy starcie. Statyczny przegląd daje wiarygodnie brzmiącą listę „optymalizacji”, która często mija się z prawdziwym kosztem, a każda z nich dokłada diff, któremu musisz zaufać.

Profiler i plan zapytania zastępują zgadywanie pomiarem. Dlatego zasada na tej stronie brzmi: żadnej poprawki bez zmierzonego baseline’u, profilu wskazującego hotspot i progu zaliczenia spisanego przed zmianą. Claude Code dobrze radzi sobie z żmudną częścią tej pętli: pisze harness, uruchamia go wiele razy, czyta profil na 40 000 linii i porównuje liczby. Tobie zostaje jedna decyzja, której agent nie powinien podejmować sam: co znaczy „wystarczająco szybko”.

Profilowanie wymaga dostępu do powłoki: skryptów benchmarku, node --cpu-prof, py-spy i psql. Zezwól z nazwy na polecenia benchmarku i profilerów, psql, pgrep i kill zatwierdzaj pojedynczo, a edycję katalogu z benchmarkiem i budżetem zablokuj. Agent, który może przepisać własny benchmark, zaliczy go, nie stając się szybszym.

.claude/settings.json
{
"permissions": {
"allow": [
"Bash(node perf/bench.mjs *)",
"Bash(node perf/compare.mjs *)",
"Bash(node --cpu-prof *)",
"Bash(py-spy record *)",
"Bash(npm test *)"
],
"deny": [
"Edit(perf/**)"
]
}
}

Edit(perf/**) blokuje tylko narzędzia plikowe. Polecenie powłoki nadal może zapisać coś w perf/, więc trwała ochrona siedzi w CI: bramka czyta budżet z gałęzi bazowej, a zmiany w perf/ wymagają akceptacji code ownera. Jak dopasowywane są reguły uprawnień, opisuje konfiguracja Claude Code.

Zacznij dochodzenie w trybie planowania (/plan albo Shift+Tab, aż tryb pokaże plan). Claude czyta wtedy kod i proponuje plan, zanim cokolwiek zmieni. Zmień tryb dopiero wtedy, gdy masz uszeregowaną listę hotspotów i się z nią zgadzasz.

  1. Wybierz trzy najwolniejsze ścieżki widoczne dla użytkownika. Weź je z monitoringu, nie z intuicji. Jeśli nie masz jeszcze monitoringu, zacznij od endpointów, na które skarżą się użytkownicy, a potem skonfiguruj monitoring z Claude Code.

  2. Poproś Claude Code o powtarzalny benchmark. Ma działać na lokalnej lub stagingowej kopii z danymi o kształcie produkcyjnym. Dziesięć wierszy w tabeli ukrywa każde zapytanie N+1.

  3. Wpisz cel do perf/budget.json samodzielnie. Na przykład: /api/orders p95 ≤ 300 ms. Cel ustalony, zanim zobaczysz poprawkę, nie dopasuje się po cichu do tego, co poprawka dała.

  4. Załaduj dane w skali produkcyjnej. Jeśli rozrzut z kroku 2 jest duży, najpierw napraw środowisko: zamknij inne obciążenia, przypnij wersję Node.js i używaj tego samego snapshotu danych w każdym przebiegu.

Daj Claude Code wynik profilera, a nie mgliste objawy. Poniższe polecenia tworzą pliki, które agent może przeczytać. Poproś go o podsumowanie ich skryptem, bo surowy profil CPU albo snapshot sterty jest dużo większy, niż chcesz mieć w oknie kontekstu.

Punkt wejścia serwera (dodaj na początku)
// Czyste wyjście na SIGTERM, żeby Node.js zapisał profil CPU przed zatrzymaniem
process.on('SIGTERM', () => process.exit(0));
Okno terminala
# Terminal: zapisz profil CPU V8, gdy benchmark obciąża serwer
node --cpu-prof --cpu-prof-dir=perf/profiles dist/server.js &
node perf/bench.mjs --endpoint /api/orders --runs 1
kill %1 # SIGTERM: handler powyżej kończy proces czysto, więc powstaje plik .cpuprofile

--cpu-prof zapisuje profil, gdy Node.js kończy działanie w normalny sposób. Bez handlera SIGTERM polecenie kill kończy proces bez zapisu i katalog perf/profiles/ zostaje pusty. Jeśli serwer ma już handler łagodnego zamykania, upewnij się, że kończy się wywołaniem process.exit(), zamiast dodawać drugi.

Jeśli trzy endpointy są wolne z niezależnych powodów, poproś Claude o równoległe dochodzenie z jednym subagentem na każdy. Każdy subagent zwraca tylko swoją uszeregowaną listę, więc główna rozmowa pozostaje mała. Wielokrotnego użytku perf-investigator z narzędziami tylko do odczytu zbudujesz według strony o własnych subagentach.

Dostęp do bazy to zwykle pierwszy podejrzany w serwisie typu CRUD, ale musi to potwierdzić profil. Gdy profil wskazuje czas zapytań, dwie klasyczne przyczyny to wzorzec N+1 i brakujący indeks. Obie wyraźnie widać w EXPLAIN ANALYZE: wiele małych, identycznych zapytań albo skan sekwencyjny dużej tabeli.

Przy powtarzalnej pracy z bazą możesz dać Claude Code bezpośredni dostęp tylko do odczytu przez Postgres MCP Pro (PyPI postgres-mcp, projekt społecznościowy Crystal DBA). Tryb restricted używa transakcji tylko do odczytu i limitów czasu wykonania, a serwer dodaje narzędzia do strojenia indeksów i planów. Ostatnie wydanie na PyPI to 0.3.0 z 2025-05-16 (stan na 2026-09-26), więc przypnij tę wersję i sprawdź aktywność repozytorium, zanim na nim oprzesz pracę. Łącz się też przez rolę bazodanową tylko do odczytu: tryb restricted to drugie zabezpieczenie, a nie jedyne.

Okno terminala
# Terminal: dodaj serwer tylko dla tego projektu (zakres local), tylko do odczytu
claude mcp add postgres -e DATABASE_URI="$STAGING_DATABASE_URL" -- uvx postgres-mcp==0.3.0 --access-mode=restricted

Powłoka rozwija $STAGING_DATABASE_URL, zanim Claude Code je zobaczy, więc connection string trafia do twojej lokalnej konfiguracji MCP użytkownika, a nie do repozytorium. Nigdy nie dodawaj tego serwera z --scope project, bo ta opcja zapisuje .mcp.json, który trafi do commita.

Do jednorazowych planów równie dobrze działa psql przez narzędzie Bash. Inne serwery bazodanowe i ich przełączniki trybu tylko do odczytu znajdziesz na stronie o serwerach MCP dla baz danych.

O bezpieczeństwie decydują dwa szczegóły. EXPLAIN ANALYZE wykonuje instrukcję, więc UPDATE albo DELETE owiń w BEGIN; … ROLLBACK;. PostgreSQL odrzuca też CREATE INDEX CONCURRENTLY wewnątrz bloku transakcji, a tak właśnie większość narzędzi migracyjnych domyślnie uruchamia każdy plik. Przy zmianach schematu na dużą skalę przejdź do migracji bazy danych i kodu oraz pracy z bazą danych w Claude Code.

Wyciek wygląda jak pamięć, która rośnie przy identycznych żądaniach i nie wraca po odśmiecaniu. Czytanie kodu pod kątem wzorców wycieków daje listę podejrzanych, ale dwa snapshoty sterty zrobione w odstępie kilku minut pokazują, co faktycznie urosło.

Okno terminala
# Terminal: rób snapshot sterty na żądanie
node --heapsnapshot-signal=SIGUSR2 dist/server.js &
kill -USR2 %1 # snapshot 1, po rozgrzaniu
node perf/bench.mjs --endpoint /api/dashboard --runs 3
kill -USR2 %1 # snapshot 2, po obciążeniu

Ta sama idea budżetu działa w przeglądarce. Poproś Claude Code o dodanie rollup-plugin-visualizer (Vite i Rollup) dla mapy drzewa oraz size-limit dla kontroli, która zatrzymuje build, gdy chunk przekroczy limit. Najpierw ustaw limit na dzisiejszy rozmiar, a potem obniżaj go z każdą poprawką. Taka zapadka daje agentowi cel, a CI bramkę. Leniwe ładowanie każdej trasy potrafi stworzyć kaskadę małych żądań, więc powiedz agentowi, ile chunków może leżeć na ścieżce krytycznej.

Pracuj w osobnym worktree, żeby benchmark w głównym checkoucie pozostał czystą próbą kontrolną. claude -w perf-orders tworzy worktree gita i uruchamia w nim sesję.

Potem uniezależnij bramkę od agenta. perf/compare.mjs to deterministyczny skrypt, który CI może uruchamiać przy każdym merge’u do gałęzi głównej. Gdy zawiedzie, uruchomienie Claude Code w trybie nieinteraktywnym (claude -p) może wyjaśnić regresję bez prawa zapisu:

Okno terminala
# CI albo nocne zadanie na zaufanej gałęzi głównej, po porażce compare.mjs
claude -p "perf/compare.mjs failed. Read perf/latest.json, perf/budget.json and git log -10. \
Name the most likely commit and hotspot, citing the numbers. Do not change files." \
--permission-mode dontAsk \
--allowedTools "Read" "Grep" "Bash(git log *)" "Bash(node perf/compare.mjs *)" \
--max-budget-usd 2 --output-format json > perf/regression-report.json

claude -p startuje w trybie Manual (od v2.1.285 w trybie auto u zewnętrznych dostawców lub z wyłączoną telemetrią), więc --permission-mode dontAsk z dokładną listą --allowedTools ogranicza przebieg do tych narzędzi oraz odczytów w katalogach roboczych i poleceń tylko do odczytu. Uruchamiaj to wyłącznie na zaufanych refach, nigdy na kodzie z niezaufanego pull requesta, gdy w środowisku są sekrety. Plik workflow i jego uprawnienia opisuje strona o Claude Code w CI/CD.

Nie wkładaj benchmarku do hooka PostToolUse. Trwa minuty, odpala się po każdej edycji i uczy agenta stroić pod szum. Uruchom go raz, na końcu zadania, tam gdzie umieszcza go powyższy prompt.

Nie musisz czytać każdej linii diffa, żeby zaufać przyspieszeniu. Potrzebujesz pięciu dowodów dołączonych do pull requesta jako część jego pakietu dowodów:

TezaDowódKto sprawdza
Jest szybciejWynik compare.mjs: baseline, nowe p95 i rozrzut, przy czym zysk przekracza dwukrotność rozrzutuBramka CI
Nadal działa poprawnieIstniejący zestaw testów przechodzi, plus test dla każdego przepisanego zapytaniaBramka CI
Poprawiono właściwe miejsceProfil lub plan sprzed poprawki wskazuje ten sam plik i funkcję, które zmienia diffRecenzent, w dwie minuty
Nic innego nie zwolniłoKażdy endpoint z budget.json przechodzi, nie tylko docelowyBramka CI
Poprzeczka się nie przesunęłaperf/budget.json bez zmian albo zmieniony w osobnym commicie zaakceptowanym przez code owneraCode owner

Praca recenzenta sprowadza się do dwóch pytań: czy profil uzasadnia tę zmianę i czy budżet pozostał nietknięty? Autor podpisuje się pod dowodami, a code owner pod każdą zmianą budżetu. Jeśli potrzebujesz profilu obciążenia większego niż benchmark na jednej maszynie, przed wydaniem uruchom testy obciążeniowe i stresowe.

Agent zoptymalizował kod, który nigdy nie był gorący. Diff jest schludny, testy przechodzą, a p95 stoi w miejscu. Wyjście: cofnij zmianę i uruchom ponownie prompt z profilem. Przyjmuj tylko poprawki, których hotspot wskazuje profil, i odrzucaj wszystko oznaczone jako „unmeasured”.

Zysk jest mniejszy niż szum. Dwa przebiegi różnią się o 20% jeszcze przed zmianą, więc 10% „poprawy” nic nie znaczy. Wyjście: korzystaj z rozrzutu z baseline.json. Zwiększ liczbę iteracji, zatrzymaj obciążenia w tle i uruchamiaj bramkę na dedykowanym runnerze CI.

Benchmark został oszukany. Agent dodał cache pod dokładne żądanie z benchmarku, zmniejszył liczbę połączeń albo zmienił budżet. Wyjście: trzymaj Edit(perf/**) na liście deny, niech CI czyta budżet z gałęzi bazowej, a bench.mjs niech zmienia parametry żądań.

Poprawka N+1 dała gorsze zapytanie. Jeden JOIN przez pięć tabel może kosztować więcej niż pięć zaindeksowanych odczytów przy dużych zbiorach wyników. Wyjście: porównaj wyniki EXPLAIN (ANALYZE, BUFFERS) przed i po, a jako alternatywę poproś Claude o wersję z paczkowanym IN (...).

Migracja z indeksem padła albo zablokowała zapisy. CREATE INDEX CONCURRENTLY nie działa w bloku transakcji, a zwykłe CREATE INDEX blokuje zapisy do tabeli na czas budowy. Wyjście: przenieś indeks do osobnej, nietransakcyjnej migracji. Nieudana budowa współbieżna zostawia nieprawidłowy indeks: usuń go i uruchom migrację ponownie.

Sesja utonęła w profilu. Agent wczytał surowy .cpuprofile albo snapshot sterty, kontekst się zapełnił, a odpowiedzi stały się mgliste. Wyjście: /rewind do momentu przed odczytem i poproś o skrypt podsumowujący. Limity kontekstu szczegółowo opisuje optymalizacja wydajności Claude Code.

Instrumentacja sama stała się spowolnieniem. Wrappery mierzące czas na każdej funkcji mają własny narzut. Wyjście: doraźne pomiary czasu schowaj za flagą środowiskową i używaj profilerów próbkujących, takich jak --cpu-prof i py-spy, które nie zmieniają twojego kodu.