четверг, 3 марта 2011 г.

Немного впечатлений от MongoDB, и WSGI-инструментария, в ходе разработки дипломного проекта

На носу "госы". А я каждый день хожу с мыслями о дипломе, и как его сделать. Спасибо куратору, который, можно сказать, заразил сделать систему управления проектами.

Конечно, все не так просто на деле, и с такого масштабного уровня, я опустился на более приземленный. Всего лишь bug tracker.

По пути, пришлось решить множество задач, которые с одной стороны и далеки от самой разработки, с другой стороны, они решают практически все в разработке дипломного проекта, как ПО. Задачи были связаны с выбором инструментальных средств, и вообще того, на чем это все будет держаться и строится. Благодаря этому, приобрел много опыта и главное впечатлений, от пробы того или иного инструментария. В основном, это касалось выбора хранилища данных, и инструментария для построения WSGI-приложения (с Python и WSGI, я определился уже очень давно, так как нравится и язык, и сама идея WSGI, и имеющиеся на данный момент инструменты, позволяют быстро и легко создавать приложения, при этом не мучаясь с организацией приложения, как это происходит в PHP, к примеру).

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


Начнем, пожалуй с WSGI-инструментария.

Раньше, когда я начал интересоваться web-ом, и в частности применением Python в этой области, первым делом я натолкнулся на Django (наверное, практически любой сейчас, начинающий web-разработчик pythonista, сталкивается прежде всего с ним). Что можно сказать. Великолепнейшая документация, великолепнейшая простота для типовых задач, особенно для новичка. Плюс, организация проекта, и помощь в структуризации кода. И много много батареек. Можно сказать, я был просто влюблен в этот фреймворк. Параллельно, конечно интересовался альтернативами, например пытался смотреть Pylons или популярную на тот момент связку, на базе Werkzeug. Но так как, я был совершенно новичком, то это казалось сложным, и несколько пугало, хотя Werkzeug мне и понравился своим большим набором (что сказать, самый настоящий швейцарский нож для WSGI-разработки).

Но, где то с месяца три-четыре назад, натолкнулся я на творение Armin Ronacher (автор Werkzeug и Jinja2), под названием Flask. Что сказать, маленький, простой и родился в результате первоапрельской шутки. На хабре его сравнили с Sinatra (микрофреймворк на Ruby), но как недавно я начал выяснять в чем была шутка, то шутка была подколом в сторону web.py. Посмотрел, понравился, но решил оставить на будущее. В конце концов, порадовало то, что он документирован по моему мнению, гораздо лучше Django, причем это касается не просто документации привычной, но и документирования кода. Плюс, это реальный и простой пример использования Werkzeug и Jinja2, с возможностью простого использования. Мечта сбылась таки.

Все было хорошо, и я пока ориентировался на Django, с оглядкой на Flask, и возможно даже на Pylons. Это продолжалось до того момента, пока не стал думать над задачей хранения данных, и вообще о том, какие данные будут в bug tracker'е. Тут и вспомнил, про замеченный мною раннее MongoDB (как раз последний год, если не ошибаюсь, волна дикой популярности вокруг NoSQL).

Решил посмотреть поближе. В общем то, JavaScript, JSON, и гибкость. И сказать честно, я теперь без ума от этого продукта. Я никогда не любил SQL, потому что, это реальные сложности, это очень тщательное проектирование, и построение запросов, а сложные меняющиеся данные, по моему мнению, в дальнейшем требуют довольно таки больших и сложных телодвижений, чтобы их можно было хранить, сопровождать, и легко менять структуру. Это меня пугает, и пугает до сих пор.

Что же дает MongoDB, и чем она мне так понравилась? Много вещей. Гибкость структуры хранения данных. То есть, не нужно париться над тем, какие таблицы нужны, какие объединять, какие нет, не нужно выдумывать кучу финтов ушами на SQL, чтобы это все было правильно и хорошо. Вот вам коллекция, вот вам JSON (объект на JavaScript, или просто аналог словаря в Python), вот вам древовидная структура для представления данных, и никаких ограничений на эту структуру даже в пределах коллекции.

При дальнейшем рассмотрении, оказалось, что и отношения есть, через dbref (даже между базами, не то что между коллекциями (для SQL - таблицами)), есть запросы, и так же есть возможность указать, какие именно поля нужно получить из записи, да еще и с возможностью slice для массивов. К примеру у вас документ, в нем хранится несколько полей представленных массивами, по 100 элементов в каждом. В сумме, документ может занимать 2-4 Мб к примеру. А нам, нужно всего лишь 10 элементов, из какого-то массива в документе. Не проблема: это возможно. То есть, после запроса, мы получим не 2-4 Мб ненужных данных, а всего лишь несколько килобайт, именно нужной информации.

То есть, по сути, MongoDB позволяет избавиться от множества JOIN и каскадов SELECT, благодаря структуре документа, при этом не теряя гибкости для выборки.

Кроме того, скорость работы MongoDB, уже многих сводит с ума, судя по множеству записей и отзывов людей, которые используют ее в реальных боевых условиях.

После этого, с хранилищем было решено, и осталось посмотреть, какая есть поддержка в Python. Как оказалось, поддержка более чем, полноценная. Драйвер pymongo реализует все возможности MongoDB на данный момент. Но это 'raw' драйвер. Мне захотелось поискать ORM. А вот здесь я был разочарован и просто введен в ступор.

Сейчас, практически все ORM для MongoDB построены по подобию SQL, MongoEngine к примеру для Django, как backend, MongoAlchemy - как плагин для SQLAlchemy. В общем, при работе с этими инструментами просто сразу теряется вся суть документо-ориентировасти MongoDB, из-за которой, я ее и выбрал. Но не все так плохо. Спасибо namlook, за его великолепный проект MongoKit. Эта ORM более чем удобна. Поддерживается вся философия MongoDB и PyMongo, да к тому же возможность задания схемы документа, но не в SQL-виде, а именно в виде словаря, с возможностью валидации даже массивов, и типа хранимых данных. Поддержка GridFS в моделях, индексов, пул соединений. В общем все, что надо.

Выбрав хранилище, пришлось вернуться к окончательному выбору WSGI-инструментария. Django отпала напрочь. Смысла в ней, я просто не вижу, по причине того, что практически все в ней завязано на ее ORM, которая ориентированна именно на SQL-решения. То есть, при использовании MongoKit, отпадает авторизация, pagination, административная панель, построение форм по моделям, и много чего еще. То есть, по сути, это становится грудой бесполезного в моей ситуации кода, который я просто не смогу задействовать должным образом, и который не даст абсолютно никаких для меня преимуществ.

И выбор пал на Flask. После копания в документации и его коде, понял, что его будет легко модифицировать под свои нужды, имея уже приличную, качественную и протестированную кодовую базу, написанную отличным специалистом в области Python и WSGI (что говорить, если Armin состоит в комитете Python, и принимает участие в формировании стандарта WSGI).

Была попытка посмотреть и на Pylons, и на вышедшую Pyramid, но они слишком гибки, и если попытаться их использовать с наскоку, то можно получить лишь разочарование, хотя на самом деле, под ними очень мощная база в виде Paste, WebOb и repoze.bfg (можно сказать, база Zope, благодаря тому, что развитием repoze.bfg насколько мне известно, занимаются авторы Zope).

Пост, пока похож на нахваливание полюбившихся продуктов, но думаю стоит внести и ложку дегтя в эту малину. Местами, конечно, стоит отметить, что эта ложка дегтя касается применения этих вещей в крупных проектах, а мой диплом вряд ли столкнется даже при практическом применении с этими ограничениями.

Начнем с MongoDB. Во первых, стоит отметить, что, если требуется хранить большое количество данных (свыше 2Гб), то использовать 32-системы невозможно. Хотя вряд ли при таком количестве данных, это маленький проект, и вряд ли под MongoDB выделят 32-битный сервер.

Второй "минус" MongoDB - ее документы. В SQL-решениях, благодаря четкой структуре, не требуется для каждой записи хранить имя поля. В случае, с хранением JSON объектов, и их не строгой структуры в пределах даже коллекции, требуется хранить также имена полей, что повышает требуемый объем памяти.

Третий "минус" MongoDB - ее алгоритм записи. Из-за используемого алгоритма, при неправильном завершении процесса сервера (к примеру выключили электричество, и соответственно выключился весь сервер), база может быть попорчена. Для восстановления есть штатные средства, но при восстановлении, меняются _id объектов (по сути индексы объектов), и если что-то в приложениях, работающих на базе, было завязано на них - то это может полететь. Эту проблему, я нашел, у кого-то в блоге, и сейчас не могу точно сказать, насколько это было верно, и насколько верно, для текущей версии.

Четвертый "минус" - минус, или не минус, это зависит от ситуации, но в MongoDB автоинкремент индекса, сделать можно только через одно место. Увы, но это так. Опять таки, в зависимости от задач, это может быть минусом, но обходные решения всегда есть, и возможно они даже не плохи.

Пятый "минус" - в MongoDB есть внутренние ограничения на количество хранимых объектов, на размер документа, а также на namespace. Namespace - один из механизмов монги, используемый к примеру в индексах. Напрямую с ним работать не приходится, однако стоит отметить, что это вводит ограничение на число индексов. По умолчанию, на каждую коллекцию дается два ns, один для самой коллекции, другой для автоматически создаваемого индекса по _id. Так что, тут тоже искусственное ограничение на индексы.

Думаю, там наверное есть еще свои сложности, но также думаю, что их вполне можно обойти.

Что касается драйвера, и в частности MongoDB. Наверное нельзя назвать это именно слабой стороной, скорее, это то, что мне просто неудобно. В MongoKit, нужно регистрировать модели в соединении, а для запросов, требуется порой писать довольно сложные строки, которые представляют "путь" к коллекции, а то и к зарегистрированной модели. Конечно, проблема скорее высосана из пальца, и это просто непривычно. Ну и плюс ко всему, нет интеграции с различными библиотеками, для работы с формами. По крайней мере с WTForms, который часто используют в связке с Werkzeug и Jinja2. Не то, чтобы проблема, но думаю, отсутствие таких инструментов, полностью перекрывается достоинствами используемых инструментов.

Что касается Flask. Это микрофреймворк, и если требуется построить сложное приложение, то требуется написать и изрядное количество батареек. И к сожалению, пока нет плагина для работы с MongoKit. Решение, через расширение, подобно Flask-SQLite3 - не лучшее решение. Так как WSGI-приложение, это не скрипт PHP, вызываемый при каждом запросе, а созданное приложение, которое конвеером обрабатывает запросы, то каждый раз создавать целый pool соединений с MongoDB и регистрировать модели всех модулей - довольно затратная вещь. Но так как, я решил пойти по пути модификации кода Flask, то решил эту задачу через создание объекта Connection, при создании экземпляра приложения. Один пул соединений, автоматически управляемый MongoKit - и никаких, так сказать проблем. Плюс регистрация моделей, производится лишь при регистрации модулей приложения, а не каждый раз, при поступлении запроса. Это упрощает код view, а также, избавляет от необходимости явно импортировать модели из соответствующих модулей приложения.

Подведу итог. Пост получился спонтанный, много хвалебных слов, но хочу еще раз повторить. Это лишь мое личное восприятие на данный момент, при решении задачи, которая стоит передо мной. В реальных, крупных проектах, возможно все это может быть совершенно по другому =))) так что, не судите строго.