четверг, 12 марта 2009 г.

сериализация и юниттесты (сумбурно)

Я понемногу склоняюсь к мысли, что pickle - очень удобный, но далеко не всегда оптимальный формат.

Во первых - слишком он легко идет. Сериализует почти все, что угодно. В этом есть и минусы.

Вчера после работы чуть задержался в офисе. Приятель получил почетное переходящее знамя билдера (мы их ежедневно делаем), а я за компанию.

Плюс хотел юниттестами новое изобретение покрыть полностью и строго. Без них отдашь хорошую задумку людям, а через месяц-два в ней появится куча слабых мест и откровенных ляпов. Заметил: если программист не понял основную мысль дизайна/архитектуры - он начинает "гладить кошку против шерсти". А на работе на пальцах можно пересчитать тех, кто задумывается: а почему класс А был сделан так, а не как-нибудь иначе? Вот и лепят свои патчики так, что волосы дыбом.
Юниттесты позволяют вовремя дать по рукам. Получивший с большой вероятностью подойдет ко мне с вопросом, а там уж я ему заясню генеральную линию партии.

Это - помимо основной задачи: находить баги в коде. С этим тоже есть своя проблема. Если архитектура "правильная" - тесты пишутся легко и непринужденно, в больших количествах. И выполняют свое главное предназначение. Я только относительно недавно смог ощутить это в полной мере. И, думаю, все еще очень далек от совершенства.

Большая часть нашего немальнького кода, естественно, этому критерию не отвечает. Т.е. тесты баги не находят и не могут найти - т.к. для тестирования функции приходится подменять практически все ее окружение. Но тесты пишут, ибо начальство этого настоятельно требует. Результат тоже небесполезен: если ломается тест в результате твоих изменений - то вынужденно следует review, правится либо тест либо код, а чаще все вместе. Заодно есть шанс познакомиться с результатами чужого творчества и увидеть, что успели сделать другие люди/команды. К слову, американы в основной массе очень не любят смотреть чужой код. Наверное, это общая проблема - до этого мне просто сильно везло с коллегами.

Отвлекся.

Возвращаясь к pickle. Тесты в тот вечер я, как вы уже догадались, не писал.
Внезапно сервисы перестали работать. Практически совсем. Каждая транзакция заканчивалась exception и требуемого ответа не было. У нас клиент-сервер, так что сломалась серверная часть. Локальная катастрофа, вечерний неудачный билд не уйдет к тестерам, они не расскажут о новых ошибках, на один день сломается вся структура. Камрад Нил быстро понял, что последний находящийся в офисе чел из команды сервисописателей дупля откровенно не отбивает, окинул острым взором оставшийся народ и безошибочно направился ко мне.

Проблема была вроде бы тривиальная: что-то не могло пройти через pickle потому что было слабой ссылкой (weakref). Как я быстро обнаружил, пытались протолкнуть весьма нетривиальный объект, внутри которого к тому же было колечко (pickle ведь отлично сериализует объекты с циклическими ссылками).

Какой из многочисленных претендентов имел внутри этот самый weakref - непонятно по генерируемому исключению (к слову, использовался cPickle, а от него stacktrace совсем куцый). Путем дедукции и напряжения мозга сообразил, что пытались запиклить объект из нашего ORM (а-ля SQLAlchemy, но в силу наших внутренних особенностей алхимию использовать невозможно, создано ее подобие).

Я и не предполагал, что кому-то прийдет в голову передавать по сетке объект базы данных. Это же невозможно по ахитектурным соображениям: где сессия, к чему теперь информация об изменениях полей? Весь концепт рушится!!! Примерно как недавно кто-то пытался сериализовать boundmethod.

Быстро переопределил для всех сущностей из ORM __getnewargs__, дабы бросал хорошее и информативное исключение. Естественно, на следующем прогоне сразу же поймал вора.
Потребовался крошечный фикс на 10 строк, и успешный билд ушел.

А на следующий день я крепко насел на Тима, лида этих сервисников (к слову, Тим - весьма неглупый парень и голова у него работает как надо). Настоящая ПРОБЛЕМА была в том, что пересылалось ОЧЕНЬ МНОГО ИЗБЫТОЧНЫХ ДАННЫХ. Которые гораздо быстрее было бы получить на приемнике (они у него на самом деле уже есть). Т.е. вместо того, чтобы проталкивать очень большой объект, очень редко изменяемый (обе стороны уже имеют его в своем кеше, следят за изменениями и сами перечитывают когда нужно, прогнозируемые изменения не чаще раза в день) - можно передать его unique key, и подхватить его на другой стороне.

Отчасти ее видели по резкому снижению производительности серверной части. Но ведь «кажется, стало медленней работать» - еще не показатель»

Тим сильно удивился такому положению дел. Предложил быстрый патчик (и он прошел - еще бы, тривиально, когда знаешь в чем дело). Посопротивлялся идее "а давайте напишем свой pickle с нашими ограничениями".

Это его не спасет, т.к. только свой сериализатор позволит вовремя заорать: не то в меня пихают, на что я согласный!!!

Для начала сделаем свой restricted pickle(унаследоваться от Pickler и UnPickler и зарезать функциональность) , а потом, если будет нужно - перепишем на плюсах, как водится.

Уже практически сказал все, что хотел.
Теперь - во вторых (мелочь).

Специализированный pickle может быть быстрее. Не знаю насколько. Быть может - на "совсем чуть-чуть". Можно быстрее обрабатывать твои специфические типы, не кодируя их как "наследник от object, лежащий в модуле my_pope.my_cardinal.my_priest.my_guy", а просто вставив свой префикс для класса.

Улучшение, наверное, будет иметь только академически интересный прирост в скорости. Но к тому, что в стандартный pickle пролезут совсем неожиданно огромные объекты - будь готов. Просто по недосмотру и незнанию. Сейчас ты контролируешь все - но что будет, если проект разрастется?

3 комментария:

  1. Можно пропробывать http://code.google.com/intl/ru-RU/apis/protocolbuffers/docs/overview.html
    И по скорости хорошо. И идея светлая.

    ОтветитьУдалить
  2. Много чего можно. Только "тупо" пиклить - не стоит.

    ОтветитьУдалить
  3. Иметься ввиду если вообще по сетке передавать данные то Protocol Buffer, для простых данных хорошо подойдет. Тем более заставляет тебя так сказать к строгости.

    ОтветитьУдалить