efektywny projekt tabeli / indeksu mysql dla 35 milionów wierszy + tabela, z ponad 200 odpowiednimi kolumnami (podwójnymi), o które można zapytać o dowolną kombinację

17

Szukam porady na temat projektowania tabeli / indeksu w następującej sytuacji:

Mam dużą tabelę (dane historii cen akcji, InnoDB, 35 milionów wierszy i rośnie) ze złożonym kluczem podstawowym (assetid (int), date (date)). oprócz informacji o cenach mam 200 podwójnych wartości, które muszą odpowiadać każdemu rekordowi.

CREATE TABLE `mytable` (
`assetid` int(11) NOT NULL,
`date` date NOT NULL,
`close` double NOT NULL,
`f1` double DEFAULT NULL,   
`f2` double DEFAULT NULL,
`f3` double DEFAULT NULL,   
`f4` double DEFAULT NULL,
 ... skip a few 
`f200` double DEFAULT NULL, 
PRIMARY KEY (`assetid`, `date`)) ENGINE=`InnoDB` DEFAULT CHARACTER SET latin1 COLLATE
    latin1_swedish_ci ROW_FORMAT=COMPACT CHECKSUM=0 DELAY_KEY_WRITE=0 
    PARTITION BY RANGE COLUMNS(`date`) PARTITIONS 51;

początkowo zapisałem 200 podwójnych kolumn bezpośrednio w tej tabeli, aby ułatwić aktualizację i wyszukiwanie, i działało to dobrze, ponieważ jedyne zapytania wykonane w tej tabeli dotyczyły atrybutu i daty (są one religijnie uwzględnione w każdym zapytaniu dotyczącym tej tabeli ), a 200 podwójnych kolumn zostało odczytanych. Rozmiar mojej bazy danych wynosił około 45 Gig

Jednak teraz mam wymaganie, w którym muszę mieć możliwość zapytania do tej tabeli za pomocą dowolnej kombinacji tych 200 kolumn (o nazwach f1, f2, ... f200), na przykład:

select from mytable 
where assetid in (1,2,3,4,5,6,7,....)
and date > '2010-1-1' and date < '2013-4-5'
and f1 > -0.23 and f1 < 0.9
and f117 > 0.012 and f117 < .877
etc,etc

nigdy wcześniej nie miałem do czynienia z tak dużą ilością danych, więc moim pierwszym instynktem było to, że indeksy były potrzebne w każdej z tych 200 kolumn, albo skończyłbym się skanowaniem dużych tabel itp. Dla mnie oznaczało to, że Potrzebowałem tabeli dla każdej z 200 kolumn z kluczem podstawowym, wartością i indeksem wartości. Więc poszedłem z tym.

CREATE TABLE `f1` (
`assetid` int(11) NOT NULL DEFAULT '0',
`date` date NOT NULL DEFAULT '0000-00-00',
`value` double NOT NULL DEFAULT '0',
PRIMARY KEY (`assetid`, `date`),
INDEX `val` (`value`)
) ENGINE=`InnoDB` DEFAULT CHARACTER SET latin1 COLLATE latin1_swedish_ci ROW_FORMAT=COMPACT CHECKSUM=0 DELAY_KEY_WRITE=0;

wypełniłem i zindeksowałem wszystkie 200 tabel. Pozostawiłem nietkniętą główną tabelę ze wszystkimi 200 kolumnami, ponieważ regularnie jest ona sprawdzana w zakresie assetid i zakresu dat oraz wybierane są wszystkie 200 kolumn. Uznałem, że pozostawienie tych kolumn w tabeli nadrzędnej (nieindeksowane) do celów odczytu, a następnie dodatkowo indeksowanie ich we własnych tabelach (do filtrowania złączeń) byłoby najbardziej wydajne. Uruchomiłem wyjaśnienia na temat nowej formy zapytania

select count(p.assetid) as total 
from mytable p 
inner join f1 f1 on f1.assetid = p.assetid and f1.date = p.date
inner join f2 f2 on f2.assetid = p.assetid and f2.date = p.date 
where p.assetid in(1,2,3,4,5,6,7)
and p.date >= '2011-01-01' and p.date < '2013-03-14' 
and(f1.value >= 0.96 and f1.value <= 0.97 and f2.value >= 0.96 and f2.value <= 0.97) 

Rzeczywiście mój pożądany wynik został osiągnięty, wyjaśnienie pokazuje mi, że zeskanowane wiersze są znacznie mniejsze dla tego zapytania. Jednak skończyło się z pewnymi niepożądanymi skutkami ubocznymi.

1) moja baza danych zmieniła się z 45 Gig na 110 Gig. Nie mogę dłużej przechowywać bazy danych w pamięci RAM. (Po drodze mam jednak 256 GB pamięci RAM)

2) wstawianie co noc nowych danych należy teraz wykonać 200 razy zamiast raz

3) konserwacja / defragmentacja nowych 200 stołów zajmuje 200 razy więcej niż tylko 1 stół. Nie można go ukończyć w ciągu nocy.

4) zapytania dotyczące tabel f1 itp. Niekoniecznie są wydajne. na przykład:

 select min(value) from f1 
 where assetid in (1,2,3,4,5,6,7) 
 and date >= '2013-3-18' and date < '2013-3-19'

powyższe zapytanie, chociaż wyjaśnia, że ​​wygląda na <1000 wierszy, może potrwać ponad 30 sekund. Zakładam, że dzieje się tak, ponieważ indeksy są zbyt duże, aby zmieścić się w pamięci.

Ponieważ było wiele złych wiadomości, szukałem dalej i znalazłem podział. Zaimplementowałem partycje na głównym stole, partycjonowane co 3 miesiące. Wydawało mi się, że miesięcznik ma sens, ale przeczytałem, że po uzyskaniu ponad 120 partycji wydajność spada. dzielenie kwartalne pozostawi mnie pod tym przez następne 20 lat. każda partycja ma nieco mniej niż 2 gig. Uruchomiłem wyjaśnianie partycji i wszystko wydaje się prawidłowo przycinane, więc niezależnie od tego, uważam, że partycjonowanie było dobrym krokiem, przynajmniej do analizy / optymalizacji / naprawy.

Spędziłem dużo czasu z tym artykułem

http://ftp.nchu.edu.tw/MySQL/tech-resources/articles/testing-partitions-large-db.html

moja tabela jest obecnie podzielona na partycje z kluczem podstawowym. W artykule wspomniano, że klucze podstawowe mogą spowolnić partycjonowaną tabelę, ale jeśli masz komputer, który może to obsłużyć, klucze podstawowe na partycjonowanej tabeli będą szybsze. Wiedząc, że mam po drodze dużą maszynę (256 G RAM), zostawiłem klucze włączone.

tak, jak widzę, oto moje opcje

opcja 1

1) usuń dodatkowe 200 tabel i pozwól zapytaniu wykonać skanowanie tabeli w celu znalezienia wartości f1, f2 itp. nieunikalne indeksy mogą w rzeczywistości obniżyć wydajność prawidłowo podzielonej na partycje tabeli. uruchom wyjaśnienie, zanim użytkownik uruchomi kwerendę i odrzuć je, jeśli liczba zeskanowanych wierszy przekroczy określony przeze mnie próg. ocalę sobie ból gigantycznej bazy danych. Cholera, i tak wszystko wkrótce zostanie w pamięci.

podpytanie:

czy to brzmi jak wybrałem odpowiedni schemat partycji?

Opcja 2

Podziel wszystkie 200 tabel według tego samego 3-miesięcznego schematu. ciesz się skanowaniem mniejszych wierszy i pozwól użytkownikom uruchamiać większe zapytania. teraz, gdy są one podzielone na partycje, mogę nimi zarządzać 1 partycją na raz w celach konserwacyjnych. Cholera, i tak wszystko wkrótce zostanie w pamięci. Opracuj skuteczny sposób aktualizowania ich co noc.

podpytanie:

Czy widzisz powód, dla którego mogę uniknąć indeksowania kluczy głównych w tabelach f1, f2, f3, f4 ..., wiedząc, że zawsze mam identyfikator zasobu i datę podczas zapytania? wydaje mi się to sprzeczne z intuicją, ale nie jestem przyzwyczajony do zestawów danych o tym rozmiarze. to zmniejszyłoby bazę danych o kilka, które zakładam

Opcja 3

Upuść kolumny f1, f2, f3 w tabeli głównej, aby odzyskać to miejsce. wykonaj 200 dołączeń, jeśli muszę przeczytać 200 funkcji, być może nie będzie to tak wolne, jak się wydaje.

Opcja 4

Wszyscy macie lepszy sposób na ustrukturyzowanie tego, niż do tej pory myślałem.

* UWAGA: Wkrótce dodam kolejne 50-100 tych podwójnych wartości do każdego elementu, więc muszę zaprojektować wiedząc, że nadchodzi.

Dzięki za wszelką pomoc

Aktualizacja nr 1 - 24.03.2013

Poszedłem z pomysłem zasugerowanym w komentarzach, które otrzymałem poniżej i stworzyłem jedną nową tabelę z następującą konfiguracją:

create table 'features'{
  assetid int,
  date    date,
  feature varchar(4),
  value   double
}

Podzieliłem tabelę na 3 miesiące.

Zdmuchnąłem wcześniejsze 200 tabel, więc moja baza danych została przywrócona do 45 Gig i zaczęłam wypełniać ten nowy stół. Półtora dnia później zakończyło się, a moja baza danych znajduje się teraz na pulchnym 220 koncertach!

Daje to możliwość usunięcia tych 200 wartości z tabeli głównej, ponieważ mogę je pobrać z jednego połączenia, ale tak naprawdę dałoby mi to tylko 25 koncertów

Poprosiłem go, aby utworzył główny klucz na assetid, date, feature i indeks wartości, a po 9 godzinach chasing naprawdę nie zrobił to żadnego wrażenia i wydawało się, że zamarł, więc zabiłem tę część.

Odbudowałem kilka partycji, ale nie wydawało się, aby zajmowały dużo miejsca.

Tak więc rozwiązanie wygląda na to, że prawdopodobnie nie będzie idealne. Zastanawiam się, czy wiersze zajmują znacznie więcej miejsca niż kolumny, czy właśnie dlatego to rozwiązanie zajęło o wiele więcej miejsca?

Natknąłem się na ten artykuł:

http://www.chrismoos.com/2010/01/31/mysql-partitioning-tables-with-millions-of-rows

dało mi to pomysł. To mówi:

Na początku myślałem o partycjonowaniu RANGE według daty i chociaż używam daty w moich zapytaniach, bardzo często zapytanie ma bardzo duży zakres dat, co oznacza, że ​​może z łatwością obejmować wszystkie partycje.

Teraz dzielę też zakresy według dat, ale pozwolę też na wyszukiwanie według dużego zakresu dat, co zmniejszy skuteczność mojego podziału. Zawsze będę mieć zakres dat podczas wyszukiwania, ale zawsze będę też mieć listę aktywów. Być może moim rozwiązaniem powinno być podzielenie według atrybutu i daty, gdzie identyfikuję najczęściej wyszukiwane zakresy identyfikatorów zasobów (które mogę wymyślić, istnieją standardowe listy, S&P 500, Russell 2000 itp.). W ten sposób prawie nigdy nie spojrzałbym na cały zestaw danych.

Z drugiej strony, i tak jestem przede wszystkim nastawiony na assetid i date, więc może to nie pomoże.

Wszelkie dodatkowe uwagi / komentarze będą mile widziane.

dyeryn
źródło
2
Nie rozumiem, dlaczego potrzebujesz 200 stolików. Jeden stół (value_name varchar(20), value double)byłby w stanie przechowywać wszystkiego ( value_nameistota f1, f2...)
a_horse_with_no_name
dzięki. powodem, dla którego umieściłem je indywidualnie było przekroczenie limitu 50 indeksów na stole. Myślałem o umieszczeniu ich w 5 tabelach, po 40 wartości, ale wstawiam około 17000 rekordów dziennie dla każdego z nich i nie wiedziałem, jaka będzie wydajność wstawiania na stole z 40 indeksami. zauważ, że każda kombinacja assetid, date otrzymuje własne wartości f1, f2 ... Czy sugerujesz pojedynczą tabelę z (assetid, data, wartość_nazwa, wartość), z kluczem podstawowym assetid, data, może indeks na (nazwa_wartości, wartość)? ta tabela miałaby 35 milionów * 200 = 7 miliardów wierszy, ale może dobrze podzielony na partycje działałby?
dyeryn
zaktualizowany post z moimi doświadczeniami dotyczącymi tej metody
dyeryn
mam ostateczne rozwiązanie w fazie rozwoju, zaktualizuję je po zakończeniu. jest to zasadniczo zaproponowane tutaj rozwiązanie z pojedynczą tabelą z określonym partycjonowaniem i logicznym dzieleniem na fragmenty.
dyeryn
Czy może pomóc inny silnik pamięci masowej? Zamiast InnoDb może spróbować InfiniDB? Dane kolumnowe, wzorce dostępu wyglądają jak duża aktualizacja partii, odczyty oparte na zakresie i minimalne utrzymanie tabeli.
niechlujny

Odpowiedzi:

1

przypadkiem przyjmuję również obsługę klienta, w której zaprojektowaliśmy strukturę pary klucz-wartość dla elastyczności, a obecnie tabela ma ponad 1,5B wierszy, a ETL jest zbyt wolny. w moim przypadku jest wiele innych rzeczy, ale czy myślałeś o tym projekcie? będziesz mieć jeden wiersz ze wszystkimi obecnymi 200 kolumnami, ten wiersz zostanie przekonwertowany na 200 wierszy w projekcie pary klucz-wartość. zyskasz przewagę miejsca dzięki temu projektowi w zależności od danego AssetID i Data, ile wierszy faktycznie zawiera wszystkie wartości 200 f1 do f200? jeśli powiesz, że nawet 30% kolumn ma wartość NULL, oznacza to oszczędność miejsca. ponieważ w projekcie pary klucz-wartość, jeśli wartość o wartości NULL tego wiersza nie musi znajdować się w tabeli. ale w istniejącym projekcie struktury kolumny nawet NULL zajmuje miejsce. (Nie jestem w 100% pewien, ale jeśli masz więcej niż 30 kolumn NULL w tabeli, NULL zajmie 4 bajty). jeśli zobaczysz ten projekt i założysz, że wszystkie 35M wierszy ma wartości we wszystkich 200 kolumnach, wówczas bieżąca db stanie się natychmiast 200 * 35M = 700M wierszy w tabeli. ale nie będzie dużo miejsca w tabeli, co miałeś ze wszystkimi kolumnami w pojedynczej tabeli, ponieważ właśnie transponujemy kolumny do wiersza. w tej operacji transpozycji faktycznie nie będziemy mieli wierszy, których wartości to NULL. dzięki czemu możesz uruchomić zapytanie dla tej tabeli i zobaczyć, ile jest wartości null, i oszacować docelowy rozmiar tabeli, zanim ją zaimplementujesz. ale nie będzie dużo miejsca w tabeli, co miałeś ze wszystkimi kolumnami w pojedynczej tabeli, ponieważ właśnie transponujemy kolumny do wiersza. w tej operacji transpozycji faktycznie nie będziemy mieli wierszy, których wartości to NULL. dzięki czemu możesz uruchomić zapytanie dla tej tabeli i zobaczyć, ile jest wartości null, i oszacować docelowy rozmiar tabeli, zanim ją zaimplementujesz. ale nie będzie dużo miejsca w tabeli, co miałeś ze wszystkimi kolumnami w pojedynczej tabeli, ponieważ właśnie transponujemy kolumny do wiersza. w tej operacji transpozycji faktycznie nie będziemy mieli wierszy, których wartości to NULL. dzięki czemu możesz uruchomić zapytanie dla tej tabeli i zobaczyć, ile jest wartości null, i oszacować docelowy rozmiar tabeli, zanim ją zaimplementujesz.

drugą zaletą jest wydajność odczytu. jak wspomniałeś, nowy sposób zapytania danych to dowolna kombinacja tej kolumny f1 do f200 w klauzuli where. z parą wartości klucza projekt f1 do f200 są obecne w jednej kolumnie, powiedzmy „FildName”, a ich wartości są obecne w drugiej kolumnie, powiedzmy „FieldValue”. możesz mieć indeks CLUSTERED w obu kolumnach. Twoje zapytanie będzie UNION z tych Selekcji.

GDZIE (FiledName = „f1” i FieldValue MIĘDZY 5 ORAZ 6)

UNIA

(FiledName = „f2” i FieldValue MIĘDZY 8 ORAZ 10)

itp.....

Dam ci kilka wyników z rzeczywistego serwera prod. mamy 75 kolumn cenowych dla każdego KADERA bezpieczeństwa.

Anup Shah
źródło
1

W przypadku tego rodzaju danych, w których należy wstawić wiele wierszy, a także potrzebować naprawdę dobrej wydajności zapytań analitycznych (zakładam, że tak jest w tym przypadku), może się okazać, że kolumna RDBMS jest dobrze dopasowana . Spójrz na Infobright CE i InfiniDB CE (oba kolumny do przechowywania danych podłączone do MySQL), a także Vertica CE (więcej podobnych do PostgreSQL zamiast MySQL) ... wszystkie te edycje społeczności są bezpłatne (chociaż Vertica nie jest open source, skaluje się do 3 węzłów i 1 TB danych za darmo). Kolumnowe RDBMS zazwyczaj oferują czasy odpowiedzi „duże zapytanie”, które są 10-100 razy lepsze niż oparte na wierszach, a czasy ładowania są 5-50 razy lepsze. Musisz użyć ich poprawnie, bo śmierdzą (nie wykonuj operacji w jednym rzędzie ... wykonuj wszystkie operacje masowo), ale poprawnie użyte, naprawdę działają. ;-)

HTH, Dave Sisk

Dave Sisk
źródło
1
Mamy prawie miliard wierszy danych typu clickstream (nie różniących się tak bardzo od giełdowych danych giełdowych) w 3-węzłowej instalacji Vertica ... możemy załadować dane z całych dni w około 15 sekund, a czasy odpowiedzi na zapytania otrzymujemy w zakres 500 milisekund. W twoim przypadku z pewnością wygląda na to, że warto to sprawdzić.
Dave Sisk
Mogę ręczyć za to samo. W mojej ostatniej firmie mieliśmy 8-węzłowy klaster Vertica z mniej więcej taką samą liczbą wierszy i proste zapytania agregacyjne w całym zestawie zwracane w ciągu 1-3 sekund (średnio). To był około 1/4 kosztu naszego wcześniejszego klastra Greenplum.
bma