Соединения с базой данных Haskell

Пожалуйста, посмотрите на это скотти-приложение (оно взято из старого ответа 2014 года):

import Web.Scotty
import Database.MongoDB
import qualified Data.Text.Lazy as T
import Control.Monad.IO.Class

runQuery :: Pipe -> Query -> IO [Document]
runQuery pipe query = access pipe master "nutrition" (find query >>= rest) 

main = do
  pipe <- connect $ host "127.0.0.1"
  scotty 3000 $ do
    get "/" $ do
      res <- liftIO $ runQuery pipe (select [] "stock_foods")
      text $ T.pack $ show res

Вы видите, как соединение с базой данных (pipe) создается только один раз при запуске веб-приложения. Впоследствии тысячи, если не миллионы посетителей будут одновременно заходить на маршрут "/" и читать из базы данных, используя одно и то же соединение (pipe).

У меня есть вопросы о том, как правильно использовать Database.MongoDB:

  1. Это правильный способ настройки? В отличие от создания подключения к базе данных для каждого посещения "/". В этом последнем случае мы могли бы иметь миллионы соединений одновременно. Это не рекомендуется? Каковы преимущества и недостатки такого подхода?
  2. В приложении выше, что произойдет, если соединение с базой данных потеряно по какой-то причине, и его необходимо создать заново? Как бы вы оправились от этого?
  3. Как насчет аутентификации с auth функция? Если auth Функция вызывается только один раз после создания pipeили он должен вызываться при каждом попадании в "/"?
  4. Некоторые говорят, что я должен использовать бассейн (Data.Pool). Похоже, это поможет ограничить количество посетителей, одновременно использующих одно и то же соединение с базой данных. Но зачем мне это делать? Разве у подключения MongoDB нет встроенной поддержки одновременного использования?

3 ответа

Решение
  1. Даже если вы создадите соединение для каждого клиента, вы не сможете создать слишком много из них. Вы ударите Ulimit. Как только вы нажмете этот ulimit, клиент, который ударит по этому ulimit, получит ошибку во время выполнения. Причина, по которой это не имеет смысла, состоит в том, что сервер mongodb будет тратить слишком много времени на опрос всех этих соединений, и у него будет только столько значимых работников, сколько столько процессоров у вашего db-сервера. Одно соединение - неплохая идея, потому что mongodb предназначен для отправки нескольких запросов и ожидания ответов. Таким образом, он будет использовать столько ресурсов, сколько может иметь ваш mongodb только с одним ограничением - у вас есть только один канал для записи, и если он случайно закроется, вам нужно будет заново создать этот канал. Таким образом, имеет больше смысла иметь пул соединений. Это не должно быть большим. У меня было приложение, которое аутентифицирует пользователей и дает им токены. При 2500 одновременных пользователях в секунду было только 3-4 одновременных подключения к базе данных.

Вот что дает вам пул соединений:

  • Если вы достигнете предела подключения к пулу, вы будете ждать следующего доступного подключения и не получите ошибку времени выполнения. Итак, ваше приложение немного подождет, а не отклонит ваш клиент.

  • Бассейн будет воссоздавать связи для вас. Вы можете настроить пул для закрытия избыточного количества соединений и создания большего количества до определенного предела по мере необходимости. Если ваше соединение разрывается во время чтения или записи в него, вы просто берете другое соединение из пула. Если вы не вернете это разорванное соединение в пул, вы создадите другое соединение для вас.

    1. Если соединение с базой данных закрыто, то: слушатель mongodb на этом соединении прекратит печатать сообщение об ошибке на вашем терминале, ваше приложение получит ошибку ввода-вывода. Чтобы устранить эту ошибку, вам нужно создать еще одно соединение и повторить попытку. Когда дело доходит до обработки этой ситуации, вы понимаете, что проще использовать пул БД. Потому что в конечном итоге ваше решение будет очень похоже на пул соединений.

    2. Я делаю авторизацию один раз как часть открытия соединения. Если вам нужно авторизовать другого пользователя позже, вы всегда можете это сделать.

    3. Да, mongodb обрабатывает одновременное использование, но, как я уже сказал, он дает только один канал для записи, и вскоре он становится узким местом. Если вы создадите как минимум столько соединений, сколько ваш сервер mongodb может позволить потокам для их обработки (количество процессоров), то они будут работать на полной скорости.

Если я что-то пропустил, не стесняйтесь просить разъяснений. Спасибо Вам за Ваш вопрос.

Что вам действительно нужно, так это пул соединений с базой данных. Посмотрите на код из этого другого ответа.

Вместо auth, ты можешь использовать withMongoDBPool если ваш сервер MongoDB находится в безопасном режиме.

Это правильный способ настройки? В отличие от создания подключения к базе данных для каждого посещения "/". В этом последнем случае мы могли бы иметь миллионы соединений одновременно. Это не рекомендуется? Каковы преимущества и недостатки такого подхода?

Вы не хотите открывать одно соединение, а затем использовать его. Используемый вами HTTP-сервер, лежащий в основе Скотти, называется Warp. Warp имеет многоядерный дизайн с несколькими зелеными нитями. Вам разрешено использовать одно и то же соединение во всех потоках, поскольку Database.MongoDB прямо говорит, что соединения являются поточно-ориентированными, но что произойдет, так это то, что когда один поток блокируется в ожидании ответа ( протокол MongoDB следует простой схеме запрос-ответ), все потоки в вашем веб-сервисе будут блокироваться. Это неудачно.

Вместо этого мы можем создать соединение по каждому запросу. Это тривиально решает проблему блокировки одного потока другим, но приводит к собственной доле проблем. Затраты на настройку TCP-соединения, хотя и незначительные, также не равны нулю. Вспомните, что каждый раз, когда мы хотим открыть или закрыть сокет, мы должны перейти от пользователя к ядру, подождать, пока ядро ​​обновит свои внутренние структуры данных, а затем вернуться назад (переключение контекста). Нам также приходится иметь дело с TCP-рукопожатием и прощаниями. Мы также, при высокой нагрузке, запускаем файловые дескрипторы или память.

Было бы хорошо, если бы у нас было решение где-то посередине. Решение должно быть

  • Поточно-
  • Давайте максимально ограничим количество соединений, чтобы мы не исчерпали ограниченные ресурсы операционной системы
  • Быстрый
  • Совместное использование потоков между потоками при нормальной нагрузке
  • Создавайте новые соединения по мере увеличения нагрузки
  • Позвольте нам очистить ресурсы (например, закрыв дескриптор), так как соединения удаляются при пониженной нагрузке
  • Надеюсь, уже написано и проверено в бою другими производственными системами

Именно эту проблему решает пул ресурсов.

Некоторые говорят, что я должен использовать пул (Data.Pool). Похоже, это поможет ограничить количество посетителей, одновременно использующих одно и то же соединение с базой данных. Но зачем мне это делать? Разве у подключения MongoDB нет встроенной поддержки одновременного использования?

Непонятно, что вы подразумеваете под одновременным использованием. Я могу предположить одну интерпретацию: вы имеете в виду что-то вроде HTTP/2, в котором встроен конвейер в протокол.

http://research.worksap.com/wp-content/uploads/2015/08/pipeline.png

Выше мы видим, что клиент делает несколько запросов к серверу, не дожидаясь ответа, и затем клиент может получить ответы обратно в некотором порядке. (Время течет сверху вниз.) Этот MongoDB не имеет. Это довольно сложный дизайн протокола, который не намного лучше, чем просто попросить ваших клиентов использовать пулы соединений. И MongoDB здесь не одинок: простой дизайн запросов и ответов - это то, на чем остановились Postgres, MySQL, SQL Server и большинство других баз данных.

И еще: правда, что пул соединений ограничивает нагрузку, которую вы можете принять как веб-службу, прежде чем все потоки будут заблокированы, и ваш пользователь просто увидит полосу загрузки. Но эта проблема может возникнуть в любом из трех сценариев (пул соединений, одно общее соединение, одно соединение на запрос)! Компьютер имеет ограниченные ресурсы, и в какой-то момент что-то рухнет при достаточной нагрузке. Преимущества пула подключений состоят в том, что он изящно масштабируется вплоть до точки, в которой он не может. Правильным решением для обработки большего трафика является увеличение количества компьютеров; мы не должны избегать объединения просто из-за этой проблемы.

В приложении выше, что произойдет, если соединение с базой данных потеряно по какой-то причине, и его необходимо создать заново? Как бы вы оправились от этого?

Я полагаю, что такого рода вопросы типа "что-если" выходят за рамки переполнения стека и не заслуживают лучшего ответа, чем "попробуй и посмотри". Buuuuuuut, учитывая, что сервер прерывает соединение, я могу попытаться понять, что может произойти: если Warp разветвляется зеленым потоком для каждого запроса (что, я думаю, так и происходит), каждый поток будет иметь непроверенную IOException как он пытается записать в закрытое соединение TCP. Warp поймал бы это исключение и использовал бы его как HTTP 500, надеясь, что он напишет что-то полезное для журналов. Предполагая модель с одним соединением, как у вас сейчас, вы можете сделать что-то умное (но с большим количеством строк кода), где вы "перезагрузите" свой main функционировать и установить второе соединение. Что-то, что я делаю для хобби-проектов: если происходит что-то странное, например, обрыв соединения, я прошу свой процесс супервизора (например, systemd) просмотреть журналы и перезапустить веб-сервис. Хотя это явно не отличное решение для производственного, прибыльного веб-сайта, оно работает достаточно хорошо для небольших приложений.

Как насчет аутентификации с auth функционировать? Если auth Функция вызывается только один раз после создания канала, или она должна вызываться при каждом обращении к "/"?

Он должен быть вызван один раз после создания соединения. Аутентификация MongoDB для каждого соединения. Вы можете увидеть пример того, как db.auth() команда изменяет структуры данных сервера MongoDB, соответствующие текущему клиентскому соединению.

Другие вопросы по тегам