2011-02-01 21 views
7

Mam prostą usługę z wysokim wynikiem dla gry online, która stała się bardziej popularna niż oczekiwano. Najwyższy wynik to usługa sieciowa, która wykorzystuje backend MYSQL z prostą tabelą, jak pokazano poniżej. Każdy rekord o wysokim wyniku jest przechowywany jako wiersz w tej tabeli. Problem polega na tym, że przy> 140 tys. Wierszy widzę, że niektóre kluczowe zapytania zwalniają tak bardzo, że wkrótce będą zbyt wolne dla żądań serwisowych.Skalowanie wysokiej bazy danych

Głównym tabela wygląda następująco:

  • id jest unikalny klucz dla każdego zdobyć rekord
  • gra jest numerem ID gry, który przedstawił ocenę (obecnie zawsze równy „1” wkrótce będzie musiał obsługiwać więcej gier choć)
  • nazwa jest wyświetlana nazwa składania tego gracza
  • playerID to unikatowy identyfikator dla danego użytkownika
  • wynik to wynik numeryczny reprezentacja ex 42035
  • czas to czas składania
  • pozycja to duża liczba całkowita, która w unikatowy sposób sortuje wyniki punktacji dla danej gry. Jest to wspólne dla ludzi, którzy wiążą się z określonym wynikiem, więc w takim przypadku remis zostaje przerwany przez osoby, które przesłały pierwszy. W związku z tym wartość w tym polu jest równa w przybliżeniu "wynik * 100000000 + (MAX_TIME - time)"
 
+----------+---------------+------+-----+---------+----------------+ 
| Field | Type   | Null | Key | Default | Extra   | 
+----------+---------------+------+-----+---------+----------------+ 
| id  | int(11)  | NO | PRI | NULL | auto_increment | 
| game  | int(11)  | YES | MUL | NULL |    | 
| name  | varchar(100) | YES |  | NULL |    | 
| playerId | varchar(50) | YES |  | NULL |    | 
| score | int(11)  | YES |  | NULL |    | 
| time  | datetime  | YES |  | NULL |    | 
| rank  | decimal(50,0) | YES | MUL | NULL |    | 
+----------+---------------+------+-----+---------+----------------+ 

indeksy wyglądać następująco:

 
+-----------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+ 
| Table  | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | 
+-----------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+ 
| pozscores |   0 | PRIMARY |   1 | id   | A   |  138296 |  NULL | NULL |  | BTREE  |   | 
| pozscores |   0 | game  |   1 | game  | A   |  NULL |  NULL | NULL | YES | BTREE  |   | 
| pozscores |   0 | game  |   2 | rank  | A   |  NULL |  NULL | NULL | YES | BTREE  |   | 
| pozscores |   1 | rank  |   1 | rank  | A   |  138296 |  NULL | NULL | YES | BTREE  |   | 
+-----------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+ 

Kiedy użytkownik wysokie wyniki, zazwyczaj zażądać około 75 najlepszych wyników z dowolnego punktu na liście "malejąco według rankingu". Żądania te są typowe dla "zawsze" lub tylko dla wyników w ciągu ostatnich 7 dni.

Typowe zapytanie wygląda następująco: "SELECT * FROM scoretable WHERE game=1 AND time>? ORDER BY rank DESC LIMIT 0, 75;" i działa w ciągu 0,00 sek.

Jednakże, jeśli zażądasz pod koniec listy "SELECT * FROM scoretable WHERE game=1 AND time>? ORDER BY rank DESC LIMIT 10000, 75;" i działa w 0.06 sek.

"SELECT * FROM scoretable WHERE game=1 AND time>? ORDER BY rank DESC LIMIT 100000, 75;" i działa w 0,58 sek.

Wygląda na to, że szybko zacznie to potrwać zbyt długo, ponieważ każdego dnia będzie przesyłanych kilka tysięcy nowych wyników!

Dodatkowo istnieją dwa inne rodzaje zapytań, które są używane do znalezienia konkretnego gracza według identyfikatora na liście uporządkowanej według rankingu. one wyglądać tak:

"SELECT * FROM scoretable WHERE game=1 AND time>? AND playerId=? ORDER BY rank DESC LIMIT 1"

następnie

"SELECT count(id) as count FROM scoretable WHERE game=1 AND time>? AND rank>[rank returned from above]"

Moje pytanie brzmi: co można zrobić, aby uczynić to skalowalny system? Widzę, że liczba rzędów, które wkrótce rosną, wynosi kilka milionów. Miałem nadzieję, że wybór inteligentnych indeksów pomógłby, ale poprawa była tylko marginalna.

Aktualizacja: Oto wyjaśnienie wiersz:

 
mysql> explain SELECT * FROM scoretable WHERE game=1 AND time>0 ORDER BY rank DESC LIMIT 100000, 75; 
+----+-------------+-----------+-------+---------------+------+---------+------+--------+-------------+ 
| id | select_type | table  | type | possible_keys | key | key_len | ref | rows | Extra  | 
+----+-------------+-----------+-------+---------------+------+---------+------+--------+-------------+ 
| 1 | SIMPLE  | scoretable| range | game   | game | 5  | NULL | 138478 | Using where | 
+----+-------------+-----------+-------+---------------+------+---------+------+--------+-------------+ 

Rozwiązanie znalezione!

Rozwiązałem problem dzięki niektórym wskazówkom z tego wątku. Sporządzenie indeksu klastrowego było dokładnie tym, czego potrzebowałem, więc skonwertowałem tabelę tak, aby używała InnoDB w mysql, który obsługuje indeksy klastrowe. Następnie usunąłem pole id i ustawiłem tylko klucz podstawowy (ASC gry, pozycja DESC). Teraz wszystkie zapytania działają bardzo szybko, bez względu na to, z jakiego offsetu korzystam. Wyjaśnienie pokazuje, że nie jest wykonywane żadne dodatkowe sortowanie i wygląda na to, że łatwo obsługuje cały ruch.

+3

Wykorzystanie Mongo DB. Jest to skala sieciowa. – anon

+7

To dziwne, że nie jest możliwe przypisywanie komentarzy ("Użyj Mongo DB, to jest skala sieciowa.") – zerkms

+1

@ user509841: podaj wyjaśnienia. – zerkms

Odpowiedz

4

Widząc, jak nie ma chętnych, dam mu szansę. Pochodzę z tła SQL Server, ale te same idee mają zastosowanie.

Niektóre ogólne obserwacje:

  • Kolumna ID jest dość dużo sensu i nie powinien uczestniczyć w żadnych indeksów, chyba że istnieją inne tabele/kwerendy nie mówisz nam o. W rzeczywistości nie musi to być Twoje ostatnie zapytanie. Możesz zrobić COUNT (*).
  • Indeks klastrowany powinien być kierowany na najczęstsze zapytania. Dlatego indeks klastrowy w grze ASC, czasie DESC i rank DESC działa dobrze. Sortowanie według czasu DESC jest zwykle dobrym pomysłem na historyczne tabele, w których zazwyczaj interesują Cię najnowsze rzeczy. Możesz także wypróbować oddzielny indeks z pozycją ułożoną w innym kierunku, choć nie jestem pewien, jaką korzyść przyniesie to.
  • Czy na pewno potrzebujesz SELECT *? Jeśli możesz wybrać mniej kolumn, możesz utworzyć indeks zawierający wszystkie kolumny potrzebne do WYBORU i GDZIE.

1 milion wierszy to naprawdę nie tyle. Stworzyłem tabelę podobną do twojej z 1 000 000 wierszy przykładowych danych, a nawet z jednym indeksem (ASC gry, time DESC i ranking DESC) wszystkie zapytania trwały mniej niż 1 sekundę.

(Tylko część nie jestem pewny jest playerid. Zapytania wykonywane tak dobrze, że playerid nie wydaje się konieczne. Być może można go dodać na końcu indeksu klastrowego.)

+0

Dzięki! Jak utworzyć indeks klastrowy, jak mówisz? –

+0

Rozumiem, że to wymyśliłeś =) Widzę, że zostawiłeś czas i playerId z indeksu w klastrze. Prawdopodobnie będą przydatne pewnego dnia, chociaż teraz, gdy o tym myślę, wydłużanie czasu może być lepsze, ponieważ inserty pojawią się na końcu indeksów. Nie jestem tego pewien. Możesz też przełączyć się na Mongo DB, ponieważ jest to skala sieciowa =) – anon