Optymalizacja wydajności z Cursor
Twój dashboard SaaS ładuje się 6 sekund. Strona produktu potrzebuje 3 sekund, żeby stać się interaktywna. Endpoint API zasilający dashboard billingowy ma latencję p95 wynoszącą 2,4 sekundy, a CPU bazy danych skacze do 80% za każdym razem, gdy ktoś otwiera zakładkę “Analityka zużycia”. Użytkownicy narzekają. Twój CTO chce 50% poprawy do końca miesiąca.
Optymalizacja wydajności to dyscyplina, w której najpierw stawia się diagnozę. Największą stratą czasu jest optymalizacja niewłaściwej rzeczy — oszczędzanie 50ms na renderze, który nigdy nie był wąskim gardłem, podczas gdy prawdziwy problem to zapytanie do bazy danych uruchamiane 47 razy na załadowanie strony. Mocną stroną Cursor jest zdolność do analizy dużych ilości danych profilowania, dostrzegania wzorców w setkach linii metryk i sugerowania celnych poprawek z konkretnymi oczekiwaniami przed/po.
Co wyniesiesz z tej lekcji
Dział zatytułowany „Co wyniesiesz z tej lekcji”- Workflow profilowania, który identyfikuje faktyczne wąskie gardło, zanim dotkniesz jakiegokolwiek kodu
- Prompt do analizy logów zapytań bazodanowych i automatycznego wyłapywania wzorców N+1
- Prompt analizy bundli, który znajduje największe zależności i sugeruje możliwości tree-shakingu
- Prompt optymalizacji renderowania React, który identyfikuje niepotrzebne re-rendery i możliwości memoizacji
- Technika benchmarkowania przed/po, która udowadnia, że twoja optymalizacja faktycznie zadziałała
Workflow
Dział zatytułowany „Workflow”Krok 1: Zmierz przed optymalizacją
Dział zatytułowany „Krok 1: Zmierz przed optymalizacją”Kardynalna zasada pracy z wydajnością: najpierw zmierz. Użyj trybu Ask, aby skonfigurować profilowanie i mieć punkt odniesienia do porównania.
Uruchom aplikację z włączonym profilowaniem. Załaduj strony, które są wolne. Zbierz co najmniej 5 minut danych lub 100 żądań — cokolwiek jest większe.
Krok 2: Zidentyfikuj wąskie gardło
Dział zatytułowany „Krok 2: Zidentyfikuj wąskie gardło”Wrzuć dane profilowania do trybu Ask i pozwól AI znaleźć wzorzec.
W większości aplikacji jednym z trzech wąskich gardeł są: zapytania N+1 do bazy danych, brakujące indeksy lub zbyt szerokie zapytania pobierające więcej danych niż potrzeba. AI zidentyfikuje, które to jest, i wskaże konkretny kod.
Krok 3: Napraw zapytania N+1
Dział zatytułowany „Krok 3: Napraw zapytania N+1”Zapytania N+1 to najczęstszy problem wydajnościowy backendu. Zamiast ładować powiązane dane w jednym zapytaniu z JOIN-em, kod ładuje rekordy nadrzędne, a potem uruchamia osobne zapytanie dla każdego rekordu podrzędnego.
@src/api/billing/usage.ts @src/db/schema.ts
Ten endpoint ładuje dane zużycia organizacji ze wzorcem N+1:- Pierwsze zapytanie: SELECT * FROM organizations WHERE plan = 'enterprise'- Potem dla KAŻDEJ organizacji: SELECT SUM(amount) FROM usage WHERE org_id = ? AND month = ?
To wykonuje 1 + N zapytań, gdzie N to liczba organizacji enterprise (aktualnie 340).
Przepisz to na użycie jednego zapytania z JOIN i GROUP BY.Wymagania:- Format odpowiedzi musi pozostać dokładnie taki sam (nie psuj frontendu)- Użyj query buildera Drizzle, nie surowego SQL- Dodaj indeks na usage(org_id, month), jeśli jeszcze nie istnieje- Zaloguj czas wykonania zapytania przed i po, abym mógł zweryfikować poprawęKrok 4: Zoptymalizuj bundle frontendu
Dział zatytułowany „Krok 4: Zoptymalizuj bundle frontendu”Dla wydajności frontendu rozmiar bundla jest często największą dźwignią. Bundle JavaScript o rozmiarze 500KB potrzebuje sekund na sparsowanie na telefonie średniej klasy.
Krok 5: Napraw wydajność renderowania React
Dział zatytułowany „Krok 5: Napraw wydajność renderowania React”Jeśli frontend jest wolny pomimo rozsądnego rozmiaru bundla, problem zazwyczaj tkwi w niepotrzebnych re-renderach. React DevTools Profiler może pokazać, które komponenty re-renderują się przy każdej zmianie stanu, ale analiza tego outputu jest żmudna. Wrzuć go do Cursor.
@src/components/billing-dashboard.tsx @src/components/usage-chart.tsx @src/hooks/use-billing-data.ts
Dashboard billingowy re-renderuje całą stronę za każdym razem, gdy pojawia siętooltip wykresu zużycia (najechanie myszką). React Profiler pokazuje re-renderowanie tych komponentów:- BillingDashboard (40ms)- UsageChart (25ms)- InvoiceList (15ms) -- ten NIE powinien się re-renderować przy hover na wykresie- UsageSummary (10ms) -- ten też NIE powinien się re-renderować
Napraw niepotrzebne re-rendery:
1. Zidentyfikuj, która zmiana stanu wyzwala pełny re-render2. Rozdziel stan tooltipa, aby wyzwalał re-render tylko w UsageChart3. Memoizuj InvoiceList i UsageSummary za pomocą React.memo (dodaj prawidłowe porównanie)4. Jeśli hook billing data powoduje re-rendery, ustabilizuj jego wartość zwracaną za pomocą useMemo5. NIE memoizuj wszystkiego na ślepo -- tylko komponenty, które są kosztowne do re-renderowania
Wyjaśnij swoje rozumowanie dla każdej zmiany. Niewłaściwa memoizacja jest gorsza niż brak memoizacji.Krok 6: Zweryfikuj benchmarkami
Dział zatytułowany „Krok 6: Zweryfikuj benchmarkami”Po optymalizacji udowodnij poprawę liczbami. Nie polegaj na odczuciu “wydaje mi się, że jest szybciej.”
@src/api/billing/usage.ts @src/components/billing-dashboard.tsx
Utwórz porównanie wydajności przed/po:
Backend:1. Uruchom 100 żądań do endpointu billingowego2. Zaraportuj: medianę, p95, p99 latencji3. Zaraportuj: liczbę zapytań bazodanowych per żądanie4. Zaraportuj: łączny czas bazy danych per żądanie
Frontend:1. Uruchom audyt wydajności Lighthouse na dashboardzie billingowym2. Zaraportuj: LCP, TBT, CLS, Speed Index3. Zaraportuj: rozmiar bundla JavaScript4. Zaraportuj: liczbę cykli renderowania przy początkowym ładowaniu strony
Porównaj z pomiarami bazowymi z Kroku 1.Sformatuj jako tabelę pokazującą przed, po i procentową poprawę.Kiedy to się psuje
Dział zatytułowany „Kiedy to się psuje”Przedwczesna optymalizacja nie-wąskich gardeł. AI chętnie zoptymalizuje cokolwiek, na co wskażesz, niezależnie od tego, czy to ma znaczenie. Zawsze zaczynaj od danych profilowania. Jeśli funkcja zajmuje 2ms, a całe żądanie 2000ms, optymalizacja tej funkcji to strata czasu.
Memoizacja, która spowalnia. React.memo, useMemo i useCallback mają narzut. Jeśli komponent jest tani w re-renderowaniu (prosty JSX, brak kosztownych obliczeń), dodanie memoizacji dodaje obciążenie pamięciowe i koszt porównania bez żadnych korzyści. Memoizuj tylko komponenty, które są mierzalnie kosztowne.
Indeksy bazodanowe, które spowalniają zapisy. Każdy indeks przyspiesza odczyty, ale spowalnia zapisy. Dla tabeli otrzymującej 10 000 zapisów na sekundę (jak tabela śledzenia zużycia), dodanie 5 indeksów oznacza 5 dodatkowych wstawień do B-tree na zapis. Użyj trybu Ask, aby przeanalizować stosunek odczytów do zapisów przed dodaniem indeksów.
Podział bundla, który zwiększa łączny rozmiar pobrań. Lazy-loading komponentu dodaje nowe żądanie HTTP i osobny chunk. Jeśli komponent jest mały (poniżej 10KB) i ładowany przy większości wyświetleń strony, narzut dodatkowego żądania przewyższa oszczędności. Dziel chunki tylko wtedy, gdy są duże I rzadko ładowane.
Zoptymalizowany kod, który jest trudniejszy w utrzymaniu. Ręcznie pisane zapytanie SQL z 4 subquery i 3 CTE może być szybsze niż zapytanie ORM, ale jeśli nikt w zespole nie potrafi go zrozumieć ani zmodyfikować, zamieniłeś wydajność runtime na szybkość developmentu. Używaj ORM domyślnie i schodź do surowego SQL tylko dla udowodnionych hot path z zmierzonymi wymaganiami wydajnościowymi.