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.py
sprowadza 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.