Tytu­łem wstępu: Who­osh to bar­dzo przy­jemny sil­nik indek­su­jący i wyszu­ku­jący doku­menty w try­bie full-text. Nie­stety, masa ludzi ma pro­blemy 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 obja­wami faktu, że wię­cej niż jeden pro­ces pró­buje uży­wać tego samego indeksu w tym samym cza­sie. Główną przy­czyną jest nie­świa­do­mość tego, że Who­osh nie gwa­ran­tuje bez­pie­czeń­stwa wąt­ków.

Rozwiązanie pierwsze: gdy używasz Haystack

Ponie­waż zde­cy­do­wana więk­szość użyt­kow­ni­ków nie odwo­łuje się bez­po­śred­nio do API Who­osh, a korzy­sta z niego za pośred­nic­twem modułu Hay­stack, naj­pierw przed­sta­wię roz­wią­za­nie dla nich.

Jak usta­li­li­śmy przed­wczo­raj z auto­rem Hay­stack, przy­kła­dowy bac­kend dostar­czany dla Who­osh błęd­nie zakłada bez­pie­czeń­stwo wąt­ków tego ostatniego.

Dodat­kowo, Hay­stack używa leni­wych obiek­tów (obiek­tów z odwle­czoną ini­cja­li­za­cją) do łado­wa­nia indek­sów, co powo­duje, że nawet przy zacho­wa­niu bez­pie­czeń­stwa wąt­ków, apli­ka­cja nie będzie dzia­łać pra­wi­dłowo w środo­wi­sku typu pre­fork.

Naj­praw­do­po­dob­niej w przy­szło­ści Hay­stack będzie uru­cha­miał Who­osh w postaci usługi sys­te­mo­wej, co pozwoli na wie­lo­do­stęp do tego samego indeksu i tym samym zli­kwi­duje pro­blemy zwią­zane z roz­ga­łę­zia­niem pro­ce­sów i wąt­ko­wa­niem. Póki co, roz­wią­za­nie składa się z dwóch kroków:

  1. Nało­że­nie na Hay­stack mojej łatki, która zapew­nia bez­pie­czeń­stwo wąt­ków: haystack-whoosh.patch

  2. Upew­nie­nie się, że Django działa w ser­we­rze wąt­ko­wa­nym, a nie for­ko­wa­nym. Dla uży­wa­ją­cych manage.py spro­wa­dza się to do dopi­sa­nia para­me­tru method:
    python manage.py runfcgi method=threaded ...

Rozwiązanie drugie: gdy bezpośrednio wołasz Whoosh

Tutaj goto­wej łatki podać nie mogę, bo każdy pisze kod po swo­jemu. Wystar­czy jed­nak — podob­nie jak zro­bi­łem to w łatce dla Hay­stack — użyć mecha­ni­zmu threading.Lock i ogro­dzić wszyst­kie wywo­ła­nia API wspólną blo­kadą (czy może ryglem, jak cza­sem jest to słowo tłu­ma­czone). 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ów­nież upew­nić, że w danej chwili ist­nieje tylko jeden obiekt typu whoosh.index.Index na każdy kata­log indeksu, a także mak­sy­mal­nie jedna instan­cja whoosh.writing.IndexWriter. Pierw­szy przy­pa­dek roz­wią­zuje imple­men­ta­cja sin­gle­tona, drugi — ogro­dze­nie całego cyklu życia obiektu (aż do pomyśl­nego wywo­ła­nia metody commit lub cancel) wewnątrz tej samej blo­kady, co reszta wywo­łań API.