• Haskell Продолжаю запиливать парсер на Haskell. Текущая версия получилась топорная, медленная. Есть список категорий (>900), которые парсятся последовательно.
    Каждая категория отдаёт 270 страниц, на каждой по 40 объектов из данной категории. Если брать по 1 странице, то в среднем уходит 10-15 секунд на страницу. Сейчас реализовано ужасно и отвратительно на коленке: 1 страница — 1 runDB $ mapM_ insertUnique obs (до 80 запросов c postgresql-persistent) Медленно! За 7 часов непрерывной работы — 190 тысяч записей в базе и 21 категорию прошли. Т.е. примерно 10-15 дней парсится будет. Не годится.
    Пробовал при такой реализации по 4-8 страниц подавать многопоточно с forkIO.
    В итоге выбиваю пул коннектов и через 2-10 минут вываливаюсь с ошибкой. 10 минут, если играться с threadDelay.
    До следующей категории тупо не дойти. Часть данных теряется. Не годится.
    В итоге, как я понял, надо осилить concurrency и отделить парсинг страниц от вставки в базу. Т.е. забивать список, ограничив сверху K, с одной стороны. А с другой стороны брать по N записей и класть их в одну транзакцию. Тогда забивать очередь будут все до K, потом ждать M мс. опа, K-N — уплотняем, парсим дальше. K — ждём.

Replies (11)

  • @agr, Можно и разделить процессы обрабатывающие данные и пишущие в базу и соединить их каналом, но кофейная гуща подсказывает что проблемы в чем-то другом.
  • @qnikst, Да там чётко было: увеличиваю число тредов — одновременно обрабатывающихся страниц — начинают сыпаться Couldn'tGetSQLConnection. Вырубаю многопоточность — парсер работает без сбоев. Всё из-за привязки парсинга к вставке в базу.

    И да, на канал как раз и смотрел. Это естественно, но глядя на вас, я пока немного офигеваю. Наверное, с практикой придёт понимание. Нужно больше практики.
  • @agr, возможно, просто 10-15 сек на страницу кажется, что много, тут в одном месте где парсятся страницы предварительно преобразуясь и с лексическим анализом, за 20 секунд прожовывает под сотню (основную часть кода писал не я), при этом видны места где можно улучшать.

    Поэтому хорошо бы понимать где тратится время.

    Исходя из этого разделять логику разбора и вставки вполне логично и правильно.

    причем логика не сильно поменяется:

    ```
    ch <- newChan
    replicateM_ workersNo $ forkIO (parseData >>= writeChan ch)
    replicateM_ dbWorkersNo $ forkIO (readChan ch >>= runDB ...)
    — ожидание завершения
    ```

    С каналами есть достаточно большой выбор, но для начала можно взять обычные IO. (и я уже не помню как там в persistent с базами все работает, так что могу не вспомнить об очевидных подводных камнях)
  • @agr, Наверное, с практикой придёт понимание. Нужно больше практики.
    да, практика сильно помогает, а вообще тут прилично более толковых haskell-е девелоперов :)
  • @qnikst, не скромничай :)
  • @qnikst, Ну я не конкретно про вас, а вообще про народ местный. Клёвые вы.
  • @qnikst, Спасибо! Может и в этом году заценить успею.
  • @agr, Речь о веб-страницах? Непонятно что с ней можно делать 15 секунд. Это же явно не CPU-bound, если там не алгоритмы с экспоненциальной сложностью, и вряд ли где-то рядом стоит ленточный накопитель, значит очень-очень-очень много обращений куда-то по сети?
    Я бы для начала в эту сторону посмотрел, где-то сидит совершенно чудовищный в своей неэффективности код, который должен легко ускоряться.
  • @blaze, 15 секунд уходят на всё: от получения страницы до 80 запросов в базу.
  • @agr, уходит 10-15 секунд на страницуНу тут, наверно, надо показать код.
  • @rkit, Не надо.
    Честно.