problem z wydajnością .NET i SQL Express

0

Mam problem z wydajnością aplikacji w VB.NET. Prosta aplikacja za pomocą SQLClient'a wybiera ~700 tyś. rekordów z tabeli "A" bazy SQL Express i dla każdego rekordu wykonuje jednego SELECT'a do tabeli "B" sprawdzając czy pewna wartość z aktualnie przetwarzanego rekordu w niej występuje.

Po każdych 10 tyś przetworzonych rekordów wyświetlam informację o postępie przetwarzania i czasie w ms ile zajęło sprawdzenie ostatnich 10 tyś rekordów.

Problem polega na tym, że czasem aplikacja działa prawidłowo tj. kolejne paczki przetwarzane sa w podobnym czasie w granicach 2 s., a czasem odpalenie aplikacji powoduje, że czas przetworzenia paczki rekordów rośnie liniowo od ~2 s. do ponad ~8 s. dla ostatnich paczek.

Problem zdaje się wynikać z samego środowiska ponieważ dla testu zachowałem exe'ka "prawidłowo" działającego kodu i po kolejnym kórymś tam uruchomieniu pojawił się opisywany efekt.

Pytanie jest takie, czym może to być spowodowane? Czy istnieje jakiś wewnętrzny mechanizm , który odpala się w pewnym momencie pracy FrameWork'a i robi coś co może spowolnić wykonywanie aplikacji? Spotkał ktoś podobne zjawisko?

Dodatkowe info i sprawy które sprawdziłem za podpowiedzią innych:

  1. FrameWork 1.1 i VS 2003.

  2. SQL Express jest na tej samej maszynie co uruchamiana aplikacja więc raczej wykluczam wpływ innych aplikacji.

  3. Maszyna ma 2 GB RAM'u i nie występuje efekt swapowania.

  4. Zajętość pamięci obu procesów (SQL'a i mojej aplikacji) nie wzrasta w trakcie wykonywania.

  5. Do SQL'a utrzymywane jest jedno połączenie.

  6. Bazy tymczasowe na SQL'u nie rosną w trakcie pracy palikacji.

  7. Tabela "B" zawiera aktualnie 0 (zero) rekordów (odpada problem rosnącej objętości, aktualizowania indeksu itp.)

Za wszelkie podpowiedzi z góry dzięki i pozdrawiam

root666

PS. Póki co nie wrzucam kodu, post i tak już jest długi

0

Pytanie jest takie, czym może to być spowodowane? Czy istnieje jakiś wewnętrzny mechanizm , który odpala się w pewnym momencie pracy FrameWork'a i robi coś co może spowolnić wykonywanie aplikacji? Spotkał ktoś podobne zjawisko?

Chociazby GC.

  1. Co takiego robisz, ze nie wykonasz tego 1 zapytaniem? Przetwarzanie 700tys. rekordow i odpalanie takiej samej ilosci selectow w zyciu nie bedzie nawet zblizone do czegokolwiek co stalo kolo slowa 'szybko' :P
0

A próbowałeś odwrotnej kolejności ? Może baza tak dane przechowuje, że akurat jedne rekordy daje się sprawdzić szybciej niż inne.

I przedstaw nam budowę bazy oraz jakie operacje na niej przeprowadzasz i jak często (łącznie z tym zagnieżdżonym select'em).
Może da się to lepiej zaprojektować, a może też jakaś wyspecjalizowana struktura danych by tu bardziej spasiła od bazy.

0

Dzięki za zinteresowanie

johny_bravo:

Ok, sprawdzę wątek GC.

Według mnie to jest szybko. Niestety, opisywanego zadania nie wykonam jednym SELECT'em z prostego powodu. "Rozbieram" kilkaset tysięcy rekordów aby rozrzucić dane do inych tabel.
Inna sprawa, że nie kwestia wydajności jest tu kluczowa tylko brak determinizmu. Jesli wprowadzam jakąś modyfikację w kodzie, muszę mieć możliwośc oceny czy ta zmiana wpłynęła in plus czy in minus na wydajność.

Teraz jest tak, że w pewnym momencie program zaczyna wykonywać się coraz dłużej pomimo, że zmianę kodu wykonałem zupełnie gdzieś indziej. Muszę mieć jaką linie bazową dla sensownej oceny.

adf88:

Tabela z której pobieram dane nie zmienia się na razie. Te 700 tyś rekordów traktuję jako wsad testowy. Gdyby uporządkowanie danych miało znaczenie dla wykonania aplikacji, to działo by się tak zawsze, a tak niestety nie jest.

pozdrawiam

root666

0

adf88:

Tabela "A" (pole1, .., .., pole13)

Tabela "B" (id_pola1, pole1)

Zadanie trywialne, wybierz z tabeli "A" wszystkie rekordy i dla kazdego z nich sprawdź czy 'pole1' występuje w tabeli "B". Na razie tabela "B" nie zawiera żadnych rekordów. Jak osiągnę stały poziom czasu wykonania przebiegu to wtedy zacznę pakować dane do tabeli "B" budując słownik.

Z całej aplikacji wydzieliłem to zdanie do osobnego projektu aby wykluczyc wpływ czegokolwiek na jego wykonanie. Mamy więc pętlę biegnącą po jednym SqlDataReader i dla każdego rekordu wykonującego zapytanie do tabeli "B". I tak 700 tyś. razy :-)

pozdrawiam

root666

0

Nie pytam o to w jaki sposób ty chcesz to zrealizować, tylko co chcesz zrealizować. Jaki jest ogólny cel tek akcji. Chodzi mi o to nie jak jest w testowej, tylko jak będzie w docelowej. Może da się bazę zaprojektować lepiej, inne zapytania zrobić, inne zależności.

Zadanie trywialne, wybierz z tabeli "A" wszystkie rekordy i dla kazdego z nich sprawdź czy 'pole1' występuje w tabeli "B"
To zrobisz jednym zapytaniem

SELECT (jakiestam_kolumny) 
FROM A JOIN B 
ON A.pole1 = B.pole1
0
adf88 napisał(a)

Nie pytam o to w jaki sposób ty chcesz to zrealizować, tylko co chcesz zrealizować. Jaki jest ogólny cel tek akcji. Chodzi mi o to nie jak jest w testowej, tylko jak będzie w docelowej. Może da się bazę zaprojektować lepiej, inne zapytania zrobić, inne zależności.

Ok, więc jak napisałem wyżej, chodzi o "rozebranie" rekordu wsadowego i zbudowanie kilku słowników. Na razie ograniczam się do jednego pola i jednego słownika. Samego słownika nie tworzę bo nawet bez insertów do niego zauwazyłem przypadkowe wydłużenie czasu wykonania

pozdrawiam

root666

0
root666 napisał(a)

Ok, więc jak napisałem wyżej, chodzi o "rozebranie" rekordu wsadowego i zbudowanie kilku słowników.
Hę ? Możesz jaśniej. Wysil się trochę bo inaczej ci nie pomożemy.

0
adf88 napisał(a)
root666 napisał(a)

Ok, więc jak napisałem wyżej, chodzi o "rozebranie" rekordu wsadowego i zbudowanie kilku słowników.
Hę ? Możesz jaśniej. Wysil się trochę bo inaczej ci nie pomożemy.

Rozumiem, że chcesz mi pomóc, bardzo to doceniam. Ale... wysyłasz posta, ja na niego odpowiadam, a ty w tym czasie edytujesz swojego zmieniając jego treść. Stąd nie widzisz co napisałem wyżej. Zerknij proszę post wyżej i zobaczysz, że znajduje się tam opisane meritum sprawy.

pozdrawiam
root666

0

Nie widzę.
Na kilka pytań mich odpowiedziałeś tylko na jedno i to bardzo szczątkowo, nie jasno. Chyba się nie dogadamy :|

Co to niby jest "rozebranie rekordu wsadowego" [???]

0
adf88 napisał(a)

Co to niby jest "rozebranie rekordu wsadowego" [???]

Jest sobie duży rekord w tabeli "A", zawiera klikanaście pól. Dane w tym rekordzie cechują się sporą powtarzalnością. Rekordów jest bardzo dużo więc ich przechowywanie w postaci "płaskiej" jest wysoce nieefektywne, istotne jest aby rozłożyć te dane po stosunkowo niewielkich słownikach oszczędzając w ten sposób mnóstwo miejsca. 9 pól z rekordu wsadowego wyląduje docelowo w słownikach, w głównym rekordzie zostanie zastąpionych odpowiednimi wartościami kluczy ze słowników. W tym celu dla każdego pobranego rekordu i 9 jego pól sprawdzane jest czy dana wartość występuje w odpowiednim słowniku. Wykonywane jest 9 selectów aby to sprawdzić.

Ponieważ pojawił się nieoczekiwany efekt, który stał się zaczątkiem tego tematu, ograniczyłem zadanie tylko do jednego sprawdzanego pola i tylko jednej tabeli słownikowej. Dodatkowo, aby wyeliminować wpływ samego tworzenia słownika (przyrastająca tabela i aktualizacja jej indeksu) na razie nie wstawiam do słownika wartości więc wszystkie 700 tyś. SELECT'ów pytających jest chybionych.

pozdrawiam
root666

0

Jest sobie duży rekord w tabeli "A", zawiera klikanaście pól. Dane w tym rekordzie cechują się sporą powtarzalnością. Rekordów jest bardzo dużo więc ich przechowywanie w postaci "płaskiej" jest wysoce nieefektywne, istotne jest aby rozłożyć te dane po stosunkowo niewielkich słownikach oszczędzając w ten sposób mnóstwo miejsca
Czyli zrobisz to raz u siebie i masz spokój tak ? To co ci zależy na tych sekundach ?

0
adf88 napisał(a)

Jest sobie duży rekord w tabeli "A", zawiera klikanaście pól. Dane w tym rekordzie cechują się sporą powtarzalnością. Rekordów jest bardzo dużo więc ich przechowywanie w postaci "płaskiej" jest wysoce nieefektywne, istotne jest aby rozłożyć te dane po stosunkowo niewielkich słownikach oszczędzając w ten sposób mnóstwo miejsca
Czyli zrobisz to raz u siebie i masz spokój tak ? To co ci zależy na tych sekundach ?

Nie, nie opsiuję całej otoczki biznesowej tego zagadnienia bo nie ma to większego znaczenia dla skupienia się nad meritum sprawy czyli: przypadkowym, liniowo rosnącym czasem wykonania tego samego kodu.

Rekordy będa napływać do tabeli źródłowej w dużej ilości i będą cyklicznie przetwarzane przez aplikację w celu optymalnej archiwizacji. Póki co pracuję na testowej paczce ~700 tyś. rekordów. Czas przetworzenia staje się o tyle istotny aby aplikacja była w stanie nadążyć z napływającymi rekordami.

pozdrawiam

root666

0
  1. Jesli w tych 700 tys. razy tworzysz ciagle nowe obiekty (np. SqlCommand) to taki efekt da wlasnie coraz czestsze odpalanie GC (bo odpalane jest tylko kiedy zaczyna brakowac pamieci).
  2. Ja kombinowalbym sql'em, zeby te dane na raz powrzucac tam gdzie trzeba. Czy decyzja o losie jednego z 9 pol jest rozna w zaleznosci od jego wartosc? Bo jesli pole nr 1 zawsze laduje w tabeli slownikowej 1, pole 2 w tabeli 2, to wykonanie 9 zapytan sql + ewentualne sprzatajace powinno wystarczyc.
0

Eh, wiem że NIE O TO CI CHODZI. Ale jak miałbym coś podpowiedzieć, to skoro rozpieprzarz jedną tabelkę na kilka, to napisz do tego procedurkę i ją wykonaj - W CAŁOŚCI PO STRONIE BAZY. A jak chcesz mieć progressbar'a, to w procce ustawiaj jakiś parametr w innym miejscu i jego odczytuj.
Jak już chcesz aby dane wędrowały z DB do klienta i z powrotem (ale po co), to jak będziesz robić te inserty do słowników - to cache'uj wyniki ! Skoro są powtarzalne.

A co do meritum sprawy, ja bym obstawiał za tym co johny_bravo - GC. Ewentualnie może baza ... lecz raczej GC. A jak z transakcjami, robisz to w jakiejś domyślnej czy jak ?

b

0

root666

Niestety opis Twojego rozwiązania jest za mało szczegółowy żeby odpowiedzieć na to pytanie. Może być kilka powodów takiego zachowania. Jednak jeżeli nie dasz więcej szczegółów trudno będzie pomóc. Po pierwsze powinieneś postarać się wydzielić ten fragment kodu który jest odpowiedzialny za spowolnienie. Do tego może okazać się przydatne napisanie odpowiedniego testu. W ten sposób możesz dotrzeć dokładnie do tego elementu który powoduje problem.

W takich sytuacjach jak Twoja, nie ma sensu zdawać się na zgadywanie. Postaraj się podejść do tego metodycznie i upraszczaj cały proces w oddzielnym projekcie tak długo aż problem spowolnienia zniknie. Oznaczać to będzie że ostatnie uproszczenie zniwelowało przyczynę problemu.

Tak rzutem na taśmę co może być przyczyną takiego spowolnienia:

  1. Operacje są wykonywane non-stop, a Garbage Collector nie ma czasu żeby zebrać śmieci. Tą sytuację możesz wykryć przez obserwowanie ile pamięci operacyjnej i wirtualnej zajmuje w czasie Twoja aplikacja. Jeżeli to ten przypadek - musisz sam zapewnić wywoływanie GC jawnie w swojej aplikacji.

  2. Nadmierna ilość tworzonych obiektów - jeżeli zależy Ci na wydajności postaraj się przenieść co się da do procedur składowanych i widoków. Po stronie kodu nie twórz ciągle nowych obiektów, ale przygotuj obiekt, który zajmie się przetwarzaniem wszystkich danych.

  3. Sprawdź po stronie bazy danych Profilerem jak wyglądają zapytania. Sprawdź w planie wykonania zapytań czy korzystają z indeksów które utworzyłeś. Jeżeli nie masz indeksów albo są utworzone nieodpowiednio (złe kolumny) utwórz je poprawnie.

  4. Sprawdź jak wyglądają statystyki w bazie danych, przebuduj indeksy, sprawdź stan bazy danych.

  5. Jeżeli to wszystko nie pomoże spróbuj okroić problem do najmniejszego kawałka jaki dasz radę. Do takiego kawałka którym możesz się podzielić z ludźmi tutaj na forum. Wklej ten kawałek kodu wraz z informacją (a najlepiej przykładową bazą danych) tutaj na forum, i zobaczy się co może być jeszcze przyczyną. Ale pamiętaj przedtem o krokach 1- 4.

1 użytkowników online, w tym zalogowanych: 0, gości: 1