До сих пор общий доступ к ресурсам обеспечивался блокировками чтения-записи (RW-блокировками). При высокой нагрузке блокировки могли создавать проблемы, когда требовалось изменить индексы. Чтобы от них избавиться, нам пришлось переосмыслить связь между потоками и индексами.
Индексы могут быть большими или даже огромными, и они общие для всех рабочих процессов. На многоядерном процессоре можно одновременно запускать множество запросов, и они распределятся по ядрам, используя один и тот же индекс. Это просто и понятно. Однако иногда требуется обновить индексы. Бесшовная ротация индексов неплохо работала со старыми рабочими процессами в режиме fork: мы просто загружали новые файлы индекса, оставляя работающие форки обслуживать старые. И в один момент новые запросы переходят к форкам, которые уже используют новый загруженный индекс. Таким образом, нет задержек в обслуживании: вы просто бесшовно переходите со старого индекса на новый. Рабочие процессы, использующие старую версию, завершатся (или упадут), и в итоге предыдущий индекс будет освобождён.
В случае рабочих потоков использовался простой механизм RW-блокировок (разделяемых и эксклюзивных блокировок): множество потоков с разделяемым доступом (читателей) могут одновременно обращаться к ресурсу, но изменять его может только один поток с эксклюзивным доступом (писатель). Поэтому индексы всегда были либо «разделяемыми» между запросами, либо «заблокированными» ротацией. Они загружались в начале и освобождались в конце. При ротации один поток загружал новый индекс, но ему требовался эксклюзивный доступ к дескриптору активного индекса. Однако потоки, обрабатывающие запросы, имеют разделяемый доступ, и ничто не мешает им брать новые задачи. Это означает, что при высокой нагрузке выполнить «бесшовную» ротацию просто невозможно из-за её последнего шага — атомарной замены старого индекса новым.
В новой модели, появившейся в версии 2.7, индексы живут между потоками независимо, как в старом случае с 'fork', поскольку они больше не являются ни «разделяемыми», ни «заблокированными», а просто неизменяемы. Это упрощает работу: рабочий поток не заботится о «блокировке» или «разделении» индекса, он просто его использует. Когда нужно выполнить ротацию (загрузить новый индекс), демон делает это, не обращая внимания на выполняющиеся запросы (а их потоки, в свою очередь, не обращают внимания на «писателя»). В итоге ротация просто переключает указатель активного индекса на новый (загруженный), и дальше всё работает как с форками: старые запросы продолжают использовать предыдущий индекс, новые направляются на только что загруженный. Эксклюзивная блокировка по-прежнему используется только при выполнении UPDATE.
Ещё одно преимущество новой модели в том, что тип индекса можно менять «на лету», перезагрузив конфигурацию. Раньше изменить тип индекса, просто поправив конфигурационный файл и перезагрузив его (по сигналу HUP), удавалось не всегда — например, при переключении индекса 'plain' на 'distributed ' или 'distributed' на 'template'. Теперь, когда вы перезагружаете новую конфигурацию, в которой от старого индекса осталось только имя, демон сначала разбирает её. Затем, если новые индексы можно использовать сразу (как в случае шаблонных и распределённых индексов), он бесшовно подменяет ими старые. Если же новые индексы «тяжёлые» (то есть их нужно предварительно загрузить в ОЗУ, и это может занять несколько минут), ротация откладывается до их загрузки. «Откладывается» означает, что вызывающая сторона получает ответ 'ok', '(all rotated)'. Но рабочие потоки продолжают направлять все новые запросы к старым индексам, пока новый индекс окончательно не загрузится и не станет активным.
