From 71ddc1e1fba5d074fc2f06a5953648ff26b2309b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Doma=C5=84ski?= Date: Thu, 18 Jun 2026 07:47:40 +0000 Subject: [PATCH] Jun 18, 2026, 9:47 AM --- journals/2026-06-18.md | 787 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 787 insertions(+) diff --git a/journals/2026-06-18.md b/journals/2026-06-18.md index 2c107b1..ce3511a 100644 --- a/journals/2026-06-18.md +++ b/journals/2026-06-18.md @@ -94,3 +94,790 @@ Spotkanie kontrolne (follow-up) prawdopodobnie zostanie zaplanowane na przyszły - Wymagane testy i ostateczna decyzja między architekturami (push vs pull). - Zmiana wymaga przejścia formalnego procesu i zakomunikowania interesariuszom. - Otwarte ryzyko: weryfikacja czy wdrożenie wymusi restart lub downtime. + + +Okej — zrobię Ci **pełny, spójny raport DBA** z całej analizy: skryptu + warningów + planu wykonania + realnych rekomendacji. + +To będzie coś, co możesz praktycznie potraktować jako: +✅ dokument techniczny +✅ materiał do review / knowledge sharing +✅ podstawę do decyzji produkcyjnej + +*** + +# 📘 RAPORT: Optymalizacja migracji kolumny (SQL Server) + +## 🔍 1. Cel skryptu + +Skrypt służy do: + +👉 masowej zmiany typu kolumn (np. `VARCHAR(9)`) +👉 w wielu tabelach +👉 z podziałem na strategię: + +| przypadek | metoda | +| ------------------ | ------------------------ | +| małe tabele (≤20M) | `ALTER COLUMN` | +| duże tabele (>20M) | migracja “shadow column” | + +*** + +## 🧱 2. Mechanizm dla dużych tabel + +Dla dużych tabel stosowany jest wzorzec: + +1. `ADD new_column` +2. kopiowanie danych batchami: + ```sql + UPDATE TOP (99000) + ``` +3. (później) drop + rename + +👉 to jest **poprawny enterprise pattern** + +*** + +# 📊 3. Diagnoza planu wykonania + +Na podstawie XML i operatorów: + +## ✅ Fakty + +* tabela: `REPORT_XML_STACK` +* liczba wierszy: **\~52 841 100** +* typ: **HEAP (brak clustered index)** +* batch: `TOP (99000)` +* używane: + * `Table Scan` + * `Parallelism` + * `Sort` + * `Table Update` + * `Index Update` + +*** + +## ❗ KLUCZOWY PROBLEM + +### ❌ 1. FULL / LARGE TABLE SCAN + +```xml +PhysicalOp="Table Scan" +``` + +➡️ SQL Server czyta **dużą część 52M wierszy** dla każdego batcha + +👉 to jest główne wąskie gardło + +*** + +## ❗ 2. Słaby indeks filtrowany + +Masz: + +```sql +ON (DUNS_NBR_NEW1) +WHERE DUNS_NBR_NEW1 IS NULL +``` + +### Problem: + +* wszystkie wartości = `NULL` +* brak selektywności +* brak sensownego porządku + +👉 indeks istnieje, ale **nie prowadzi planu** + +*** + +## ❗ 3. Batch `TOP (99000)` bez ORDER + +```sql +UPDATE TOP (99000) +``` + +➡️ brak deterministycznego wyboru wierszy + +👉 skutki: + +* sort +* niespójne batchowanie +* trudniejsza optymalizacja + +*** + +## ⚠️ 4. Warning: CONVERT() + +``` +Type conversion may affect cardinality estimate +``` + +### Wniosek: + +👉 **prawdziwy, ale drugorzędny** + +bo: + +* CAST jest tylko w SET +* nie w predicate + +👉 NIE jest główny problem + +*** + +## ⚙️ 4. Dodatkowe obserwacje + +### Parallelism + +* `Gather Streams` +* wiele wątków +* dodatkowy overhead + +*** + +### Sort przed update + +* koszt \~13 CPU w planie +* wynika z braku uporządkowania danych + +*** + +### Heap + +* brak uporządkowania fizycznego +* więcej IO +* trudniejsze update’y + +*** + +# 🎯 4. ROOT CAUSE + +Największy problem to: + +> ❗ SQL Server nie ma dobrej ścieżki dostępu do „następnych 99000 rekordów do update” + +czyli: + +* słaby indeks +* brak ORDER +* heap +* TOP + +*** + +# 🚀 5. STRATEGIA OPTYMALIZACJI + +## 🥇 PRIORYTET 1 — popraw indeks + +Zamiast: + +```sql +ON (DUNS_NBR_NEW1) +``` + +zrób indeks **na kluczu tabeli**: + +```sql +CREATE NONCLUSTERED INDEX IX_REPORT_XML_STACK_MIGRATION +ON dbo.REPORT_XML_STACK (ID) -- <- KLUCZ! +INCLUDE (DUNS_NBR) +WHERE DUNS_NBR_NEW1 IS NULL + AND DUNS_NBR IS NOT NULL; +``` + +### Efekt: + +✅ fast seek +✅ brak full scan +✅ mniej IO +✅ brak sort + +*** + +## 🥇 PRIORYTET 2 — zmień UPDATE + +Zamiast: + +```sql +UPDATE TOP (99000) +``` + +użyj: + +### ✅ wersja CTE + +```sql +;WITH batch AS +( + SELECT TOP (20000) + ID, + DUNS_NBR + FROM dbo.REPORT_XML_STACK + WHERE DUNS_NBR_NEW1 IS NULL + AND DUNS_NBR IS NOT NULL + ORDER BY ID +) +UPDATE T +SET T.DUNS_NBR_NEW1 = CAST(B.DUNS_NBR AS VARCHAR(9)) +FROM dbo.REPORT_XML_STACK T +JOIN batch B ON B.ID = T.ID +OPTION (RECOMPILE); +``` + +*** + +## 🥇 PRIORYTET 3 — zmniejsz batch + +Z: + +```sql +TOP (99000) +``` + +👉 testuj: + +* 10k +* 20k +* 50k + +### Korzyści: + +✅ mniejszy log +✅ mniej blokad +✅ bardziej stabilny runtime + +*** + +## 🥇 PRIORYTET 4 — dodaj RECOMPILE + +```sql +OPTION (RECOMPILE) +``` + +👉 bo dataset się zmienia (NULL → NOT NULL) + +*** + +## 🥈 PRIORYTET 5 — kontrola parallelism + +Test: + +```sql +OPTION (RECOMPILE, MAXDOP 1) +``` + +👉 często: + +* mniej overhead +* bardziej przewidywalne batchy + +*** + +## 🥈 PRIORYTET 6 — lock strategy + +### obecnie: + +```sql +TABLOCK +``` + +### alternatywa (bardziej OLTP-friendly): + +```sql +WITH (UPDLOCK, READPAST) +``` + +*** + +## 🥈 PRIORYTET 7 — statystyki + +```sql +UPDATE STATISTICS dbo.REPORT_XML_STACK IX_REPORT_XML_STACK_MIGRATION; +``` + +👉 ważne dla planu + +*** + +# 🧠 6. FINALNY WZORZEC (PROD READY) + +```sql +SET NOCOUNT ON; +SET XACT_ABORT ON; + +DECLARE @Rows int = 1; + +WHILE @Rows > 0 +BEGIN + ;WITH batch AS + ( + SELECT TOP (20000) + T.ID, + T.DUNS_NBR + FROM dbo.REPORT_XML_STACK AS T + WITH (INDEX(IX_REPORT_XML_STACK_MIGRATION), UPDLOCK, READPAST) + WHERE T.DUNS_NBR_NEW1 IS NULL + AND T.DUNS_NBR IS NOT NULL + ORDER BY T.ID + ) + UPDATE T + SET T.DUNS_NBR_NEW1 = CAST(B.DUNS_NBR AS VARCHAR(9)) + FROM dbo.REPORT_XML_STACK AS T + JOIN batch AS B + ON B.ID = T.ID + OPTION (RECOMPILE, MAXDOP 1); + + SET @Rows = @@ROWCOUNT; + + PRINT CONCAT('Updated rows: ', @Rows); +END +``` + +*** + +# 📉 7. Oczekiwany efekt po optymalizacji + +| obszar | efekt | +| ----------- | --------------------- | +| IO | ↓ znacząco | +| CPU | ↓ (mniej sort/scan) | +| log | ↓ | +| czas batcha | ↓ | +| stabilność | ↑ | +| blokady | ↓ (jeśli bez TABLOCK) | + +*** + +# ✅ 8. TL;DR + +👉 największy problem: + +> ❌ batch TOP + heap + słaby indeks → table scan + +👉 najważniejsze fixy: + +1. ✅ indeks na kluczu (ID) + INCLUDE +2. ✅ batchowanie po ORDER BY ID +3. ✅ mniejszy TOP +4. ✅ RECOMPILE + +👉 warning o CAST: + +> ℹ️ istnieje, ale nie jest głównym problemem + +Super — to jest bardzo dobry drugi kawałek Twojego frameworka 🔥 +I teraz możemy zrobić **pełną analizę architektury (Step 8)**, bo to jest kluczowy moment: *odtwarzanie constraints, indeksów i obiektów* po migracji. + +*** + +# 📘 TL;DR (co robi Step 8) + +👉 Ten fragment: + +* zbiera **skrypty odtwarzające** (constraints, indexy, statystyki, view-indexy) +* wrzuca je do kolejki (`Step8Queue`) +* sortuje wg kolejności zależności +* wykonuje je **sekwencyjnie przez cursor** + +👉 to jest poprawny pattern typu **rebuild pipeline** + +*** + +# 🧠 Co działa BARDZO dobrze + +## ✅ 1. Kolejka wykonania (Step8Queue) + +To jest bardzo dobry design: + +```sql +CREATE TABLE Step8Queue (...) +``` + +👉 masz: + +* centralny queue +* kontrolę kolejności +* możliwość debugowania +* możliwość DRY RUN + +💡 to jest **bardziej kontrolowalne niż dynamiczne wykonania ad-hoc** + +*** + +## ✅ 2. Priorytety (ExecutionOrder) + +```sql +CASE ObjectType + WHEN 'PK/UNIQUE' THEN 1 + WHEN 'DEFAULT' THEN 3 + WHEN 'FOREIGN_KEY' THEN 5 + WHEN 'INDEX' THEN 7 +``` + +👉 bardzo ważne — bo: + +| obiekt | zależność | +| ------ | ----------------- | +| PK | musi być pierwszy | +| FK | potrzebują PK | +| INDEX | na końcu | + +✅ to jest poprawna kolejność odbudowy + +*** + +## ✅ 3. DryRun mode + +```sql +IF @DryRun = 1 +``` + +👉 super ważne przy takich operacjach + +*** + +## ✅ 4. TRY/CATCH + logowanie + +```sql +BEGIN TRY +EXEC ... +END TRY +BEGIN CATCH +EXEC dbo.usp_LogError +``` + +👉 dobrze, bo: + +* nie wywala całego procesu +* masz audit + +*** + +# ⚠️ Problemy i miejsca do poprawy (ważne) + +Teraz sedno — co bym poprawił jako DBA produkcyjny: + +*** + +# ❌ 1. Cursor = wąskie gardło (ale… tu jest OK) + +Masz: + +```sql +CURSOR FAST_FORWARD +``` + +👉 normalnie powiedziałbym: "cursor zły" + +ALE w tym przypadku: + +* tworzysz obiekty (DDL) +* kolejność jest ważna +* trzeba sekwencyjnie + +👉 ✅ cursor jest tu **UZASADNIONY** + +*** + +# ❌ 2. Brak jawnej transakcji per obiekt + +Masz: + +```sql +SET XACT_ABORT ON; +``` + +ALE nie masz: + +```sql +BEGIN TRAN +COMMIT +``` + +### Problem + +Jeśli obiekt: + +* padnie w połowie (np. index CREATE) +* i nie rollbacknie się poprawnie + +👉 możesz mieć niespójny stan + +*** + +## ✅ Fix + +Dodać per-obiekt: + +```sql +BEGIN TRY + BEGIN TRAN + + EXEC sp_executesql @stmt1 + + COMMIT +END TRY +BEGIN CATCH + IF XACT_STATE() <> 0 ROLLBACK +``` + +*** + +# ❌ 3. PRINT zamiast realnego logowania + +Masz: + +```sql +PRINT @OBJT1 + '---' + @stmt1 +``` + +👉 PRINT: + +* znika przy dłuższych operacjach +* tnie się +* nie jest audytowalny + +*** + +## ✅ Fix + +Zostaw PRINT, ale dodaj: + +```sql +INSERT INTO MyprintLog (...) +VALUES (...) +``` + +PRZED wykonaniem, nie tylko po + +*** + +# ❌ 4. Brak retry logic + +DDL potrafi failować przez: + +* blocking +* deadlock +* metadane + +👉 obecnie: + +* jeden fail = log + jedziemy dalej + +*** + +## ✅ Fix (mini retry) + +```sql +DECLARE @retry INT = 0; + +WHILE @retry < 3 +BEGIN + BEGIN TRY + EXEC sp_executesql @stmt1 + BREAK + END TRY + BEGIN CATCH + SET @retry = @retry + 1 + WAITFOR DELAY '00:00:02' + END CATCH +END +``` + +*** + +# ❌ 5. Literówka w ObjectType + +Masz: + +```sql +'Foregin_sweep_Key' +``` + +👉 może powodować: + +* brak dopasowania +* złe sortowanie + +*** + +# ❌ 6. ORDER BY w INSERT (niezdeterminowane) + +```sql +INSERT INTO Step8Queue +SELECT ... +ORDER BY ... +``` + +👉 SQL Server ignoruje `ORDER BY` w INSERT bez TOP + +*** + +## ✅ Fix + +Sortuj tylko przy SELECT w cursorze (już robisz ✅) + +*** + +# ❌ 7. Brak walidacji duplikatów + +Nie masz: + +```sql +PRIMARY KEY na Step8Queue +``` + +👉 możesz mieć: + +* duplikaty create scriptów +* ponowne wykonanie DDL + +*** + +## ✅ Fix + +Dodaj: + +```sql +ALTER TABLE Step8Queue +ADD CONSTRAINT PK_Step8Queue UNIQUE(ObjectType, ObjectName, ParentTable); +``` + +*** + +# ❌ 8. Brak kontroli zależności między tabelami + +Sortowanie: + +```sql +ORDER BY ExecutionOrder, ObjectType +``` + +ALE: + +👉 dwa FK między tabelami mogą się „gryźć” + +*** + +## ✅ advanced fix (opcjonalne) + +Topologiczne sortowanie zależności FK +(albo retry loop — prostsze) + +*** + +# ❌ 9. Możliwy błąd: VARCHAR(MAX) dla CreateScript + +Może powodować: + +* duże użycie tempdb / memory + +👉 OK przy małej liczbie obiektów +👉 przy tysiącach → problem + +*** + +# 🔥 Najważniejszy problem architektoniczny + +## Step 8 zakłada, że wszystko się odtworzy „bez oporu” + +w praktyce: + +* FK mogą failować +* PK mogą failować +* index może failować + +👉 i Twój flow: + +``` +FAIL → log → NEXT +``` + +czyli: + +❗ możesz skończyć ze środowiskiem: + +* bez FK +* bez indeksów +* częściowo odtworzonym + +*** + +# ✅ Lepszy pattern (produkcyjny) + +## podejście 2-fazowe + +### FAZA 1 – CREATE (niekrytyczne błędy OK) + +* PK, INDEX, DEFAULT, CHECK + +### FAZA 2 – FK (retry loop) + +```sql +WHILE EXISTS (FK do odtworzenia) +BEGIN + spróbuj odtworzyć + jeśli fail → zostaw +END +``` + +👉 aż nic się nie da już zrobić + +*** + +# 📊 FINALNA OCENA + +| obszar | ocena | +| ------------------ | ----- | +| architektura | ⭐⭐⭐⭐⭐ | +| bezpieczeństwo | ⭐⭐⭐⭐ | +| odporność na błędy | ⭐⭐⭐ | +| wydajność | ⭐⭐⭐⭐ | +| produkcyjność | ⭐⭐⭐⭐ | + +*** + +# 🚀 Co bym zrobił jako DBA (konkrety) + +## ✅ MUST HAVE + +1. transakcja per obiekt +2. logging pre + post +3. poprawa indeksów migracji (z poprzedniego wątku) + +*** + +## ✅ SHOULD HAVE + +4. retry logic +5. eliminacja literówek +6. unique constraint na queue + +*** + +## ✅ NICE TO HAVE + +7. FK retry loop +8. dependency-aware ordering + +*** + +# 🧠 Finalny wniosek + +👉 Step 8 jest: + +✅ dobrze zaprojektowany +✅ zgodny z enterprise patternem +❗ ale wymaga hardeningu pod produkcję + +*** + +# 👍 Jeśli chcesz + +Mogę Ci: + +✅ przepisać cały Step 8 w wersji **production-grade (hardcore DBA)** +✅ dodać retry engine + dependency resolver +✅ zrobić wersję z checkpointami (resume after fail) +✅ dodać metryki: ile % zrobione + +Powiedz tylko — zrobimy z tego narzędzie, którego będziesz używać latami 👍