2013-11-24

О JSON в PostgreSQL

Как известно, в последних версиях PostgreSQL появилась поддержка JSON. Соответствующий тип данных появился в версии 9.2. Набор функций для работы с JSON был существенно расширен в версии 9.3.
В связи с этим, а также потому что у нас JSON используется для передачи данных по сети, возник соблазн воспользоваться новым типом данных для того, чтобы сэкономить на сериализации реляционных данных. Еще раз, данные - преимущественно реляционные, мы их сериализуем в JSON каждый раз для передачи по сети. Вопрос: целесообразно ли хранить уже сериализованные данные в JSON поле (конечно же в ущерб удобству модификации этих данных)?
Еще в списке функций для работы с JSON соблазнительной выглядит row_to_json(), которая может представлять строку обычной таблицы (или выборки) в виде "плоского" JSON документа, содержащего поля одного уровня вложенности согласно колонкам результата. Примерно такого документа:
{ "column1" : value1, "column2" : value2 }

Чтобы проверить возможности PostgreSQL я написал простенький тест. Вот такие получились результаты для PostgreSQL 9.3 на моем локалхосте:
Я просто сгенерировал 100 тысяч строк с десятью текстовыми колонками по 100 байт случайных символов, а также 100 тысяч аналогичных JSON документов (в отдельной таблице) и пытался выбирать эти строки и документы различными способами. Я делал просто SELECT *, я просто выбирал JSON документ целиком, я делал ручную сериализацию, я преобразовывал реляционные строки в JSON с помощью row_to_json(). Я повторил это для половины всех колонок и полей JSON документа (красные полоски).
Быстрее всего оказалось просто извлечь JSON документ. Однако извлечь половину полей получалось значительно медленнее, позже объясню почему. Удивительно, что получение реляционной строки через row_to_json() практически идентично по времени с получением этих данных напрямую. Это значит, что если вас устраивает примитивный "плоский" JSON, то использование row_to_json() (т.е. сериализация на стороне PostgreSQL) может быть хорошим решением. Ну и сериализация в JSON на клиентской стороне - не так уж и медленна (я использовал Python, не самый быстрый вариант).

Я, конечно, не удержался и повторил эксперимент в MongoDB.
Монга слегка подпортила мне набор данных тем, что добавила _id ко всем документам. Еще (как минимум в Python) оказалось, что документ, возвращаемый драйвером Монги - вовсе не JSON, и его нужно явно сериализовать. Зато Монге не требуется титанических усилий, чтобы извлечь часть полей из документа.

Так почему же PostgreSQL такой медленный, когда приходится делать "срезы" JSON? Посмотрите еще раз на список функций и операций над JSON. Здесь есть извлечение значений отдельных полей (причем можно сразу в виде текста). Есть преобразование в другие типы. Есть возможность извлечь поле вложенного документа. Но нет ничего, чтобы сделать "срез". В результате приходится извлекать по отдельности n полей и затем склеивать их в новый JSON. И, судя по этому графику, извлечение поля - весьма затратная операция.
Я замерил время выборки 100 тысяц строк с различным числом колонок/полей: из обычной SQL таблицы, из JSON документа, хранящегося в PostgreSQL, из MongoDB.

В общем, PostgreSQL - это вам не MongoDB. JSON здесь - лишь CLOB с проверкой синтаксиса. И использовать его нужно целиком, "как есть". Есть, конечно, операции доступа к внутренностям этого CLOB, но они довольно дороги. И (в отличие от Монги) совсем нет операций для модификации JSON документа "на месте". Ну вы поняли.

Для себя мы остановились на обычных SQL таблицах и, по возможности, использовании row_to_json().

2013-11-17

О ворчунах

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

Гном Ворчун из диснеевской "Белоснежки" 1937 года. Классический ворчун. Единственный не поддавался женскому очарованию Белоснежки. Однако сдался после поцелуя в лысину и далее был исключительно на её стороне. В интернетах считают, что Ворчун мог бы составить с Белоснежкой гораздо более интересную пару, чем какой-то банальный принц.

В "Мишках Гамми" тоже был Ворчун. Больше я про него ничего не помню.

Есть еще и другие "Заботливые мишки". Со своим Ворчуном. Этот Ворчун более прагматик, нежели ворчун. Мне нравится. Знаменит своими невероятно вкусными печеньями, сделанными, тем не менее, как попало и из чего попало.

Мистер Брюзга (так уж перевели) из "Мистера Мэна" - французского мультсериала с толстым английским юмором. Самый обычный синий квадратный ворчун. Интересен не столько он сам, сколько мультик и, полагаю, книги. Действительно исконно английского автора. Почему-то мне кажется, что эти круглые, квадратные (а иногда треугольные) мистеры и мисс как-то вдохновили создателей отечественных "Смешариков".

Смурфный ворчун. Тоже синий. Идеальный ворчун в том отношении, что ему действительно ничего никогда не нравится.

Шрэк - замечательный ворчун. Тут добавить нечего. Хотя, пожалуй, излишне добродушный. Зеленый.

Капитан (тоже) Зеленый из "Тайны третьей планеты". Очень любимый мною ворчун. Прекрасно понимает, что ворчать бесполезно, а потому просто приходит и спрашивает: "Ну, что у нас плохого?"

В "Понечках" явных ворчунов не наблюдается. То ли потому, что контингент преимущественно женский. То ли потому, что главгероинь меньше семи.
Хотя Спайк иногда ворчит. Особенно когда его просят что-то сделать тогда, когда он хочет спать.
А вот Рэйнбоу Дэш, когда её покусал "заколдовал" Дискорд, превратилась вполне так в полноценного ворчуна. Всех посылала подальше с их заботами. Жаль, что ненадолго :)



P.S. Пока искал картинки с Рэйнбоу Дэш, обнаружил такой вот шедевр:
Это не про ворчунов, зато про лень :) Картинка, кстати, называется "Best job EVER". (с) AxemGR



2013-11-10

О Кассандре

На HighLoad++ я проиллюстрировал Кассандру портретом Зекоры.
С тех пор мое отношение к ним не изменилось :) И теперь есть пони про NoSQL ;)

Да. Мы используем Кассандру. Которая Apache Cassandra.

Каждый знает, что модель данных в Кассандре, это BigTable, или ColumnFamily или еще как-то. Нужно только помнить, что здесь таблица, это не совсем та, и даже совсем не та таблица, что в SQL. Здесь у каждой строки есть обязательный, уникальный и единственный ключ. В каком-то смысле это аналог первичного ключа в SQL. Только обязательный и уникальный. И не может быть составным. И не имеет явного имени. Ключ используется для шардинга. Строка - единица хранения, и именно строки распределяются между узлами кластера и реплицируются. Ключ используется для записи. Для любой операции записи нужен ключ строки и обязательно он (ну и имена и значения колонок, конечно).
Колонок в таблице может быть сколько угодно (реально, миллиарды). Схемы нет. Проблемы c null значениями в "лишних" колонках тоже нет. При желании, каждая строка может иметь абсолютно свой набор колонок. Для правильного использования Кассандры важно знать, что колонки у нее упорядочены по имени. В отличие от строк, которые упорядочены так, как при начальной конфигурации кластера было положено (а положено в 99% случаев в порядке хэшей значений ключа строки).
Собственно, тройка: ключ строки - имя колонки - значение, приводит нас к мартинфаулерскому определению BigTable как мапа мапов (ну или словаря словарей). Только в Кассандре были (и есть) еще супер-колонки, что приводит нас к мапу мапа мапов. А с появлением составных колонок появляется возможность изобразить мапы сколько угодной вложенности. И даже замапить произвольные объекты и даже документы. С нетерпением жду библиотеки, позволяющей сохранять JSON документы в Кассандру как это делает Монга...

Ко всем этим прелестям в виде семейств колонок, колонок, супер-колонок и вторичных индексов доступ осуществляется через бинарный RPC протокол Thrift. Обращаться можно к любому узлу кластера, он займется контролем ваших операций и будет пересылать данные с/на другие узлы кластера. Однако это может быть неэффективным, и даже может быть неприятным (если узел, к которому вы подключились, упадет). Поэтому настоятельно рекомендуется пользоваться Гектором (который брат Кассандры). Это такой умный клиент, который понимает, как располагаются данные в кластере Кассандры, а также мониторит доступность узлов кластера. В результате он передает запрос на правильные узлы, чтобы максимально быстро получить ответ.
Кроме Трифта есть еще CQL (я имею ввиду CQL 3.х). Внешне это язык запросов, обманчиво похожий на SQL. Только помните, что это не SQL. В условиях WHERE вы не можете использовать колонки, на которых нет индексов. Несколько условий в WHERE вы можете объединять только операцией AND, но не OR. UPDATE возможен только по первичному ключу (безо всяких WHERE)... Внутри же CQL коварным образом скрывает и преобразует структуру нашей BigTable, делая её чуть более похожей на реляционную модель. Однако лишая вас веселой возможности манипулировать миллионами колонок.

В общем, Кассандру надо уметь готовить. Например, нам понадобилось обновлять некую группу значений (закодированных в JSON, черт, антипаттерн) из разных источников так, чтобы победил сильнейший последний, кто внес изменения. Если бы время изменений определялось временем прихода их в Кассандру, не было бы проблемы. Просто перезаписываем значение и все, а Кассандра уже сама внутри кластера разберется, какое значение последнее. Но наши источники любят менять данные самостоятельно, по различным печальным причинам сообщая об этом Кассандре позднее. Т.е. разрешать конфликты нужно по таймстампу изменения, присланному вместе с данными. В SQL можно было бы воспользоваться транзакциями и сделать read-modify-write: прочитать таймстамп из базы, убедиться, что пришедшие данные новее и записать их. Но в Кассандре нет транзакций (по крайней мере до версии 2.0) и пока вы будете читать да думать, кто-то другой уже перезапишет ваши данные. В SQL можно было бы сделать и еще проще, что-то вроде условного апдейта:
UPDATE table SET data = new_data, timestamp = new_timestamp WHERE id = id_data AND new_timestamp > timestamp.
Но в Кассандре нельзя делать WHERE на UPDATE. В Монге можно было бы воспользоваться операцией findAndModify (которая во многом аналогична приведенному выше апдейту). Но в Кассандре нет findAndModify.
Что можно делать в Кассандре? Создавать столбцы и хранить все изменения. Собственно, упорядоченные столбцы, это очень даже естественный (для Кассандры) способ хранить time-series data. Ну т.е. берем и на каждый апдейт создаем новый столбец, чье имя содержит значение таймстампа. Получаем кучу столбцов вида: data:timestamp1, data:timestamp2... При чтении выбираем наибольший столбец и вуаля.
Однако тут возникает еще одна неприятная особенность Кассандры. Если мы хотим выбрать только определенные столбцы, то мы либо должны явно перечислить их имена (единственный способ, доступный в SQL), либо задать начальное имя столбца и количество столбцов в выборке (или же конечное значение имени столбца). Но вот нельзя выбрать и по именам, и по диапазону. В наших же данных, кроме чудесного столбца data с таймстампом, было еще и несколько обычных столбцов. Примерно так:
columnA, columnB, data:timestamp1, data:timestamp2
Конечно, можно выбирать все колонки и выбирать данные с последним таймстампом на клиенте. Но очень не хотелось гонять лишние данные по сети. Поэтому мы применили грязный хак военную хитрость. Помните, что колонки отсортированы? А количество "обычных" колонок - известно. А из таймстампов нужен наибольший. Так давайте назовем колонки так, чтобы они выстроились в нужном порядке:
_data:timestamp1, _data:timestamp2, columnA, columnB
А теперь выбираем нужное количество колонок (в данном примере - три), в обратном порядке (т.е. начиная с конца). И получаем только те данные, что нужно.

Столько мучений. Зачем же использовать Кассандру?
Ну вот, например, зачем. У нас все выглядит примерно так:
Кассандра размазана между четырьмя датацентрами. Данные реплицированы тоже четыре раза, т.е. в каждом датацентре есть своя копия. В каждом датацентре есть API, причем API без состояния, все запросы отражаются в Кассандре. А вот клиенты выбирают API для запросов в случайном порядке. Нет привязки клиентов или датацентров по каким-либо признакам вроде географического расположения. И получается совершенно честный мультимастер. Реликация и контроль целостности полностью обеспечиваются Кассандрой. Я не знаю, как такое сделать с каким-либо SQL или даже с Монгой (где имеются выделенные мастеры, принимающие все запросы записи).
Кассандра легко разворачивается, довольно быстра, надежна и практична. Пользуйтесь.

2013-11-06

Об идеальном гаджете

Выступление Дэна Ромеску на GDG DevFest Omsk 2013 разбередило мои давнишние фрустрации на тему идеального гаджета. Дэн пытался показать, что таскать на себе акселерометр, умночасы, парочку умнотелефонов, а также иметь дома умновесы - это нормально. Удивительно, что гуглоочки на DevFest принес не он :)
Сам я тоже обвешан гаджетами. В кармане всегда - Galaxy Nexus (4.65", 316 PPI). В рюкзаке - Asus Transformer TF101 (10.1", 149 PPI) и Asus Zenbook (13.3", 138 DPI). К чему привожу тут цифры? Размер имеет значение. Разрешение тоже, но размер - заметнее :) Способ ввода тоже важен.
Телефон хорош для СМСок, Твиттера и тому подобного общения максимум на пару предложений. На нем можно смотреть ленты того же Твиттера и Гуглоплюса. На нем можно оперативно воспользоваться переводчиком/словарем или картами. Но что-то более серьезное требует большего экрана.
Планшет (десятидюймовый) хорош для чтения. Статей, новостей, книг. Хорош для рисования. Я, конечно, не художник, но набросать диаграммку или какую-нибудь белиберду в момент великой скуки - можно. Хоть Трансформер у меня и с клавиатурным доком, самой клавиатурой я пользуюсь редко (в основном пользую как вторую батарейку). И причин неиспользования клавиатуры две. Первая - Андроид. Это замечательная по многим параметрам операционная система. Но для создания контента она не приспособлена. Просто сравните функционал того же Гмейла и Гуглодрайва в браузере и в андроидном приложении. В браузере можно все. В андроидном клиенте комфортно можно только смотреть. Не хватает многооконности, чтобы копипастить туда-обратно. Банально нет серьезных IDE (для меня создавать - это в первую очередь код). Вторая причина - размер экрана. 10 дюймов - это очень мало, чтобы вместить даже все панельки серьезного текстового редактора (не говоря уж об IDE).
Размер имеет значение. Поэтому у меня есть тринадцатидюймовый ноутбук. До этого был чудесный семнадцатидюймовый HP с матовым экраном. Чудесный экран. Но совершенно нетранспортабельный ноутбук. Экспериментально пришел к выводу, что 13 дюймов - это конфортный минимум для создания контента. На FullHD в 13 дюймов прекрасно умещается IDEA. И сенсорный экран на ноутбуке мне не нужен. Руки устанут тянутся и тыкать в него. Проверено на Трансформере, с подключенным доком удобнее пользоваться тачпадом, чем тыкать в экран.

Итак, размер имеет значение. А значит, идеальный гаджет должен быть с изменяемой геометрией экрана. Да, достаю из кармана маленький совочек. Стало тесно? Тянем за уголки и растягиваем. До лопаты. Как сейчас окно на десктопе. Растягиваем до семи, десяти, пятнадцати дюймов. Хотим посмотреть кино с друзьями - растягиваем до метра, вешаем на стену и смотрим. Фантастика? А то!
Пожалуй, первый подход на эту тему нам продемонстрировал Asus. Со своим PadPhone. Пожалуй, это все, что можно сделать на сегодняшнем уровне технологии. Хотите телефон - вот вам телефон. Хотите планшет - пристегните телефон к большому экрану. Хотите ноутбук - пристегните клавиатуру. Все хорошо. Но мы ограничены десятью дюймами. Дополнительные экраны и клавиатуры надо еще с собой таскать. Ну и не так это элегантно, пристегнуть вместо растянуть ;)
Второй подход обещают гибкие дисплеи. Моей фантазии пока хватает только на большой дисплей, свернутый в трубочку и помещенный в телефон. Жду таких моделей в ближайшие пару лет. Представляете, достаете телефон и вытягиваете из него семидюймовый экран для семейной демонстрации котофоточек.

Но с другой стороны. А так ли плохо таскать с собой три разных устройства вместо одного универсального? Меня лично это пока не напрягает. Напрягает другое. Отсутствие синхронизации.
Я хочу, начав редактирование какого-нибудь документа на ноутбуке, пойти куда-нибудь в переговорку и открыть этот же документ, на том же месте, на планшете. А потом "смахнуть" на проектор, чтобы показать коллегам. Или перечитать на телефоне, сидя в маршрутке. Да, Гуглодрайв подошел к задаче как никогда близко, но не хватает прозрачности. Ну хотя бы так, как синхронизируются открытые вкладки в Хроме. И даже еще более прозрачно. Чтобы я не думал, что нужно сделать, чтобы продолжить работу на другом устройстве. Чтобы работа просто ужа была там.
Если я правильно понял посыл Марка Шаттлворта, именно Каноникал хочет добавить прозрачную унификацию в работе на различных устройствах. Так что с нетерпением жду Убунту на десктопе (собственно, уже пользуюсь), планшете и телефоне. Вдруг хоть мечта о прозрачной синхронизации исполнится?