Filtrowanie firm według jednej kategorii i jednego miasta jest proste. Problem zaczyna się wtedy, gdy lista ma uwzględniać jednocześnie kilka branż, kilkanaście lokalizacji, promień dojazdu, status działalności oraz kompletność danych kontaktowych. Źle zbudowany filtr potrafi przeszukiwać setki tysięcy rekordów przy każdym kliknięciu, zwracać te same firmy wielokrotnie albo pomijać przedsiębiorstwa działające poza adresem rejestrowym.
Największe przyspieszenie zwykle nie wynika z zakupu mocniejszego serwera. Najpierw trzeba uporządkować model danych, ograniczyć liczbę rekordów analizowanych przez zapytanie i przestać traktować kategorię oraz lokalizację jak zwykłe pola tekstowe. Dopiero później ma sens strojenie bazy, cache i interfejsu.
Rozdziel kategorie, lokalizacje i dane firmy
Częsty błąd polega na zapisaniu wszystkich kategorii w jednej kolumnie, na przykład:
hydraulik, instalacje grzewcze, pompy ciepła
Takie dane trudno filtrować. Wyszukiwanie przez LIKE '%hydraulik%' może wyglądać niewinnie przy 5 tys. firm, ale przy 500 tys. rekordów staje się kosztowne, szczególnie gdy użytkownik zaznacza kilka kategorii jednocześnie. Dochodzi też problem niejednoznacznych nazw: „instalacje”, „instalator”, „instalacje wodne” i „usługi instalacyjne” mogą oznaczać podobne rzeczy, lecz system traktuje je jako osobne wartości.
Lepszy model składa się z co najmniej czterech elementów:
- tabeli firm,
- słownika kategorii,
- tabeli łączącej firmy z kategoriami,
- tabeli lokalizacji lub obszarów obsługi.
Jedna firma może wtedy należeć do wielu kategorii bez kopiowania całego rekordu. Przedsiębiorstwo zajmujące się fotowoltaiką, magazynami energii i pompami ciepła otrzymuje trzy relacje do słownika kategorii, a nie jeden długi ciąg tekstowy.
Kategorie powinny mieć stałe identyfikatory, niezależne od nazwy wyświetlanej użytkownikowi. Pozwala to zmienić etykietę „Serwis klimatyzacji” na „Montaż i serwis klimatyzacji” bez przebudowy relacji. Przy bardziej rozbudowanym katalogu przydaje się również hierarchia:
- Budownictwo
- Instalacje
- Hydraulika
- Ogrzewanie
- Klimatyzacja
- Instalacje
Hierarchia skraca listę filtrów, ale wymaga jasnej decyzji: czy zaznaczenie kategorii nadrzędnej ma obejmować wszystkie podkategorie. Najbezpieczniej ustalić to w logice systemu, zamiast za każdym razem rozwijać drzewo kategorii w aplikacji.
Podobnie trzeba potraktować lokalizacje. Pole tekstowe „Warszawa, Mokotów” nie wystarczy, jeśli system ma rozpoznawać województwo, powiat, gminę, dzielnicę i promień od wskazanego punktu. Minimalny zestaw danych lokalizacyjnych powinien obejmować:
- miejscowość,
- kod pocztowy,
- gminę i powiat,
- województwo,
- współrzędne geograficzne,
- osobno zapisany obszar obsługi.
Ostatni element ma duże znaczenie. Firma z siedzibą w Piasecznie może obsługiwać całą Warszawę, natomiast zakład z adresem w centrum stolicy może przyjmować klientów wyłącznie stacjonarnie. Adres siedziby nie jest tym samym co zasięg działalności.
W praktyce najlepiej rozdzielić trzy modele obsługi:
- usługa realizowana pod adresem firmy,
- dojazd do klienta w określonym promieniu,
- obsługa wskazanych miejscowości lub regionów.
To ogranicza fałszywe wyniki. Sam filtr odległości nie rozwiązuje problemu, ponieważ promień 30 km wokół firmy nie zawsze odpowiada faktycznemu obszarowi dojazdu. Granice administracyjne, korki i układ dróg bywają ważniejsze niż odległość w linii prostej.
Ogranicz liczbę rekordów, zanim zaczniesz je sortować
Szybkie filtrowanie zaczyna się od właściwej kolejności operacji. Najpierw należy zastosować warunki, które najmocniej zmniejszają zbiór danych, a dopiero później wykonywać kosztowne obliczenia, sortowanie i pobieranie dodatkowych informacji.
Jeżeli użytkownik szuka elektryków i instalatorów alarmów w trzech powiatach, system nie powinien najpierw pobierać całej bazy firm, obliczać odległości i dopiero na końcu sprawdzać kategorie. Powinien rozpocząć od rekordów powiązanych z wybranymi kategoriami i obszarami.
Najważniejsze są indeksy bazodanowe. Powinny obejmować przede wszystkim:
- identyfikator kategorii w tabeli powiązań,
- identyfikator firmy,
- miejscowość, powiat i województwo,
- status publikacji wpisu,
- datę aktualizacji,
- pola używane do sortowania.
Nie należy jednak indeksować wszystkiego. Każdy dodatkowy indeks zajmuje miejsce i spowalnia dodawanie oraz aktualizowanie danych. Przy katalogu, w którym rekordy zmieniają się rzadko, koszt ten jest umiarkowany. W systemie aktualizowanym tysiące razy na godzinę nadmiar indeksów szybko staje się problemem.
Indeksy wielokolumnowe trzeba układać zgodnie z realnymi zapytaniami. Jeśli większość wyszukiwań obejmuje status aktywnego wpisu, kategorię i województwo, indeks powinien odzwierciedlać właśnie ten schemat. Indeks zbudowany dla pól, których użytkownicy prawie nie łączą, będzie zajmował miejsce bez wyraźnego efektu.
Kolejny krok to analiza planów zapytań. W PostgreSQL służy do tego między innymi EXPLAIN ANALYZE, a w MySQL polecenie EXPLAIN. Trzeba sprawdzić, czy baza korzysta z indeksów, ile wierszy odczytuje i gdzie wykonuje pełne skanowanie tabeli. Sam fakt utworzenia indeksu nie oznacza jeszcze, że silnik go wykorzysta.
Praktyczny cel wydajnościowy dla typowego katalogu internetowego wygląda następująco:
- do 200–300 ms na odpowiedź samego API,
- do 1 sekundy na pojawienie się wyników w interfejsie,
- maksymalnie 50–100 wyników pobieranych w jednej paczce,
- brak pełnego przeliczania liczników po każdym kliknięciu użytkownika.
Czasy powyżej 2–3 sekund są już odczuwalne. Użytkownik zaczyna ponawiać kliknięcia, zmienia filtry albo opuszcza stronę, zakładając, że wyszukiwarka nie działa.
Dużym obciążeniem bywają liczniki przy filtrach, na przykład „Hydraulicy — 1842” albo „Mazowieckie — 9360”. Jeżeli każdy licznik jest osobnym zapytaniem, jedno otwarcie panelu może uruchamiać kilkanaście lub kilkadziesiąt operacji. Rozsądniejsze jest:
- obliczanie liczników jednym zapytaniem agregującym,
- przechowywanie ich przez kilka minut w cache,
- aktualizowanie ich okresowo,
- rezygnacja z dokładnego licznika tam, gdzie nie wpływa on na decyzję użytkownika.
Cache daje duży efekt tylko przy powtarzalnych zapytaniach. Można przechowywać wyniki popularnych kombinacji, takich jak „restauracje w Krakowie” czy „mechanicy samochodowi w Poznaniu”. Nie opłaca się natomiast zapisywać każdej kombinacji kilkunastu filtrów. Liczba wariantów rośnie wtedy zbyt szybko, a większość wpisów w cache nigdy nie zostanie ponownie wykorzystana.
Uprość wybór wielu filtrów i pilnuj jakości danych
Wydajna baza nie pomoże, gdy użytkownik musi przeklikiwać długą listę kategorii i nie rozumie, czy wybrane warunki działają jako „oraz”, czy „lub”. Interfejs powinien jasno pokazywać logikę wyszukiwania.
Dla kategorii najczęściej sprawdza się operator OR: firma może być hydraulikiem lub instalatorem ogrzewania. Operator AND ma sens wtedy, gdy użytkownik świadomie szuka przedsiębiorstwa posiadającego wszystkie wskazane kompetencje. Warto udostępnić oba warianty, ale nie ukrywać ich pod niejasnym przełącznikiem.
Przykład:
- „Hydraulika LUB pompy ciepła” pokaże firmy należące do co najmniej jednej kategorii.
- „Hydraulika ORAZ pompy ciepła” pokaże wyłącznie firmy przypisane do obu kategorii.
Drugi wariant daje znacznie mniej wyników. Jeśli katalog ma niepełne przypisania kategorii, może wręcz zwrócić pustą listę, mimo że odpowiednie firmy istnieją. To typowa niedogodność: precyzyjniejszy filtr ujawnia braki w danych, zamiast automatycznie poprawiać jakość wyników.
W przypadku wielu lokalizacji użytkownik powinien móc wybierać miejscowości, powiaty i województwa bez wpisywania każdej nazwy od początku. Dobrze działa pole z autouzupełnianiem, które pokazuje również typ jednostki:
- Warszawa — miasto,
- Warszawa Zachodnia — powiat,
- mazowieckie — województwo.
Bez takiego oznaczenia łatwo pomylić miejscowości o podobnych nazwach. W Polsce powtarzające się nazwy wsi i gmin nie są wyjątkiem. Samo pole „Nowa Wieś” nie pozwala jednoznacznie ustalić lokalizacji.
Przy wyborze promienia trzeba podać punkt odniesienia i jednostkę. Użytkownik powinien wiedzieć, czy system liczy odległość od centrum miejscowości, kodu pocztowego, konkretnego adresu czy bieżącej pozycji. Promień 20 km od geometrycznego środka Warszawy da inne wyniki niż 20 km od adresu przy ulicy Puławskiej.
Wyszukiwanie przestrzenne najlepiej oprzeć na mechanizmach przeznaczonych do danych geograficznych, takich jak PostGIS dla PostgreSQL. Obliczanie odległości prostym wzorem dla wszystkich rekordów przy każdym zapytaniu jest łatwe do wdrożenia, ale źle skaluje się wraz z rozrostem katalogu. Najpierw warto ograniczyć kandydatów za pomocą indeksu przestrzennego i prostokąta obejmującego obszar, a dopiero później policzyć dokładną odległość.
Największym ograniczeniem pozostaje jednak jakość wpisów. W praktyce problemy powodują:
- nieaktualne numery telefonów,
- firmy oznaczone jako aktywne mimo zawieszenia działalności,
- błędne współrzędne,
- kategorie dodane wyłącznie w celach promocyjnych,
- kilka wpisów tej samej firmy,
- adres korespondencyjny przedstawiany jako punkt obsługi klientów.
Przed optymalizacją zapytań warto zmierzyć skalę tych błędów. Szybka wyszukiwarka zwracająca duplikaty i nieaktualne wizytówki nadal jest słabą wyszukiwarką.
Rekordy można oceniać za pomocą wskaźnika kompletności. Przykładowy model punktowy:
- poprawny numer telefonu: 20 punktów,
- działająca strona internetowa: 15 punktów,
- potwierdzony adres: 20 punktów,
- współrzędne geograficzne: 15 punktów,
- przypisana kategoria szczegółowa: 15 punktów,
- aktualizacja w ciągu ostatnich 12 miesięcy: 15 punktów.
Firmy z wynikiem poniżej ustalonego progu można oznaczyć do weryfikacji albo przesunąć niżej, ale nie należy automatycznie usuwać ich z rezultatów. Mała działalność usługowa może nie mieć strony internetowej, a mimo to regularnie odbierać telefon i realizować zlecenia. Brak jednego pola nie zawsze oznacza nieaktualny wpis.
Duplikaty najlepiej wykrywać na podstawie kilku cech jednocześnie: NIP-u, numeru telefonu, domeny internetowej, adresu i podobieństwa nazwy. Porównywanie wyłącznie nazw daje dużo błędów. „ABC Serwis” może oznaczać kilka niezależnych działalności, a jedna firma może występować jako „ABC Serwis”, „ABC-Serwis” i „ABC Serwis Sp. z o.o.”.
Przy porządkowaniu źródeł danych pomocne jest również porównanie sposobu prezentowania nazwy, adresu i telefonu w zewnętrznych bazach. Dodatkowe informacje na: katalog NAP – https://aiqo.pl.
FAQ
Czy wiele kategorii lepiej łączyć operatorem AND czy OR?
Domyślnie lepiej używać OR, ponieważ użytkownik zwykle szuka firmy należącej do dowolnej z wybranych kategorii. AND warto włączyć jako opcję dla osób poszukujących przedsiębiorstwa spełniającego wszystkie kryteria jednocześnie.
Ile lokalizacji można filtrować naraz?
Nie ma jednej uniwersalnej granicy. Przy prawidłowym modelu danych kilkanaście lub kilkadziesiąt lokalizacji nie powinno stanowić problemu. Setki miejscowości przesyłanych w jednym zapytaniu wymagają już innego rozwiązania, na przykład zapisanych regionów, grup lokalizacji albo tymczasowej tabeli identyfikatorów.
Czy Elasticsearch lub OpenSearch przyspieszy filtrowanie?
Tak, szczególnie przy wyszukiwaniu pełnotekstowym, literówkach, podpowiedziach i rozbudowanych agregacjach. Nie jest jednak konieczny dla każdego katalogu. Przy kilkudziesięciu lub kilkuset tysiącach dobrze ułożonych rekordów PostgreSQL może zapewnić wystarczającą szybkość i prostszą administrację.
Kiedy stosować cache?
Po zoptymalizowaniu modelu danych i zapytań. Cache nie powinien maskować pełnego skanowania tabel ani źle dobranych indeksów. Najlepiej przechowywać popularne wyniki i liczniki filtrów przez kilka minut, a nie każdą możliwą kombinację parametrów.
Czy adres firmy wystarcza do filtrowania po lokalizacji?
Nie. Potrzebne są również współrzędne oraz informacja o obszarze obsługi. Firma może dojeżdżać do klientów w kilku powiatach albo świadczyć usługę wyłącznie w swojej siedzibie.
Jak sprawdzić, co naprawdę spowalnia wyszukiwarkę?
Trzeba zmierzyć osobno czas zapytania do bazy, czas działania API i czas renderowania interfejsu. Następnie należy przeanalizować plany najwolniejszych zapytań. Optymalizowanie „na wyczucie” często prowadzi do poprawiania elementu, który odpowiada za niewielką część opóźnienia.
Pierwsza decyzja powinna dotyczyć struktury kategorii i lokalizacji. Sprawdź najwolniejsze zapytanie oraz liczbę rekordów, które baza musi odczytać, aby zwrócić jedną stronę wyników. Jeżeli kategorie są zapisane jako tekst, lokalizacja ogranicza się do nazwy miasta albo jedno zapytanie skanuje większość tabeli, usuń ten błąd przed wdrożeniem cache, nowej wyszukiwarki czy mocniejszego serwera.