Mingle (WordPress) – szybsza poczta

Przyszła pora na wprowadzenie kilku zmian we wtyczce Mingle (wersja 0.0.32), a dokładniej w poczcie. Jeżeli masz stronę postawioną na WordPressie, do tego społeczność jest gromadzona poprzez wtyczkę Mingle, to istnieje duże prawdopodobieństwo, że ludzie skarżą się na szybkość działania poczty. Im więcej wiadomości w bazie (wp_mngl_messages), tym większe problemy ze stabilnością serwisu. Jeżeli jest tak w Twoim przypadku, to wiedz że jest na to rada (dwie, jeśli liczyć napisanie całości od zera tak jak ma być).

Problematyczna część siedzi w wp-content/plugins/mingle/classes/models/MnglMessage.php i nazywa się:

function get_latest_messages($pagenum=1,$pagesize=5)

Funkcja ta odpowiada za pobranie wiadomości pogrupowanych w osobne wątki z podanego przedziału (domyślnie 10 najnowszych). Zapytanie pobierające dane wygląda następująco:

$query = "SELECT mpm.*, UNIX_TIMESTAMP(mpm.created_at) as created_at_ts
 FROM {$this->table_name} mpm
 WHERE mpm.id=( SELECT mpm2.id
 FROM {$this->table_name} mpm2
 WHERE mpm2.thread_id=mpm.thread_id
 AND mpm2.recipient_id=%d
 ORDER BY mpm2.created_at DESC
 LIMIT 1 )
 ORDER BY mpm.created_at DESC
 {$limit}";

Proszę zwrócić uwagę na FROM. Zgadza się, dane są pobierane przy użyciu wewnętrznego zapytania działającego na tej samej tabeli. Wygląda strasznie, strona z tym zapytaniem działa wolno, zobaczmy co powie na to EXPLAIN:

id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY mpm index NULL created_at 8 NULL 10 Using where
2 DEPENDENT SUBQUERY mpm2 index recipient_id, thread_id created_at 8 NULL 3 Using where

Teoretycznie nie jest tak źle. Jednakże trzeba wziąć pod uwagę to, że EXPLAIN podaje szacunkowe wyliczenia, a skoro strona muli, to trzeba sięgnąć po logi mysql.

# Query_time: 2.760502  Lock_time: 0.000264 Rows_sent: 8  Rows_examined: 99448

(serwer  nie ma łatek Percony, więc podaje tylko podstawowe informacje). Jak widać mysql musiało sprawdzić prawie 100 tysięcy rekordów, żeby odesłać 8 (użytkownik zapisany w logach ma tylko tyle wiadomości w swojej skrzynce). Jeszcze jedna rzecz, która nie jest zbyt ciekawa, to to, że w bazie siedzi obecnie niecałe 1,5k wiadomości.

1. Id wątków, 2. wiadomości do wątków

Skoro jedno zapytanie sprawia tyle problemów z bazą, to być może nie warto rozważyć opcję wyciągnięcia danych przy pomocy dwóch osobnych zapytań. Najpierw zapytanie pobierające wątki, potem zapytanie pobierające wiadomości do tych wątków:

$query = "SELECT DISTINCT (
thread_id
)
FROM {$this->table_name}
WHERE recipient_id = %d
ORDER BY created_at DESC
{$limit}" ;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE wp_mngl_messages ref recipient_id, thread_id recipient_id 4 const 44 Using where; Using temporary; Using filesort

Z poziomu php wyciągamy id wątków i wrzucamy do kolejnego zapytania, które będzie robiło za to właściwe (odpowiedź z tego zapytania kierujemy na zewnątrz tak jak w oryginalnym Mingle):


$query = "SELECT *
FROM {$this->table_name}
WHERE thread_id
IN ( {$threads} )
AND recipient_id = %d
GROUP BY thread_id
ORDER BY created_at DESC" ;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE wp_mngl_messages ref recipient_id, thread_id recipient_id 4 const 44 Using where; Using temporary; Using filesort

Po tych zmianach strona z pocztą zaczęła ładować się w rozsądnym czasie, a debugger wbudowany w WordPressa pokazał, że ogółem na zapytania związane z wiadomościami mysql potrzebował 0.3 sekundy.

1. Id najnowszych wiadomości, 2. wiadomości ze wskazanymi id

Jest jeszcze drugie rozwiązanie. Zamiast brać identyfikatory wątków, to można wyciągnąć id wiadomości z wątków:

$query = "SELECT max( id ) as ID
FROM {$this->table_name}
WHERE recipient_id = %d
GROUP BY thread_id
ORDER BY created_at DESC" ;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE wp_mngl_messages ref recipient_id recipient_id 4 const 1013 Using where; Using temporary; Using filesort

Gdy już mamy id wiadomości, to pobieramy je do pokazania:

$query = "SELECT m.*, UNIX_TIMESTAMP(m.created_at) as created_at_ts
FROM {$this->table_name} m
WHERE id in ( {$id_wiadomosci} ) order by id desc" ;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE m range PRIMARY PRIMARY 4 NULL 10 Using where

Uwagi końcowe

Nie są to jedyne rozwiązania, ale one dwa wpadły mi do głowy, gdy na szybko musiałem przywrócić szybkość działania strony. Można jeszcze pobawić się zapytaniami mającymi podobną konstrukcję co oryginalne zapytanie. Trzeba jednak uważać, bo można niechcący zablokować działanie mysqla (100% mocy w to jedno zapytanie).

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

*