В настоящее время я экспериментирую с небольшим веб-сервером Haskell, написанным в Snap, который загружает и предоставляет клиенту множество данных. И мне очень трудно получить контроль над процессом сервера. В случайные моменты процесс использует много CPU в течение нескольких секунд до минут и становится невосприимчивым к клиентским запросам. Иногда использование памяти скапливается (а иногда и падает) сотнями мегабайт в течение нескольких секунд.
Надеюсь, у кого-то есть больше опыта с длительными процессами Haskell, которые используют много памяти и могут дать мне несколько указателей, чтобы сделать эту вещь более стабильной. Я отлаживал эту вещь в течение нескольких дней, и я начинаю немного отчаяться здесь.
Небольшой обзор моей установки:
-
При запуске сервера я прочитал около 5 гигабайт данных в большую (вложенную) структуру Data.Map в памяти. Вложенная карта - это значение строгое, и все значения внутри карты относятся к типам данных, причем все их поля также строги. Я потратил много времени на то, чтобы не избежать неоплаченных громов. Импорт (в зависимости от загрузки системы) занимает около 5-30 минут. Странная вещь - флуктуация последовательных прогонов намного больше, чем я ожидал бы, но это другая проблема.
-
Большая структура данных живет внутри "TVar", которая разделяется всеми потоками клиентов, порожденными сервером Snap. Клиенты могут запрашивать произвольные части данных с использованием небольшого языка запросов. Объем запроса данных обычно мал (до 300 кбайт или около того) и касается только небольшой части структуры данных. Все запросы только для чтения выполняются с использованием "readTVarIO", поэтому они не требуют каких-либо транзакций STM.
-
Сервер запускается со следующими флагами: + RTS -N -I0 -qg -qb. Это запустит сервер в многопоточном режиме, отключит время простоя и параллельный GC. Это, похоже, ускоряет процесс.
Сервер работает без проблем. Тем не менее, время от времени запрос клиента истекает, и процессор достигает 100% (или даже более 100%) и продолжает делать это в течение длительного времени. Тем временем сервер больше не отвечает на запрос.
Есть несколько причин, по которым я могу думать, что может привести к использованию ЦП:
-
Запрос требует много времени, потому что предстоит много работы. Это несколько маловероятно, потому что иногда это случается для запросов, которые оказались очень быстрыми в предыдущих запусках (с быстрым я имею в виду 20-80 мс или около того).
-
Есть все еще некоторые неоцененные thunks, которые необходимо вычислить до того, как данные могут быть обработаны и отправлены клиенту. Это также маловероятно по той же причине, что и предыдущая точка.
-
Как-то сборка мусора запускается и начинает сканирование всей моей кучи на 5 ГБ. Я могу себе представить, что это может занять много времени.
Проблема в том, что я не знаю, как правильно понять, что происходит и что с этим делать. Поскольку процесс импорта занимает столь долгое время, результаты профилирования не показывают мне ничего полезного. Кажется, что нет никакого способа условно включить и выключить профайлер из кода.
Я лично подозреваю, что проблема GC здесь. Я использую GHC7, который, похоже, имеет множество возможностей для настройки работы GC.
Какие настройки GC вы рекомендуете при использовании больших кучей с очень стабильными данными?