Archive | Kwiecień 2013

Hyper-V vs VirtualBox

Zainstalowałem Windows 8.

Po przeczytaniu kilku entuzjastycznych postów o Hyper-V uruchomiłem tą opcję w systemie.
Niestety nie miałem czasu, aby się nią pobawić. Do pracy używam na co dzień VirtualBox, którego sobie bardzo chwalę.

Zdziwiłem się „lekko”, kiedy po uruchomieniu Hyper-V VirtualBox zaczął rzucać komunikatami błędów. Jeszcze bardziej się zdziwiłem, kiedy okazało się, że wcale nie jest tak prosto wyłączyć Hyper-V.

Ale znalazłem polecenie które załatwiło sprawę (cmd.exe uruchom jako administrator):

dism.exe /Online /Disable-Feature:Microsoft-Hyper-V /All

PS. Hyper-V „kradnie” sprzętowe wsparcie procesora dla wirtualizacji. VirtualBox próbuje uruchamiać wirtualne maszyny z takim wsparciem, nie znajduje go i sypie błędami.

To Guid or not to Guid

Lubię Guidy.

Idea identyfikatora, który może być generowany gdziekolwiek i pozostaje unikalny bardzo mi się podoba.

Właśnie, czy na pewno unikalny?

Czytałem kiedyś artykuł, do którego nie mogę się teraz dokopać, w którym autor opisywał sposób w jaki guid jest generowany. Wartość jest składana z kilku wielkości –
stempla czasowego z przyzwoitą rozdzielczością, adresu MAC karty sieciowej i ostatniej – losowej części.
Co prawda, RFC dla UUID (a Guid jest Microsoftową implementacją UUID) zezwala na czysto losowe generowanie wartości, ale takich implementacji lepiej unikać.
Autor artykułu podsumował, że można z bardzo wysokim prawdopodobieństwem stwierdzić, że na maszynie wyposażonej w kartę sieciową unikalnym numerem wygenerowany Guid także będzie unikalny.

Jak już wspomniałem lubię Guidy i stosuję je wszędzie gdzie się da. Ale ostatnio ten nawyk wpędził mnie w kłopoty.

Dostałem do realizacji mały projekt usługi, która po TCP połączy się pewnymi urządzeniami, i będzie zbierała dane, które należy obrobić i wynik gdzieś zapisać. Dane będą analizowane „kiedyś”.

Niewiele się zastanawiając napisałem nieco zmodyfikowaną implementację wzorca producer-consumer, wstępnie obrobione dane pchałem do bazy SQLite (z pewnych względów nie mogłem użyć pliku tekstowego, instalacja MySQLa, czy Postgresa wydała mi się wytaczaniem armaty na muchę).
I właśnie, projektujac tablicę gdzie zapisywane są ramki danych, „z rozpędu” klucz główny zrobiłem jako Guid.

Na początku wszystko gładko szło. Rekordy przychodziły w tempie od kilkunastu do kilkuset na sekundę.

Po kilku godzinach, zauważyłem, że moja mała usługa zabiera coraz większe ilości pamięci. Początkowo podejrzewałem jakiś wyciek pamięci, ale po rzucie oka na log okazało się, że baza SQLite nie nadąża z zapisem danych, te co prawda są buforowane w pamięci, a ta musi się kiedyś skończyć.
Bez konkretnego pomysłu, na wszelki wypadek zrobiłem vacuum bazy, dane zaczęły się zapisywać znacznie szybciej, rozmiary bufora w RAM się zmniejszyły, po 15 minutach, powtórka – znowu rośnie. Coś zaczęło mi świtać…

Trochę podstaw.

Jak działa indeks bazy danych? W uproszczeniu – przechowuje dane z indeksowanej kolumny w pewnym porządku, jeśli indeksujemy kolumnę typu integer, to dane indeksu są posortowane wg wartości tej kolumny i zawierają odnośnik – w którym fizycznie miejscu pliku bazy danych znajduje się rekord.

Wyobraźmy sobie rękopis – nie posortowane kartki bez ładu i składu. Chcielibyśmy wprowadzić nieco ładu do tego chaosu, i przynajmniej znać kolejność stron. Bierzemy kartkę papieru – to będzie nasz indeks, i piszemy – strona 1 – 5 kartka stosu, strona 2 – 24 kartka, strona 3 – 132 kartka. I tak dalej.
Chcąc znaleźć konkretną stronę rękopisu bierzmy do ręki nasz indeks i wiemy gdzie jej szukać, bez indeksu jesteśmy skazani na przeszukiwanie każdej kartki od początku. Jeśli dostaniemy od naszego szalonego pisarza kolejny zestaw stron – uzupełniamy indeks.
Wszystko działa, jeśli otrzymujemy kartki w kolejności chronologicznej – dopisujemy kolejne wartości na końcu naszej kartki-indeksie, strona 2054 – kartka 2055, strona 2055 – kartka 2056.

Ale co w przypadku kiedy kartki dostajemy w zupełnie losowym porządku? Otrzymaliśmy stronę 100, następnie 107, a potem 101. 101 powinna znaleźć się między 100 a 107 w naszym indeksie. Bierzemy więc nożyczki i przecinamy nasz indeks między 100 i 107, i wklejamy tam kawałek kartki na którym wpisujemy 101.
Kartka którą wkleiliśmy jest znaczne większa niż potrzeba do wpisania 101, ale lepiej mieć trochę wolnego miejsca niż z każdym kolejnym numerem powtarzać operację Cut&Paste, która jest bardzo pracochłonna (To wolne miejsce nazywamy fragmentacją indeksu). Niestety duża ilość czystego miejsca w indeksie zwiększa jego objętość, i jego przeszukiwanie zajmuje więcej czasu.

Teraz wyobraźmy sobie, że zamiast numerów stron, które mniej-więcej zachowują jakiś porządek, indeksujemy wg jakiejś zupełnie losowej wartości. Co kilka rekordów, operacja cięcia i wklejania musi być powtarzana, wklejane dodatkowe kartki maja mnóstwo wolnego miejsca.

Chaos.

Dokładnie to się dzieje w bazie danych jeśli indeksujemy kolumnę zawierającą typ UUID. Wszystko jest OK, kiedy mamy mało rekordów, albo są one stosunkowo rzadko zapisywane. W innym przypadku zaczynają się kłopoty.

Guid jako identyfikator w bazie danych

Plusy:
Generowane po stronie klienta bazy. Nie trzeba sprawdzać to też za wartość baza nam wygenerowała.
Znacznie upraszcza replikację bazy – pola autoincrement przenosi się znacznie trudniej.
Globalny identyfikator, wartość pola typu autoincrement jest unikalna tylko w jednej tablicy.

Minusy
Zajmują więcej miejsca
Posortowane dane wg tego pola nie mówią nic, autoincrement informuje kolejności dodawania rekordów.
Trudniejsze w debugowaniu ( UserId = 123, czy UserId = {EFC49FE5-F44F-4AF0-B7E7-FC50A202B14C})

PS. Wracając do analogii rękopisu. Jeśli naszą pociętą i poklejoną kartkę indeksu przepiszemy na nową, eliminując puste miejsca – to przeprowadziliśmy defragmentację indeksu.