среда, 9 марта 2016 г.

Веб поиск на Go. Начало. Выбор БД.

Мой первый опыт знакомства с Go только по документации и статьям получился довольно неудачным, язык казался страшным и неудобным по большинству параметров. Но раз так много и упорно о нём в последнее время говорят, значит что-то в Go есть?! В общем я решил познакомиться с ним поближе. И по моему глубокому убеждению, проекты типа "hello world" для этого очень плохо подходят. Нужна большая интересная задача, в режиме "хотя бы пара месяцев по вечерам после работы". В общем я начал писать свой маленьких поисковик.

 Задача из категории невыполнимых. В том плане, что на "взрослые" поисковые системы потратили сотни человеко-лет работы очень и очень умных людей. Это значит, что соревноваться с ними не возможно в принципе и никакого тебе уязвлённого самолюбия, что не смог сделать законченный проект. А следовательно можно не заморачиваться, а просто выбирать наиболее интересные задачи и для фана их реализовывать. Как только надоест - забрасываем проект безо всяких угрызений совести. В общем идеальная задача для изучения нового языка, ну и технологий поиска заодно.

Если сразу ринуться в бой и начать тестировать возможности Go по парсингу HTML онлайн и выдиранию из него ссылок, да ещё и параллельно проверять как эффективно работают горутины - для ускорения процесса. То окажется, что сайты отнюдь не горят желанием поддерживать тебя в этом благородном начинании. Многие считают, что десяток-другой запросов в секунду с одного IP это DDoS атака и перестают отдавать мне контент. Некоторые, например habrahabr, поступали особо жестоко - он начинал отдавать мне маленькую страницу с ошибкой 503 (Service Unavailable). И пока я не добавил анализ кода ответа - очень радовался, что удалось написать код, который так быстро разбирает страницы.

В общем довольно скоро назрела необходимость в написании выделенного, относительно умного краулера с возможностью сохранять всё закаченное в БД. Одним из самых главных требований, которые я предъявлял к базе - она должна быть встроенной. Очень не хотелось, что бы для запуска моего приложения требовалось сначала поставить и настроить ещё десяток сторонних. Для Go таких баз уже написали довольно много, но почти все самые популярные - это NoSQL.

Чаще всего встречалось упоминание BoltDB. Поэтому с неё я и начал. Довольно аскетичная база - классическая key-value, оба значения бинарные, что снимает любые ограничения на хранимые данные. По ключу естественно автоматически строится индекс. В качестве плюса можно отметить реализованные транзакций и даже целых 2 типа: на "чтение" и на "чтение-запись". Скорость, традиционно для NoSQL - на высоте, по крайней мере если соблюдать несколько несложных правил - можно добиться отличных результатов.

Как вы будете переводить свои данные в бинарный формат для сохранения в базу, авторов совершенно не волнует. Возможно это и правильно, с той точки зрения, что подобного рода базы не должна навязывать какие-то ограничения в этом плане. Тем более, что для Go сериализаторов довольно много. Вот тут можно найти большую сводную таблицу с производительностью самых популярных из них. В итоге по соотношению скорость\удобство работы\кроссплатформенность был выбран MessagePack. Скорость последнего обеспечивается за счёт того, что для каждой конкретной структуры, при помощи "go generate", генерируется специфический код для сериализации\десериализации. Это в лучшую сторону отличает библиотеку от нативного пакета binary, который работает через рефлексию.

На первый взгляд всё довольно неплохо. Но после примерно 2 недель реального использования, оказалось, что плюсы у такой связки отсутствуют. Скорость работы - нивелируется тем, что между запросами к сайту нужно делать минимум 1 секунду задержки, что бы они не банили, а парсить больше 10 сайтов одновременно я особого смысла сейчас не вижу. В итоге для сохранения 10 страниц в секунду особой производительности от базы не требуется. Второй типичный для nosql решений плюс - возможность в одной таблице хранить разнородные данные, я так и не смог реализовать. В таблицы данные писались в сугубо однородном виде, страницы, что характерно, разные сайты отдавали в стандартизированном виде.

А вот минусов оказалось много:
  • Отсутствие инструментов для просмотра содержимого базы - приводило к тому, что просто для того, что бы посмотреть, что реально записалось в базу приходилось писать код, пусть по 10-20 строк, но всё же приходилось.
  • Обновление структуры базы, например добавление нового столбца, опять же приводит к тому, что я пишу код, порой довольно много кода.
  • Без индексов по полям сохраняемого документа, как оказалось, жить просто не возможно. Можно конечно написать свои собственные, на основе того, что предоставляет база, но тогда пришлось бы в каждом месте, где я сохраняю данные в базу, сразу писать код для обновления индексов. Но писать фактически свою БД в мои планы не входило.
  • Отсутствие макросов, шаблонов и нормального наследования в Go, привело к тому, что мне не удалось спрятать всю эту сериализацию\десерриализацию, обновление индексов, прочие особенности работы с BoltDB в отдельный пакет. В вызывающем коде приходится постоянно учитывать подобную специфику. Вероятно, если сильнее углубиться в возможности рефлексии Go, то эта проблема решается, но опять же это получается я буду писать свой фреймворк для работы с базой, а не поиск.
В итоге, после двух недель убитых на покорение nosql и трёх глобальных переписываний кода, я отказался от BoltDB. Не скажу, что это было совсем бесполезной тратой времени, я узнал довольно много о Go. А так же вынес довольно интересное, для меня наблюдение - на Go очень легко писать. После базового знакомства с синтаксисом просто начинаешь писать не задумываясь. На C++ и Rust приходится существенную часть времени уделять тому, что бы придумать как ту или иную вещь реализовать в языке. Тут такого нет - абсолютно типовые конструкции не напрягают мозг совершенно. Получается почти как на Python, только в последнем нет компилятора, который многие ошибки вылавливает. Читать чужой код, кстати, благодаря простоте языка тоже очень просто, даже удивительно. Плюс очень радует просто запредельное количество готовых пакетов. Хотя и минусов у языка хватает, я в прошлый раз уже о них писал.

В итоге выбор с БД у меня сократился до SQLite. С кучей сторонних приложений для работы с базой. И как бонус с возможностью относительно легко переделать код для работы с другими реляционными БД. А с задачей абстрагирования от БД мне успешно помог ORM Gorm. В последнем, кстати, я смог даже решить такую интересную задачу как - абсолютно прозрачное сжатие через zlib контента страницы при сохранении в БД, и разархивация при обращении к этому полю. Вообще, очень интересный пакет, сильно рекомендую посмотреть на него, упрощает работу с БД в разы.

На сегодня пожалуй хватит и так довольно много получилось.

Комментариев нет:

Отправить комментарий