A może normalizowanie nie jest normalne?

Oryginalny post: Maybe Normalizing Isn't Normal

Autor: Jeff Atwood

Jednym z problemów z jakimi mierzymy się teraz przy Stack Overflow jest utrzymanie wysokiego poziomu wydajności relacyjnej bazy danych, podczas gdy jej rozmiar znacząco rośnie. Bardziej precyzyjnie, chodzi o skalowanie naszego systemu tagów. Dobrze zaprojektowana baza danych to baza znormalizowana, tak mówią tradycyjne zasady projektowania. Niemniej jednak, ja nie jestem tego taki pewien.

Dare Obasanjo opublikował świetny post pt. Kiedy nie powinieneś normalizować swojej bazy SQL, w którym podaje przykładowy schemat bazy danych dla serwisów społecznościowych. Gdybyśmy chcieli zaprojektować bazę zgodnie z szeroko akceptowanymi zasadami normalizacji, wyglądałoby to tak:

Normalizacja z pewnością sprawdza się, kiedy chodzi o ograniczenie duplikacji. Każda instancja reprezentowana jest raz i tylko raz – praktycznie nie ma więc ryzyka niespójności danych. Ale takie podejście wymaga również monstrualnych sześciu joinów, aby pobrać informacje o pojedynczym użytkowniku.

select * from Users u inner join UserPhoneNumbers upn on u.user_id = upn.user_id inner joinUserScreenNames usn on u.user_id = usn.user_id inner join UserAffiliations ua on u.user_id = ua.user_id inner join Affiliations a on a.affiliation_id = ua.affiliation_id inner join UserWorkHistory uwh on u.user_id = uwh.user_id inner join Affiliations wa on uwh.affiliation_id = wa.affiliation_id

(Update: nie jest to przykład pretendujący do bycia realnym; jest tutaj po to, aby zobrazować fakt, że potrzeba sześciu joinów – albo sześciu oddzielnych zapytań, jeśli tak wolisz – żeby dostać wszystkie informacje o użytkowniku.)

Te sześć joinów nie pomaga Twojej aplikacji w kwestiach wydajności. Pełna normalizacja może być nie tylko trudna do zrozumienia i utrudniająca pracę – może być również wolna.

Jak wskazuje Dare, oczywistym rozwiązaniem jest denormalizacja – złączenie wielu danych w jedną tabelę Users.

To działa – zapytania są trywialnie łatwe (select * from users) i prawdopodobnie znacznie szybsze. Ale w zamian za to, będziesz miał kilka dziur w swoich danych, razem z wieloma niezręcznie nazwanymi polami tablicy. Wszystkie cholerne problemy z integracją, które tak bardzo Cię męczą? Będziesz musiał się teraz nimi zająć. Gratuluję „degradacji”!

Obydwa rozwiązania mają zalety i wady. Pozwól więc, że zadam pytanie: co jest lepsze – znormalizowana baza danych, czy baza zdenormalizowana?

Podstępne pytanie. Odpowiedzią jest - to nie ma znaczenia! Dopóki nie masz milionów, milionów rekordów danych. Wszystko działa szybko dla małych n. Nowoczesny na dzisiejsze standardy pecet – powiedzmy dwurdzeniowy z 4 GB pamięci – da praktycznie identyczne wyniki wydajnościowe dla obydwu przypadków, dopóki nie rozważysz największych baz. Oczywiście, zakładając, że potraficie w zespole pisać dobrze zoptymalizowane zapytania.

Nie brakuje fascynujących relacji z wojen bazodanowych dostarczanych przez firmy w to wszystko zaangażowane. Obawiam się, że te historyjki utrzymywane są w tym samym tonie co „zgubiłem 100 kilogramów, więc Ty też możesz!”, zwróć proszę uwagę na małą gwiazdkę: wyniki mogą różnić się w każdym indywidualnym przypadku, która podczas czytania tych historyjek cały czas ma zastosowanie. Poniżej znajduje się kompilacja Tima O'Reilly'a:

Jest również blog High Scalability, na którym także można znaleźć zbiór historyjek z wojen bazodanowych:

Przede wszystkim, sprawdzian na kontakt z rzeczywistością. Częściowo to grzech pychy, wyobrażać sobie, że Twoja aplikacja to następny Flickr, YouTube albo Twitter. Jak trafnie powiedział Ted Dziubaskalowalność nie jest Twoim problemem, dostarczyć to cholerstwo, jest. Więc jeśli dochodzi do projektowania bazy danych, mierz wydajność, ale staraj się podążać w stronę rozsądnego i prostego projektu. Wybierz jakikolwiek schemat bazy, który wydaje Ci się najłatwiejszy do zrozumienia i wykorzystywania podczas codziennej pracy. Nie musi to być to, co zaprezentowałem powyżej, nie musi to być nawet część tego; możesz denormalizować fragmentami, tam gdzie ma to sens, a pozostawiać postać normalną, gdzie takiego sensu – denormalizacji - nie ma.

Mimo wielu dowodów, że normalizacja rzadko daje się skalować, odkrywam, iż wielu programistów będzie zażarcie jej broniło, dla samej tylko zasady. Długo po tym, kiedy przestało mieć to sens.

Podczas rozwijania oprogramowania Cofax w Knight Ridder, natrafiliśmy na pewne problemy, po tym kiedy dodaliśmy 17-tą gazetę do systemu. Wydajność nie była taka jak wcześniej i zdarzały się sytuacje, kiedy usługa nieodpowiadała.

Rozpoczęliśmy projekt mający na celu naprawić sytuację, odnaleźć źródło problemów. Stwierdziliśmy, że baza danych, tak dobrze zaprojektowana, nie może być problemem, pomimo symptomatycznego, gwałtownego przyrostu liczby połączeń do bazy zaraz przed awarią. Skoncentrowaliśmy się więc na optymalizacji działania samej aplikacji.

Ja się z tym nie zgodziłem i rozważyłem kilka argumentów, wskazujących że to jednak nasza baza danych wymaga uwagi. Na początku potrzebowaliśmy zoptymalizować zapytania i indexy. Gdyby było potrzeba, obliczać dane przed zapisem i unikać złączeń, tworząc kilka zdenormalizowanych tabel. Była to gorzka pigułka do przełknięcia, zwłaszcza dla mnie, ponieważ byłem projektantem tej bazy. A okazało się, że dla pozostałych było to jeszcze trudniejsze! Wezwaliśmy konsultantów. Stwierdzili, że baza jest zaprojektowana dobrze i że problemem musi być sama aplikacja.

Po dwóch miesiącach, kiedy bez rezultatu zespół produkował wiele różnych modyfikacji, aby naprawić problem, wróciliśmy do moich wcześniejszych argumentów.

Pat Helland uważa, że ludzie normalizują, ponieważ tak zostali nauczeni przez swoich profesorów. Ja jestem trochę bardziej pragmatyczny; myślę, że powinieneś normalizować, kiedy dane wskazują na następujące fakty:

  1. Normalizacja jest sensowna dla Twojego zespołu.
  2. Normalizacja pozwala uzyskać lepszą wydajność (automatycznie mierzysz wszystkie zapytania składające się na Twoje oprogramowanie, prawda?).
  3. Normalizacja chroni przed dokuczliwą duplikacją lub pomaga uniknąć ryzyka problemów synchronizacji, które są szczególnie istotne dla Twojej dziedziny albo użytkowników.
  4. Normalizacja pozwala Ci pisać prostsze zapytania i kod w ogólności.

Nigdy, przenigdy nie powinieneś normalizować bazy danych ze względu na jakąś powinność wobec duchów Boyce'a i Codda.
Normalizacja nie jest magicznym proszkiem, którym posypujesz swoją bazę danych, aby wyleczyć ją ze wszystkich chorób. Często tworzy równie wiele problemów jak rozwiązuje. Nie bój się widma denormalizacji. Duplikowane dane i problemy synchronizacji często są przeceniane i relatywnie łatwe do poprawienia przy pomocy zadań crona. Dyski oraz pamięć są tanie i tanieją co każdą nanosekundę. Mierz wydajność Twojego systemu i sam zdecyduj co działa lepiej, wolny od przesądów i uprzedzeń.

Jak rzecze stare porzekadło - normalizuj dopóki to nie boli, denormalizuj dopóki to działa.

Data publikacji oryginału: lipiec 14 2008

18 komentarze:

Anonimowy pisze...

Kto jest za normalizacją? Wpisywać Miasta! ;)

rafek pisze...

@Anonimowy: Trafiłeś tu prosto z ONETu? ;)

Maciej Aniserowicz pisze...

W komentarzach pod postem Ayende można poczytać bardzo rzeczowe argumenty Fransa Bouma (twórca LLBLGen) przemawiające ZA normalizacją: http://ayende.com/Blog/archive/2010/02/22/slaying-relational-dragons.aspx.

Anonimowy pisze...

Tak mi się problem skojarzył ;-)

Mam przyjemność pracować zarówno na projektach ze znormalizowaną bazą danych, jak i ze zdenormalizowaną. Im mniej normalizacji, tym więcej kodowania "na piechotę". Brakuje mi tu jednak
podstawowego kryterium, a więc przeznaczenia systemu.

Jeśli to ma być portal społecznościowy, to wydajność będzie kluczem, ale jak mamy do czynienia z dużą aplikacją, która ma robić skomplikowane rzeczy i ma być łatwa w konserwacji, ale niekoniecznie super responsywna, to postać znormalizowana raczej nie jest luksusem :)

Anonimowy pisze...

Jeszcze coś dodam. W pokazanych przykładach proponuję testy zapytań UPDATE, np. numeru telefonu ;)
Brakuje informacji o blokowaniu rekordu, co ma niebagatelne znaczenie dla wydajności, jeśli nie mamy dedykowanego serwera dla selectów i osobnego do update/insert.

Konradzik pisze...

Wiem, że to temat o normalizacji i nawet nie pada tam słowo 'cachowanie', ale narzucało mi się ono przez cały artykuł. Cachowaniem można ściągnąć olbrzymi ciężar z bazy danych. Tutaj dodać cache na 30 sekund, tam na 40 minut, tutaj cache który jest usuwany gdy ktoś doda post itd. Miałem okazję zobaczyć jakie to czyni cuda. W przypadku gdy potrzebujemy zrobić 6 joinów żeby pobrać wszystkie dane o userze, może po prostu lepiej je rozbić na oddzielne zapytania z których część będzie cachowana? W trybie 'persistent connection' takie rozbicie powinno odbyć się bez dodatkowych kosztów.

Anonimowy pisze...

@Konradzik - wg. mnie masz rację, ale zaraz znajdą się spece od "prawdziwych języków" i "prawdziwych projektów", którzy słabo rozumieją specyfikę i potrzeby projektów webowych, w których zmian dokonuje się kilka razy częściej, reaguje się szybciej niż w aplikacjach kompilowanych i niektóre rozwiązania omija szerokim łukiem. Cache to pierwsza rzecz jaką się robi w celu optymalizacji. Oczywiście nie chcę przez to powiedzieć, że optymalizacja samej bazy, czy zapytań jest bez znaczenia, bo nie jest.

Konradzik pisze...
Ten komentarz został usunięty przez autora.
Anonimowy pisze...

Ja uważam, że wszystko z głową trzeba robić. Ja standardowo normalizuję, jeśli są problemy z wydajnością, to dopiero wtedy denormalizacja. Jeśli argumentujecie, że dyski są tanie, to zważcie, że zarządzanie bazami (backupy, migracje, ETL-e, itp) większymi o rząd lub dwa wcale już takie tanie nie jest ....

Konradzik pisze...

Przejrzałem te linki które pojawiły się w artykule. Szczerze polecam, są "mindblowing", szczególnie http://highscalability.com (chyba lubię mieć rzeczy wypunktowane), nie znałem tego bloga wcześniej. Świetnie jest się dowiedzieć, że człowiek po tylu latach jeszcze nic tak na prawdę nie widział :)

"MySQL i memcached" jest określane jako "first step". A nawiązania do "cachowania" są typu (podobnie jak napisał to dwa posty wyżej `Anonimowy`) "Cache the hell out of everything".

I jeszcze jedna rzecz z wspomnianego powyżej bloga - można bronić innych języków używanych w serwisach www, ale autorytety sieci mówią jasno - perl. Może czas i na mnie?

Greg pisze...

Mnie też słowo cache'owanie przychodzi do głowy. Ale chciałem napisać o czymś innym. Moim profesorowie na studiach nigdy nie wmawiali mi, że normalizacja jest lekiem na całe zło. Nauczyli mnie tego jak narzędzia, z którego mogę skorzystać. Bez dogmatów.
Jest takie zdanie, które pasuje do każdej niemal odpowiedzi. Czy normalizować bazę, czy ją denormalizować? TO ZALEŻY. It depends - mantra pragmatycznego programisty ;)
W swoich projektach zawsze mam znormalizowaną bazę (nie wiem tak naprawdę która to postać, 2, 3, czy BC?) bo pozwala uniknąć błędów programistów. Po wdrożeniu systemu, gdy wydajność zaczyna być problemem - memchace jeśli serwer pozwala, albo jakiś zaprogramowany cache. Denormalizacja w krytycznych miejscach też się zdarza.

Tomasz Kowalczyk pisze...

To miałeś fajnych profesorów którzy pozwalali na własne idee. ;] Ja generalnie jestem za chech'owanie, szczególnie że w nowoczesnych frameworkach mamy narzędzia takie jak Doctrine czy Propel które załatwiają za nas całą brudną robotę.

Tomasz Kowalczyk pisze...

"chech'owanie" == "cache'owaniem", musiałem pisać bardzo szybko. ;]

Łukasz pisze...

Wiem, że to strasznie naiwne pytanie, ale czy polecicie jakiś graficzny edytor SQLa, w którym wpisuje się zapytania ręcznie, ale który nie wygląda jak program DOS-owy?

Immortal pisze...

@Lukasz
A nie lepiej zadać to pytanie na devPytaniach??

Łukasz pisze...

Gdybym znał tę stronę, to pewnie bym je tam zadał. Tak więc zrobię. Dzięki.

Anonimowy pisze...

Straszliwie tendencyjny artykul, w dodatku pokazujacy tylko niewiedze autora. Atwoodowi wydaje sie, ze nikt nie rozumie powodu stosowania normalizacji, a on, oswiecony, odkryl i rozglasza "Prawde". Normalizacja nie jest jakims 'teoretycznym' wymyslem profesorow oderwanych od rzeczywistosci - jest sposobem uporzadkowania danych w celu zwiekszenia ich czytelnosci, elastycznosci oraz unikniecia anomalii operacji bazodanowych. Wystarczy troche poczytac - publikacje na ten temat maja juz kilkadziesiat lat i wcale nie sa jakies przestarzale. Jedna z nowszych ksiazek - "Relational Database Index Design" jest szczegolnie dobra.

Normalizacja jest bardzo dobra technika, dlatego podczas tworzenia schematu bazy danych najlepiej najpierw znormalizowac schemat i dopiero pozniej, jesli pojawia sie jakies problemy z wydajnoscia wprowadzic pewne 'ograniczone' denormalizacje.
Nalezy przy tym pamietac, ze denormalizacje mozemy wprowadzic na poziomie schematu fizycznego bazy danych, tak ze schemat logiczny pozostaje dalej w postaci znormalizowanej.

W odroznieniu od denormalizacji oraz "optymalizacji" bazy danych, buforowanie danych jest bardziej "niebezpiecznym" podejsciem zwiekszania wydajnosci - dlatego, ze ryzykujemy spojnosc buforowanych danych.

Moze gdyby autor wiecej poczytal i zbadal temat to nie pisalby takich dyletanckich artykulow.

Anonimowy pisze...

denormalizację danych i cache'owanie można stosować tylko tam gdzie spójność danych i czas życia danych są nieistotne.

osobiście nie wiem jak można cache'ować(przynajmniej w ten najgłupszy sposób bo są i mądrzejsze) w serwisach tworzonych przez społeczności, to tak jakby cache'ować odpowiedź na pytanie "która jest godzina"

<10:40> A - która jest godzina?
<10:40> B - 10:40
<10:45> A - która jest godzina?
<10:45> B - 10:40

Oczywiście można dać czas życia na 1 minutę

<10:40:35> A - która jest godzina?
<10:40:40> B - 10:40
<10:41:10> A - która jest godzina?
<10:41:15> B - 10:40

Oczywiście można czyścić cache o pełnej minucie

<10:40:35> A - która jest godzina, minuta i sekunda?
<10:40:40> B - 10:40:40
<10:41:10> A - która jest godzina, minuta i sekunda?
<10:41:15> B - 10:41:15
<10:41:20> A - która jest godzina, minuta i sekunda?
<10:41:25> B - 10:41:15

Zmianę odpowiedzi na tego rodzaju zapytanie można przewidzieć, ale opierać się na statystyce w przypadku możliwości zmiany danych w każdej niemożliwej do przewidzenia (przynajmniej do czasu opracowania Teorii Wszystkiego) chwili

Więc może zamiast ułatwiać sobie, a utrudniać przyszłym pokoleniom życie cache'owaniem i innymi głupotami lepiej zająć się fizyką?

Prześlij komentarz

Related Posts with Thumbnails