Tytułem wstępu: Whoosh to bardzo przyjemny silnik indeksujący i wyszukujący dokumenty w trybie full-text
. Niestety, masa ludzi ma problemy z jego wdrożeniem.
Objawy
Główne objawy to:
OSError: [Errno 17] File exists: '/path/to/index/cache/_MAIN_LOCK'
oraz:
IOError: [Errno 2] No such file or directory: '/path/to/index/cache/_MAIN_123.tiz'
Oba z nich są na ogół różnymi objawami faktu, że więcej niż jeden proces próbuje używać tego samego indeksu w tym samym czasie. Główną przyczyną jest nieświadomość tego, że Whoosh nie gwarantuje bezpieczeństwa wątków.
Rozwiązanie pierwsze: gdy używasz Haystack
Ponieważ zdecydowana większość użytkowników nie odwołuje się bezpośrednio do API Whoosh, a korzysta z niego za pośrednictwem modułu Haystack, najpierw przedstawię rozwiązanie dla nich.
Jak ustaliliśmy przedwczoraj z autorem Haystack, przykładowy backend dostarczany dla Whoosh błędnie zakłada bezpieczeństwo wątków tego ostatniego.
Dodatkowo, Haystack używa leniwych obiektów
(obiektów z odwleczoną inicjalizacją) do ładowania indeksów, co powoduje, że nawet przy zachowaniu bezpieczeństwa wątków, aplikacja nie będzie działać prawidłowo w środowisku typu prefork
.
Najprawdopodobniej w przyszłości Haystack będzie uruchamiał Whoosh w postaci usługi systemowej, co pozwoli na wielodostęp do tego samego indeksu i tym samym zlikwiduje problemy związane z rozgałęzianiem procesów i wątkowaniem. Póki co, rozwiązanie składa się z dwóch kroków:
-
Nałożenie na Haystack mojej łatki, która zapewnia bezpieczeństwo wątków: haystack-whoosh.patch
- Upewnienie się, że Django działa w serwerze wątkowanym, a nie forkowanym. Dla używających
manage.pysprowadza się to do dopisania parametrumethod:
python manage.py runfcgi method=threaded ...
Rozwiązanie drugie: gdy bezpośrednio wołasz Whoosh
Tutaj gotowej łatki podać nie mogę, bo każdy pisze kod po swojemu. Wystarczy jednak — podobnie jak zrobiłem to w łatce dla Haystack — użyć mechanizmu threading.Lock i ogrodzić wszystkie wywołania API wspólną blokadą (czy może ryglem, jak czasem jest to słowo tłumaczone). Na przykład:
import threading whoosh_api = threading.Lock() def do_something(): whoosh_api.acquire() try: # use API finally: whoosh_api.release()
Należy się również upewnić, że w danej chwili istnieje tylko jeden obiekt typu whoosh.index.Index na każdy katalog indeksu, a także maksymalnie jedna instancja whoosh.writing.IndexWriter. Pierwszy przypadek rozwiązuje implementacja singletona, drugi — ogrodzenie całego cyklu życia obiektu (aż do pomyślnego wywołania metody commit lub cancel) wewnątrz tej samej blokady, co reszta wywołań API.


by paluh
08 maj 2009 at 17:01
mam wrażenie, że w patchu brakuje prefixów: ‘@@’ w liniach określających zakresy patch’owania.
Dziki za wpis — bardzo sie przydal!
by Patrys
08 maj 2009 at 17:11
paluh:
Dzięki, kopiowałem go z serwera przez pastebin i ten je zjadł. Poprawione.
by Ryszard Szopa
08 maj 2009 at 22:52
Dzięki wielkie! BTW odważyłeś się kiedykolwiek puścić Haytacka z Whooshem na produkcję? O ile Haystack wydaje się fajnie zaprojektowany, o tyle Whoosh wydaje się jakiś taki… nie do końca godny zaufania. Obawiam się, że podobnych problemów wyjdzie na jaw jeszcze sporo. A szkoda, bo idea wyszukiwarki pełnotekstowej w Pythonie jest kusząca.
(PS. Jakbyś stworzył na Githubie forka Haytacka ze swoim patchem, świat byłby o jedno repozytorium lepszy ;-)
by Patrys
10 maj 2009 at 13:50
Ryszard Szopa:
http://github.com/patrys/django-haystack/tree/master