Najszybszy sposób na pobranie dużej ilości danych - API + MS SQL

0

Witam.
Pytanko mam, ponieważ borykam się z dziwnym problemem. Wczoraj wieczorem pobieranie danych z bazy (400 rekordów) trwało 11 sekund. Dzisiaj rano to samo zapytanie, ten sam sposób pobierania trwa max. 700+ milisekund... Zbaraniałem. Chciałem już dzwonić do specjalisty, bo chyba mam zwidy. Nie mogę używać EF, to nie moja baza, robie tylko swój program pod bazę, która w każdej chwili może się zmienić, a EF tego nie lubi. Znalazłem w internetach metodę, która robi takie prowizoryczne pobieranie danych z SQL i mapuje do obiektów:

        private static List<T> DataReaderMapToList<T>(SqlDataReader dr)
        {
            List<T> list = new List<T>();
            T obj = default(T);

            while (dr.Read())
            {
                obj = Activator.CreateInstance<T>();
                foreach (PropertyInfo prop in obj.GetType().GetProperties())
                {
                    var test = Nullable.GetUnderlyingType(prop.PropertyType);

                    if (!object.Equals(dr[prop.Name], DBNull.Value))
                    {
                        var nullableType = Nullable.GetUnderlyingType(prop.PropertyType);
                        prop.SetValue(obj, Convert.ChangeType(dr[prop.Name], nullableType ?? prop.PropertyType), null);
                    }
                }
                list.Add(obj);
            }

            return list;
        }

        public static List<T> GetData<T>(string connectionString, string query)
        {
            List<T> srw = new List<T>();

            using (SqlConnection con = new SqlConnection(connectionString))
            {
                con.Open();

                using (SqlCommand cmd = con.CreateCommand())
                {
                    cmd.CommandText = query;

                    using (SqlDataReader dr = cmd.ExecuteReader())
                    {
                        srw = DataReaderMapToList<T>(dr);
                    }
                }
            }

            return srw;
        }

Tragedii z tym rozwiązaniem nie ma, ale mam kilka problemów, a w tym właśnie wydajność:

  1. Mogę mieć tylko propertki o takiej nazwie jak kolumna - mogę sobie w query nazwać kolumny, ale na tym polega uniwersalność, żeby tego nie robić
  2. Nie mogę mieć zagnieżdżonych obiektów - sam też nie wiem jak to rozwiązać
public class Obiekt1
{
   public int ID {get;set;}
   public InnyObiekt Inny {get;set;}
}
  1. Wydajność jest pod znakiem zapytania, raz robi szybko, raz długo.

Ograniczam już dane datami, ale i tak wydaje mi się, że 400 rekordów to jest strasznie mało.
Co mogę z tym zrobić?
Jak poprawić wydajność i funkcjonalność?
Czy zagnieżdżone obiekty mogą powodować spadek wydajności?
Łączyć to jakąś klasą nadrzędną i to wysyłać do klienta?

0
AdamWox napisał(a):
  1. Mogę mieć tylko propertki o takiej nazwie jak kolumna - mogę sobie w query nazwać kolumny, ale na tym polega uniwersalność, żeby tego nie robić

No to w takim razie jak kod ma się sam domyśleć, którą kolumnę na które property zmapować? Musi gdzieś istnieć definicja mapować - czyli właściwie rdzeń działania ORMów.

  1. Nie mogę mieć zagnieżdżonych obiektów - sam też nie wiem jak to rozwiązać

No to tez jest problem rozwiązywany przez ORMy. Ale skoro i tak bawisz się refleksją, to przecież możesz sobie stworzyć także zagnieżdżony obiekt.

  1. Wydajność jest pod znakiem zapytania, raz robi szybko, raz długo.

No, ale to raczej nie jest wina kodu. Może serwer był czymś obciążony dodatkowo? Albo ruch w sieci większy niż zwykle?
Dopóki nie będziesz jakoś mierzył czasu działania poszczególnych etapów tej metody i zapisywał ich gdzieś, to zbyt wiele się nie wywnioskuje.

0

Wolne jest query czy mapowanie ?

0

@somekind:
Ten pierwszy punkt łączy się z tym drugim, faktycznie źle to napisałem, błagam o wybaczenie. W sensie, że jak mam zagnieżdżone obiekty to wali mi wyjątkiem, że nie ma takiej kolumny bo property zagnieżdżonego obiektu nie jest kolumną.
Co do wydajności to jedyne co, to to, że mogłem mięc wiecej programów włączonych lub zakładek w chrome. Wszystko odbywa się lokalnie, projekt jest w produkcji i wszystko odbywa się na jednej maszynie gdzie stoi klient, api i sql server.
@error91
No właśnie query nie jest super skomplikowane. Porównałem również wyciąganie wszystkich kolumn * z konkretnymi, które są mi potrzebne i różnica żadna. Mam tam tylko 3 warunki - typ rekordu, data od, data do z jednym joinem.

Czy rozwiązaniem może być Dapper?

0

Nie pytałem, czy jest skomplikowane tylko co jest wolne bo nie powiedziałeś tego wcześniej. Masz dostęp i uprawnienia na tej bazie ?

0

Oczywiście, pełne sa, full wypas. Co do tego czy query jest wydajne czy mappowanie... Podejrzewam, że mappowanie. W management studio to zapytanie daje

(392 rows affected)

 SQL Server Execution Times:
   CPU time = 282 ms,  elapsed time = 909 ms.
0
AdamWox napisał(a):

@somekind:
Ten pierwszy punkt łączy się z tym drugim, faktycznie źle to napisałem, błagam o wybaczenie. W sensie, że jak mam zagnieżdżone obiekty to wali mi wyjątkiem, że nie ma takiej kolumny bo property zagnieżdżonego obiektu nie jest kolumną.

No tak, musiałbyś jakoś wykrywać, czy property jest typu prostego czy to obiekt, a wtedy pobrać jego propertisy, i dopiero mapować kolumny.

Co do wydajności to jedyne co, to to, że mogłem mięc wiecej programów włączonych lub zakładek w chrome. Wszystko odbywa się lokalnie, projekt jest w produkcji i wszystko odbywa się na jednej maszynie gdzie stoi klient, api i sql server.

Baza tez jest na tej maszynie?

Czy rozwiązaniem może być Dapper?

Na pewno ułatwi mapowanie i pozwoli uniknać błędów wydajnościowych, które możesz popełnić pisząc swój kod. Ale możliwe też, że nie Twój kod jest przyczyną złej wydajności.

0

@somekind:
Baza też jest na tej samej maszynie. Myślisz, że problemem może być sql server lub moja maszyna? Zdecydowanie nie upieram się na swoje metody mapowania itp itd i chętnie skorzystam z gotowca jakim jest dapper, zawsze to trochę czasu zaoszczędzone. Tylko czy dapper ułatwi mi też zagnieżdżenia? Ogarnia takie rzeczy?

0
AdamWox napisał(a):

@somekind:
Baza też jest na tej samej maszynie. Myślisz, że problemem może być sql server lub moja maszyna?

No tak. Albo np. start aplikacji, bo była długo nie używana. W kodzie, który pokazałeś nie ma nic, co by usprawiedliwiało taką różnicę w czasie wykonania.

Zdecydowanie nie upieram się na swoje metody mapowania itp itd i chętnie skorzystam z gotowca jakim jest dapper, zawsze to trochę czasu zaoszczędzone. Tylko czy dapper ułatwi mi też zagnieżdżenia? Ogarnia takie rzeczy?

Z tego co wiem, to tak. Zajrzyj do sekcji "Multi mapping" w dokumentacji.

No i tak czy siak wypadałoby dodać jakieś logi, żeby wiedzieć ile czasu faktycznie podczas działania aplikacji ta metoda jest wykonywana.

0

Czy wykonujesz w swoim kodzie metodę GetData kilka razy w jednej metodzie albo w pętli?

0

Tylko raz, ale użytkownik może kilka razy zapytać o te dane. Może to nie jest to samo jakby było w pętli, ale jest opcja, że będzie klient pytał po kilka razy o te dane przełączając się pomiędzy modułami.

0

Powinieneś jakiegoś cache mieć. Jeśli np. chcesz pobrać dwie relacje i zmapować to na dwa obiekty odwzorowania obiektowo relacyjnego a za każdym razem łączysz się z serverem db i się rozłączasz, to może być to słabe rozwiązanie. Nie jestem pewien jak działa dokładnie sterownik od bazy, nie jestem też orłem z sieci, ale prawdopodobnie za każdym razem otwierasz i zamykasz socket, zamiast zrobić co trzeba za jednym zamachem i zakończyć komunikację. :-|

1

Miałem kiedyś podobny problem ale tam było zapytanie linq na 15 czy 20 laczonych tabelach. Klienci na androidzie, kilkanaście terminali.
Pisałem tu nawet o tym. @somekind stwierdził, że baza to ostatnie miejsce gdzie by szukał problemu i miał rację.
U mnie okazało się,że był jakiś dziwny problem z ustawieniami sieci na routerach (mikrotik i Unifi AP).
Dopiero jak zacząłem mierzyć czas operacji to zobaczyłem, że to nie bazą i nawet ie aplikacja bo log IIS pokazywał śmieszne czasy odpowiedzi.
Jacyś macherzy uproscili konfigurację sieciową i problemy się skończyły. Bazą może mogłaby być problemem gdyby była bardzo obciążona.
Czyli trzeba powierzyć. Ufać i kontrolować jak mawiał klasyk.

0

@jacek.placek:
Ja wszystko robię na localhost, wszystko jest w jednym miejscu - api, klient i sql.
@._.
Łącze się do bazy po dane w taki sposób jaki działa API. Nie rozumiem kwestii kilku połączeń i kilku SqlCommand. Jeśli aplikacja kliencka chce listę zamówień to to daje jedno połączenie do bazy i jedno zapytanie, buduje listę obiektów, rozłącza z bazy i zwraca do klienta.
Na tę chwilę robię to sam, jako jeden operator. Nie sprawdzałem co się stanie jak zacznie więcej osób ściągać z tego samego API listę zamówień i szczerze mówiąc boje się, że będzie z tego powodu tragedia i zgrzytanie zębów...

0

A co log iis-a pokazuje? Jakie czasy odpowiedzi na requesty?

0

Nie mam IIS-a. API uruchamiam z Visual Studio. Tak jak wspomniałem wcześniej, to jest tylko tymczasowe API żeby dostać prawdziwe dane do aplikacji klienta, ale metod wrzuconych wyżej miałem zamiar używać w produkcji. Znajdę gdzieś logi z IIS w Visual Studio?

1

VS używa IIS Express

C:\Users...\Documents\IISExpress\Logs\ i nazwa aplikacji

BTW Jak odpalasz app pod debugerem to nie wiem czy to jest sens sprawdzać wydajnościowo. IMHO trzeba odpalić to na IIS i pomierzyć.

EDIT
Generalnie konfigurację masz w .vs\config w folderze solucji (kurna, jak nie nie lubię tego słowa).

0

Wcześniej faktycznie leciałem na debugu i olałem, że coś wolno to chodzi. Później zacząłem używać Ctrl+F5, tutaj (chyba) uruchamia się bez debugera, znacznej poprawy nie było więc obawiam się, że to nie jest tego kwestia. Już jedno API mam na koncie i takich problemów nie było, też lokalnie wszystko. Różnica między tamtym, a tym co robię teraz jest taka, że poprzednie API było w WinForms + Owin SelfHost. Do budowania obiektów z danymi używam tam tych samych metod co pisałem na początku.

Wybiórczo spojrzałem na logi z IIS Express (z piątku, dzisiaj tego nie ruszyłem jeszcze) i czasy są znośne. W większości przypadków poniżej sekundy, ale były takie momenty, że logi pokazywały nawet 11 sekund... Nie jestem w stanie dokładnie powiedzieć jaka ilość danych w tym czasie była. Puściłem w management studio najgorszą z sytuacji gdzie wyciągam dokładnie 6234 pozycji. Pierwsze uruchomienie trwało 5037ms, drugie 4989ms, trzecie 5214ms, czwarte 4939ms. Wychodzi na to, że średnia jest znośna.
Czyli moje 11 sekund z API mogło wyciągnąć dane w 5 sekund z bazy, ale dodatkowe 6 sekund trwało mappowanie?

0
AdamWox napisał(a):

Wcześniej faktycznie leciałem na debugu i olałem, że coś wolno to chodzi. Później zacząłem używać Ctrl+F5, tutaj (chyba) uruchamia się bez debugera, znacznej poprawy nie było więc obawiam się, że to nie jest tego kwestia.

Uruchamia się bez debugera, ale kompilacja nadal jest w trybie Debug, a nie Release.

Czyli moje 11 sekund z API mogło wyciągnąć dane w 5 sekund z bazy, ale dodatkowe 6 sekund trwało mappowanie?

Bardzo wątpię, mapowanie to powinno być kilkaset milisekund najwyżej. Najlepiej to jednak sprofilować.

0

Ogarnę to API i zrobie w miarę wiarygodnie testy z IIS w trybie Release. Domyślam się, że może chodzić o VS dlatego dla pewności zrobię to pełnoprawnie i wtedy się odezwę.

1

Zrób profilowanie i wtedy dokładnie będziesz wiedział gdzie jest problem.

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