2015-01-24

О пустом Инбоксе

В GTD существует понятие «пустого инбокса». И я никогда не думал, что папка «Входящие» в GTD имеет что-то общее с папкой «Входящие» в Gmail. А тут Леша Коровянский спрашивает, кто следует подходу zero inbox. И тут я задумался...
Zero Inbox
Собствено, никакой специальной цели обнулить Инбокс в GTD нет. Просто Инбокс во всяких GTDшных софтинах — это папочка, куда падают все неразобранные и некатегоризированные задачи. И GTD настоятельно просит разбирать и категоризировать задачи в любой момент, когда активных задач нет и делать нечего. Но не реже раза в неделю. Получается, что обнулить Инбокс — это разобрать все задачи, точно знать, что делать, и познатьдзен.
Главное — понять, что пришедшие письма — это тоже задачи.
90% писем требуют от меня лишь прочтения. Ну, может, еще одного перехода по ссылке. Все. Gmail автоматически помечает их прочитанными (на всех телефонах и планшетах в том числе). И это меня устраивало.
На 5% писем можно сразу же ответить. Парой предложений. Отправить ответ собеседнику. И дальнейших действий не требуется, пока не ответят с той стороны. Такая переписка могла продолжаться неделями, и висела в Инбоксе.
Остальные 5% писем требуют действия. Чтобы на них ответить, надо что-то сделать. Это «что-то» может занять день, а может занять и пару недель. И вот тут возникает проблема. Как, спустя две недели, найти в куче других писем те самые? Как не забыть, что вот эту задачу ты уже неделю откладываешь, а ведь надо ответить?
Star
Можно пометить такие важные письма звездочкой. Но у меня звездочка используется для другой категории писем: временных «справочных» материалов — всяких паролей да ключей.
Можно пометить не желтой звездочкой, а звездочкой другого цвета или другой закорючкой из тех, что предлагает Gmail. Но это не поможет держать важные письма всегда на виду, звездочки затеряются среди прочего хлама.
Priority Inbox
Можно воспользоваться разделением писем на важные и не важные — фичей, которую Гугль добавил аж в 2010. Но ею до сих пор почти никто не пользуется. Фича прикольная. Примерно тем же способом, как Gmail определяет спам, он определяет, что это письмо для вас будет важно. Например, вам пишет человек из вашей адресной книги. Или это ответ на ваше письмо. Или вы сами пометили похожее письмо, как важное. А всякие рассылки попадают в неважные. А важные письма можно увидеть отдельным важным списком, или в отдельной папочке.
Но это опять не то. Важные письма, они важные всегда. А я хочу выделить важные в контексте сейчас выполняемых задач.
Кстати, есть дальнейшее развитие концепции разделения писем. Gmail автоматически выделяет всякие промоакции, сообщения соцсетей и прочее в разные владочки Инбокса. Только вот моим потребностям эти вкладочки ортогональны.
Labels
Есть еще в Gmail великие и могучие ярлыки. Это такие папочки, что видны в левом столбике интерфейса. Прелесть ярлыков в том, что на одно сообщение их можно навесить несколько. И получается очень гибкая и мощная схема классификации писем. Проекты, работа, не работа и прочее, и прочее.
Я вот как раз пытался помечать те самые важные 5% писем ярлыками вроде «Reply» или «ToDo». Работало плохо. Ибо если с момента пометки прошла неделя, письмо уже безнадежно потерялось. Даже отдельная папочка для просмотра только помеченных писем не очень помогает. Либо забываешь поставить ярлык. Либо забываешь снять ярлык. В результате с одного взгляда не поймешь, то ли уже ответил на письмо, то ли это письмо надо искать вообще где-то в других папках.
Кстати, расстановку ярлыков можно и нужно автоматизировать. Для этого есть фильтры. Любой набор писем, который можно найти в поиске, по тексту в сабже, по имени отправителя, по тексту самого письма, можно и отфильтровать. Это значит, что к новому письму, попадающему под эти критерии, будут применены какие-то действия. Чаще всего навесить ярлык. Или сразу пометить прочтенным.
Удивительно, но у некоторых профессиональных электронных почтменов и почтвуменов встречается даже такая категория писем, которые не должны попадать в Инбокс вообще, должны сразу помечаться прочитанными и должны сразу падать в какую-то папку/ярлык. Ну, например, какие-нибудь отчеты системы автоматической сборки, которые сейчас не очень интересны. Но когда надо, иметь под рукой их надо. И такое тоже делается фильтрами.
Archive
Но вернемся к пустому Инбоксу. Как я это сделал в Gmail.
Кнопка «Архивировать» вам пригодится очень.
Есть такая фича в Gmail — Архивирование. Название не совсем отражает суть. Просто Инбокс — это еще один ярлык. Почти такой же, как и все остальные ярлыки. Архивирование — это удаление ярлыка «Inbox» с цепочки писем. Цепочка остается в папочках других ярлыков. Цепочка остается видимой в папочке «Вся почта». Цепочка по-прежнему доступна для поиска.
Итак, правила такие. Приходит письмо в Инбокс. Прочитали. Что-то делать надо? Нет — жмем «Archive» и удаляем письмо (точнее цепочку писем) из Инбокса. Если что-то нужно делать, оставляем письмо в Инбоксе. Как только ответ готов, отправляем письмо и снова архивируем. Можно даже включить кнопку «Отправить и архивировать», чтобы делать это одним кликом. Если ваш собеседник ответит на ваше письмо, цепочка писем снова окажется в Инбоксе.
Получается, что в Инбоксе остаются только те письма, которые требуют ответа, и ответ еще не был отослан. Это и есть список текущих задач.
После архивирования более шести тысяч писем в Инбоксе, и возврата актуальных задач назад в Инбокс, у меня там оказалось три цепочки писем. Три! Лишь три дела, которые надо не забыть сделать. Ура! И я их всегда прекрасно вижу даже на Gmail виджете в телефоне.
Marks
Архивирование не противоречит использованию ярлыков и фильтров. Можно по-прежнему классифицировать и раскладывать письма по папочкам. Только вот в папочке проекта будет лежать вся переписка по проекту. А актуальные задачи извольте смотреть в Инбоксе.
А вот с важными/неважными и прочими категориями писем пустой Инбокс конфликтует. Этот интерфейс, когда можно отобразить сначала важные письма, а потом все остальные, работает только в Инбоксе. И не работает при отображении других папочек. А чего там разделять по категориям, если там лишь два-три письма?
Реквестирую Гуглу расширить варианты отображения Инбокса на другие папки. Тогда автоматическое разделение на важные и не важные будет иметь смысл и при пустом Инбоксе. В конце концов, это разделение как минимум полезно для настройки уведомлений в телефоне, чтобы он пищал только когда приходит что-то важное. Так что совсем отказываться от этого не хочется.
А пока интерфейс Gmail дорабатывается, с тремя письмами в Инбоксе и безо всяких выкрутасов с приоритетностью писем, можно даже попробовать поюзать такие простные, аскетичные, и просто даже примитивные почтовые клиенты, как Geary. Geary весьма неплохо справляется с «минимумом» пустого Инбокса: кнопкой «Archive» и ярлыками в Gmail. Гораздо лучше, чем тяжелый Thunderbird. И можно парочку сотнемегабатных gmailовых вкладочек в браузере закрыть.
Geary
P.S. Дорофеев говорит именно об этом.
P.P.S. Этот пост не имеет никакого отношения к Inbox by Gmail. Его я еще не тыкал.

2015-01-10

О bcache

Решил я переустановить Убунту. Да. Иногда случается так, что линуксы тоже надо переустанавливать. Причин для этого было у меня две.
Во-первых, пришла пора переходить на 64-битную систему. 32-битная у меня жила и обновлялась (на этом железе) года два. Потому что всякая проприетарная фигня, например для организации митингов, если и существует под Линукс, то только в 32-битном варианте. Ну а теперь оказалось, что и Docker, который пришла пора потыкать, существует только в 64-битном варианте. И Android Emulator в 32-битном варианте объявлен устаревшим.
Ну а последние Убунты, вслед за Дебианом, наконец-то стали multiarch. Каким давным давно являются Редхаты да Центосы. Теперь в системе спокойно могут сосуществовать 64-битные и 32-битные библиотеки и приложения. Значит, можно ставить 64-битную систему и иметь полную поддержку 32-бит. Кстати, это наш Скайп остается 32-битным, и прекрасно работает в 64-битной Убунте (впрочем, чуток подшаманить ради красоты нужно).
Во-вторых, у моего любимого ультрабука очень интересная ситуация с дисками. Есть 24 гига SSD. И есть 500 гигов HDD. В случае Windows все понятно, SSD диск используется как кэш и все хорошо. В случае Убунты два года назад я решил поступить проще: засунуть на SSD систему и свап, а на HDD поместить /home. Такой простейший подход себя не оправдал. Система-то грузится махом. Но вот вход в иксы, да запуск какого-нибудь браузера — не быстры. Совсем не быстры. Ибо нужно читать кучу мелких файлов с HDD.
Самый радикальный способ, конечно, — это выкинуть HDD и воткнуть на его место SSD. Но это сильно дорого получается, чтобы остаться с пятьюстами гигами. К тому же эти 24 гига встроенного SSD все-равно никуда не выкинешь, они на плате распаяны.
В общем, нужен кэш на SSD для кэширования доступа к HDD. В Линуксе для этого есть два с половиной способа (из поддерживаемых Ядром из коробки). Но ни один из них не настраивается из коробки штатными инсталляторами (Убунты). А еще захотелось расстаться с, внезапно, устаревшим MBR. И поиграться с более современным и надежным GPT. Где нет дурацких веселий про четыре основных раздела плюс логические разделы :) И где-то рядом стоит UEFI, который новая майкро молодежная замена BIOS. Но он скорее мешался, чем что-то дал.
Ubuntu
Итак, кэши.
Самый старый — dm-cache. В составе Ядра с версии 3.9, с апреля 2013. Является модулем device mapper, т.е. довольно штатным способом расширения функционала Ядра. В dm-cache пугает сложность настройки: кроме самого диска, который кэшируем, нужно еще два отдельных устройства для самого кэша и метаданных кэша. Причем размер метаданных нужно еще заранее правильно подсчитать.
Для любителей LVM dm-cache впилили в него. Возможно, это самый правильный способ нарисовать нужный мне кэш. Но я не хотел заморачиваться еще и с LVM. Все же это ноутбук, и шанс добавить в систему новый диск с тем, чтобы расширить имеющиеся разделы на него, для чего, в моем понимании, и нужен LVM, стремится к нулю. А если уж менять диск, то от наличия LVM профита нет.
И, наконец, есть bcache. Он вошел в Ядро версии 3.10, в июле 2013. Его проще настраивать: нет нужды в отдельном разделе для метаданных. Он точно умеет кэшировать несколько HDD в один SSD, а именно такого мне хотелось: иметь один кэш для двух разделов — корня ФС и /home.
Кто быстрее, dm-cache, bcache или их еще не вошедшие в Ядро аналоги вроде Flashcache и EnhanceIO — непонятно. Каждый тянет одеяло на себя. Но bcache вроде более явно высказывается об особенностях своих алгоритмов и бережном отношении к SSD.
В общем, решил попробовать поднять bcache. И, если заведется, на нем и остановиться.
У bcache есть не очень приятная багофича. Его нельзя поднять поверх существующих разделов. Он требует явного переформатирования разделов с потерей содержащихся на них данных. Он создает свой суперблок. Делается это для того, чтобы нельзя было получить доступ к этим разделам, минуя bcache. Чтобы избежать возможного нарушения целостности кэша. Ничего особо страшного тут нет, ибо bcache уже есть в ядре, а работоспособность он сохраняет и при отсутствии доступа к собственно кэшу, тупо переходит в режим прямой работы с кэшируемым устройством.
Так что пришлось два дня аккуратно упаковывать родной /home в стопочку архивов и складывать на внешний диск. Оказалось, несколько десятков гигабайт бесценного барахла накопилось. Самая сложная часть операции :)
Есть, конечно, способ сконвертировать раздел в bcache. Но не так-то просто оказалось эти скрипты запустить в Убунте 14.04, давно они не обновлялись. К тому же тут нет никаких гарантий, а значит, бэкапы все равно нужны.
А еще GRUB не умеет bcache, поэтому нужно создавать отдельный раздел для /boot. И вообще, установщики ОС тоже не умеют bcache. Поэтому пришлось сделать финт ушами. Сначала поставить Убунту в раздел, который потом станет свапом. Затем в установленной системе настроить bcache и перенести систему (и /home) на bcache разделы. И напоследок, после второй перезагрузки, засунуть свап в тот самый раздел, где он должен быть.
С загрузкой пришлось дополнительно помучаться. С одной стороны, я собрался делать GPT разметку дисков. С другой стороны, у нас имеется два варианта загрузки: классический BIOS и новый UEFI. Тестировал я установку bcache в VirtualBox, где поддержка UEFI экспериментальна и не сработала. Поэтому пришлось грузиться в GPT раздел из BIOS. GRUBу в этом случае нужен дополнительный раздел с флагом bios_grub, куда он помещает свое тельце. (В случае MBR он размещается в первых секторах, которые по стандарту вроде как не заняты, а в GPT такой роскоши нет). Однако ультрабук отказался грузить GPT диск в BIOS режиме. Пришлось грузиться в UEFI. GRUBу тогда нужен fat32(!) раздел для EFI. Получилась кучка вспомогательных разделов, которые я разместил на SSD, но также создал их и на HDD, чтобы в случае извлечения этого HDD из ультрабука, его тоже можно было бы сделать потом загрузочным.
Select boot device
Итак, бэкапы мы сделали. Теперь берем загрузочную флешку с Ubuntu 14.04 LTS 64bit и грузимся с нее в режиме UEFI. Инсталлятор пока не запускаем, а балуемся с GParted. Создаем на каждом диске новую GPT таблицу разделов. Как-то так:
  • sdb, SSD диск
    • sdb1, BIOS boot раздел, 1MB, без файловой системы, поставить флаг bios_grub
    • sdb2, UEFI boot раздел, 200MB, fat32, поставить флаг boot
    • sdb3, /boot, 250MB, ext4
    • sdb4, будет свап, но сначала сюда поставим систему, 6GB, отформатировать в ext4
    • sdb5, кэш bcache, оставшиеся 17GB SSD диска, без файловой системы
  • sda, HDD диск
    • sda1, BIOS boot раздел, 1MB, без файловой системы, резерв на случай изъятия диска
    • sda2, UEFI boot раздел, 200MB, fat32, не форматировать, резерв на случай изъятия диска
    • sda3, резерв для /boot, 250 MB, не форматировать, резерв на случай изъятия диска
    • sda4, /, 20GB, ext4, сейчас оставить без форматирования
    • sda5, /home, оставшиеся 478GB HDD диска, ext4, сейчас оставить без форматирования
Ставим Убунту в тот самый раздел, который потом сделаем свапом.
  • sdb3 — /boot
  • sdb4 — /
Загрузчик поставить в sdb. Проигнорировать предупреждения об отсутствии свапа и наличия файловой системы в /boot.
Перезагружаемся, работаем в установленной системе (которая сейчас занимает один раздел на SSD диске).
Ставим bcache-tools (далее все команды — из-под рута):
# add-apt-repository ppa:g2p/storage
# apt-get update
# apt-get install bcache-tools
Создать bcache не просто, а очень просто:
# make-bcache -C /dev/sdb5 -B /dev/sda4 /dev/sda5
Флагом -C указывается устройство-кэш. Флагом -B указываются кэшируемые (backed) устройства. Если все сделать одной командой, то сразу получится то, что нам нужно: два раздела на HDD и один кэш для них на SSD.
Если на этих разделах имеется какая-то файловая система (ну например, вы все же отформатировали что-то лишнее при разметке диска), make-bcache не будет её удалять. Придется почистить как-то так: wipefs -a /dev/sda4. А потом связывать устройства-кэши с кэшируемыми устройствами, как описано в документации. Главное тут понять, что на каждое кэшируемое устройство (раздел HDD), появляется свое устройство /dev/bcacheX. И, так называемый CSET-UUID кэша нужно приаттачить к каждому из них. Как-то так:
# ls /sys/fs/bcache/
4ed92e55-683a-49b2-8043-39d5bde433f9  register  register_quiet
# echo 4ed92e55-683a-49b2-8043-39d5bde433f9 > /sys/block/bcache0/bcache/attach
# echo 4ed92e55-683a-49b2-8043-39d5bde433f9 > /sys/block/bcache1/bcache/attach
Создаем файловые системы:
# mkfs.ext4 /dev/bcache0
# mkfs.ext4 /dev/bcache1
Копируем /home и / на новые места на bcache устройствах. Тут главное не запутаться с номерами этих устройств. Порядок нумерации может быть произвольным (но постоянным). Смотрите на размер устройств, или же смотрите, например, ls /sys/block/bcache0/slaves.
# mkdir OLD NEW HOME
# mount /dev/sdb4 OLD
# mount /dev/bcache0 NEW      #должен быть новый кэшированный рут
# rsync -a OLD/ NEW/
# mount /dev/bcache1 HOME
# rsync -a /home/ HOME/
Готовимся обновлять GRUB:
# mount /dev/sdb3 NEW/boot
# mount /dev/sdb2 NEW/boot/efi
# mount -o bind /dev NEW/dev
# mount -t proc none NEW/proc
# mount -t sysfs none NEW/sys
# chroot NEW
Нужно найти UUIDы наших bcache устройств, а также UUID старого корня (который станет свапом):
# ls -l /dev/disk/by-uuid/ | grep bcache
lrwxrwxrwx 1 root root 13 Jan  5 21:48 6538fd23-8f53-4651-adc7-43c494e75f67 -> ../../bcache1
lrwxrwxrwx 1 root root 13 Jan  5 21:48 dfc55722-a89c-4026-b1d2-758ec336017a -> ../../bcache0
# ls -l /dev/disk/by-uuid/ | grep sdb4
lrwxrwxrwx 1 root root 10 Jan  5 21:48 b0b3a280-329e-42a9-91fd-09fde7e8977f -> ../../sdb4
Любимым текстовым редактором правим /etc/fstab:
  • меняем UUID корневого раздела, со старого sdb4 на новый bcache0 (вот для чего мы их находили)
  • добавляем запись для /home
Получается что-то вроде:
UUID=dfc55722-a89c-4026-b1d2-758ec336017a /               ext4    errors=remount-ro 0       1
UUID=6538fd23-8f53-4651-adc7-43c494e75f67 /home           ext4    defaults          0       1
Любимым текстовым редактором правим /boot/grub/grub.cfg. Нужно тщательно и аккуратно заменить вхождения UUID старого корня (на sdb4) на UUID нового корня (на bcache0).
Ставим GRUB:
# grub-install /dev/sdb
Если все ок, то все ок. Перезагружаемся. Превращаем старый рут в свап:
# mkswap /dev/sdb4
# swapon /dev/sdb4
И добавляем свап в /etc/fstab:
UUID=b0b3a280-329e-42a9-91fd-09fde7e8977f none            swap    defaults        0       0
Ну и все. Можно восстанавливать бэкапы взад.
Проследить за работой bcache можно через sysfs:
% cat /sys/block/bcache*/bcache/state
clean
clean
% ls /sys/block/bcache0/bcache/stats* -d -1
/sys/block/bcache0/bcache/stats_day
/sys/block/bcache0/bcache/stats_five_minute
/sys/block/bcache0/bcache/stats_hour
/sys/block/bcache0/bcache/stats_total
% ls /sys/block/bcache0/bcache/stats_day/ -1
bypassed
cache_bypass_hits
cache_bypass_misses
cache_hit_ratio
cache_hits
cache_miss_collisions
cache_misses
cache_readaheads
% cat /sys/block/bcache*/bcache/stats_day/cache_hit_ratio
78
68
Если, кроме оптимизации чтения, хочется покэшировать и запись, можно поиграть с режимами:
% cat /sys/block/bcache0/bcache/cache_mode
[writethrough] writeback writearound none
Итак, раньше на SSD был корень, а /home был на HDD. Теперь и то и другое — на HDD, но кэшировано через SSD. Причем кэшируются только операции чтения, а запись идет сразу и в кэш и на жесткий (writethrough). По субъективным ощущениям система грузится с прежней скоростью, а вот иксы, браузер, Идея стартуют быстрее. Пакеты ставятся медленнее (потому что запись в корень стала медленнее). Прошла неделя, полет нормальный.
HDD+SSD
P.S. А еще есть btier.

2015-01-04

О NewSQL

Вот авторский вариант статьи про NewSQL, написанной вместе с Гришей Косьяненко, и опубликованной в августовском номере журнала «Хакер».

Что нового в NewSQL?

Погружение в новейшие базы данных.
Кроме SQL и NoSQL существует еще целый мир NewSQL. Это базы данных, которые взяли новые подходы распределенных систем от NoSQL и оставили реляционную модель представления данных и язык запросов SQL. Эти БД возникли буквально за последние несколько лет, но уже интенсивно борются за пользователей, оттесняя на рынке и старые добрые SQL БД, и чуть менее старые NoSQL. Давайте познакомимся с этим загадочным NewSQL поближе.

Классификация

Говоря о NewSQL базах, мы имеем в виду базы, удовлетворяющие нескольким критериям:
  • поддержка реляционной модели и транзакционности
  • SQL как основной интерфейс доступа к данным
  • горизонтальная масштабируемость
  • совершенно новый движок, не унаследованный от классических СУБД
  • появились в последние 3-5 лет
Можно выделить несколько классов решений в области хранения и обработки данных, относящихся к NewSQL.
Во-первых, к NewSQL причисляют способы использования обычных SQL баз в кластере из нескольких физических узлов для хранения и обработки больших объемов данных. Сюда можно отнести MySQL Cluster, Postgres-XC, Oracle RAC и прочие. Все промышленные СУБД пытаются собраться в кластер. Все эти решения представляют собой промежуточный слой (middleware), который распределяет запросы между узлами, хранящими данные (обычными БД) и объединяет результаты, собственно, осуществляет шардинг. Как правило, на запросы накладываются сильные ограничения на использование внешних ключей и джойнов. Мы не будем рассматривать эти системы здесь, хотя это интересная тема для дальнейшего исследования.
Во-вторых, NewSQL — это новые движки хранения данных для существующих БД. Самый популярный тут — TokuDB — движок для MySQL. Он использует индексы на так называемых фрактальных деревьях. Эти индексы значительно быстрее B-деревьев в случаях, когда индексы не помещаются в оперативной памяти и их приходится читать с диска. (Кстати, эти фрактальные индексы прикрутили и к MongoDB, в одном неофициальном форке). Так как основная особенность новых движков — тонкие отличия в производительности в некоторых экзотических случаях, мы исключаем их из этого обзора. Нас в первую очередь интересует функционал.
В-третьих, NewSQL — это совершенно новые СУБД, которые поддерживают SQL. Тут мы остановимся подробнее.
Многие NewSQL БД — это in-memory БД. Они хранят все данные в оперативной памяти. Создатели этих БД считают фатальным недостатком существующих БД стремление все хранить на диске. Если вам нужно обрабатывать данные быстро, вам нужно хранить их в ОЗУ. А относительно небольшой объем ОЗУ на одной машине можно легко компенсировать, собрав кластер из сотен узлов. Хранение в ОЗУ не означает, что данные будут потеряны при выключении питания. Все эти БД ведут журнал операций и периодически скидывают снимки данных на диск. Мы рассмотрим VoltDB и MemSQL.
Другие разработчики NewSQL борются со сложностью развертывания и управления кластерной СУБД в современных виртуальных и облачных средах. Создаются красивые веб-консоли для управления и мониторинга. Рассмотрим в этом качестве NuoDB.
Третьи разработчики NewSQL замахиваются на создание универсальных платформ для хранения, представления и обработки структурированных данных, в виде реляционной модели, но не только. Здесь мы познакомимся с замечательным FoundationDB.
Итак, чтобы новая СУБД в полной мере могла называться NewSQL-решением, она должна быть SQL и NoSQL одновременно.

VoltDB

VoltDB — это in-memory СУБД, написанная на Java. Это самая старая БД в нашем обзоре, первый публичный релиз состоялся аж в мае 2010 года.
VoltDB также единственная СУБД в обзоре, полностью доступная под свободной лицензией (AGPL). Есть и проприетарная Enterprise версия, в которую добавлены плюшки вроде VoltDB Web Studio — «среды разработки» в виде веб-приложения.
В отличие от других СУБД, где на одном сервере вы можете создать несколько баз данных, в VoltDB один процесс обслуживает одну базу. Точнее множество процессов на множестве хостов кластера обслуживают одну базу, и других баз в этом кластере нет. Каждый процесс — однопоточен, асинхронен, имеет свой каталог для хранения снапшотов, работает на одном CPU и называется партицией (partition). Соответственно, на каждом хосте кластера нужно запускать несколько процессов, согласно числу ядер CPU.
Сама БД описывается в виде каталога. В этом каталоге находится описание кластера: сколько узлов, сколько процессов на узле, количество копий данных (тут оно называется K-factor). Еще тут есть полное описание схемы БД, прямо в файле ddl.sql, в виде CREATE TABLE, ALTER TABLE и прочих конструкций. А еще есть исходные тексты хранимых процедур на Java (да, в VoltDB хранимые процедуры — только на Java). Весь этот каталог компилируется (!) в один jar архив. И при запуске процесса СУБД нужно указать этот jar файл. Это больше похоже на какой-нибудь J2EE сервер приложений, чем на СУБД.
При компиляции для каждого запроса в хранимых процедурах строится план выполнения. Потом этот план не меняется. Можно дать оптимизатору подсказки о количестве записей в таблицах, но в целом, статичный план выполнения запроса, не использующий актуальную статистику, выглядит анахронизмом.
Запустив процессы на всех узлах кластера, можно подключаться к любому узлу используя вольтдибишный JDBC драйвер. Есть клиентские библиотеки для других ЯП. А можно делать запросы к хранимым процедурам через простенький HTTP интерфейс.
Хранимые процедуры очень важны в VoltDB. Это единица транзакции, вызов каждой хранимой процедуры представляет собой транзакцию, и других транзакций нет. То есть весь набор доступных транзакционных действий мы определяем на этапе компиляции каталога БД. Хранимые процедуры являются частью сервера (вкомпилированы в сервер). «Пятью девятками» тут даже не пахнет: хочешь новую функцию — перекомпилируйся и перезапусти базу. Вся оптимизация вертится вокруг хранимых процедур. Можно, конечно, делать и произвольные SQL запросы за пределами процедур (тут они называютя ad-hoc запросы), но они будут работать медленнее и транзакционность их не гарантируется.
Мультиверсионности нет, блокировок тоже, поскольку на партицию работает только один процесс и конкурировать ему не с кем. С одной стороны пропускная способность действительно возрастает, и действительно уровень изоляции транзакций serialized. Хотя сложно говорить об уровне изоляции при фактически однопользовательской работе. Если запрос затрагивает несколько партиций, то это будет единственный запрос, выполняющийся в базе в этот момент.
При таком подходе даже неудобно упоминать о разделении прав доступа к данным. Их тоже нет.
Вспомним про такое свойство транзакций, как durability: здесь оно реализовано через журнал транзакций и периодические снапшоты. Журнал хранится только со времени последнего снапшота, поэтому обращение к историческим данным невозможно. Надежность хранения достигается за счет резервирования данных на нескольких узлах кластера. Распределенные транзакции (да, они здесь возможны) фиксируются двухфазным коммитом.
Что, собственно, нового? Эта SQL БД работает в кластере. И поэтому у нас есть два типа таблиц: партицированные (partitioned) и реплицированные (replicated).
С реплицированными таблицами (такие создаются по умолчанию, обычным CREATE TABLE) все просто — они целиком копируются на все узлы кластера. Это удобно для небольших таблиц-справочников, с которым часто делаются джойны и которые редко меняются.
Партицированные таблицы шардируются между узлами кластера (точнее между процессами-партициями). Шардируются по хэшу ключа. Ключом может быть любой столбец таблицы с целочисленным или строковым значение (но не null). Ключ у таблицы может быть только один. В общем, таблица размазывается ровным слоем по всему кластеру (учитывая, конечно, K-factor).

Партицированные таблицы в VoltDB
Соответственно, выборка множества строк из партицированной таблицы — не дешевое удовольствие, к тому же не гарантируется порядок выборки в случае отсутствия явной сортировки. При этом строки с разных узлов будут выданы в том порядке, в каком узлы ответили.
Обычно запрос из процедур отправляется на все узлы кластера, которые могут содержать интересующие нас данные, а затем агрегируются. Однако, если процедура оперирует данными по одному ключу, и все задействованные таблицы партицированы по этому же ключу, то вся процедура может быть выполнена на одном узле. Это, конечно же, намного эффективнее. Таким образом, при проектировании БД, нужно тщательно подумать о том, какие данные будут использоваться вместе, и партицировать их по одному и тому же ключу.
Помните, что любое изменение структуры данных требует перекомпиляции каталога БД, а для некоторых (к примеру, добавление уникального индекса) требуется полный перезапуск базы. Такой подход к управлению структурой — ну совсем не гибкий.
Кроме того, поддерживать нормализованную реляционную схему для более-менее связных данных не представляется возможным: запросы ограничены по сложности, джойнить шардированные таблицы можно только по ключу шардинга и только на равенство. В update и delete подзапросы вообще запрещены. Кроме того, есть ограничения на размер резалт-сета — не более 50 мегабайт.
В добавок, VoltDB не поддерживает внешние ключи и check constraint, а уникальность записей для unique constraint обеспечивает только в пределах партиции. При этом заявляется, что СУБД поддерживает реляционную модель. Кодд переворачивается в гробу.

MemSQL

MemSQL очень похожа на VoltDB. Тоже in-memory СУБД, только написана на C++. Первый публичный релиз состоялся в июне 2012 года.
У MemSQL имеется только проприетарная версия. Бесплатных версий нет, но есть триалка.
В MemSQL узлы кластера неравнозначны. Здесь есть агрегаторы и листы (leaf). Агрегаторы принимают запросы от пользователей, модифицируют и рассылают запросы листам, агрегируют результаты. Есть главный агрегатор, который отвечает за создание БД и таблиц, и целостность кластера в целом. Листы непосредственно хранят данные и выполняют (частичные) запросы. При падении мастер-агрегатора кластер остается работоспособным, хотя DDL-запросы выполнять уже нельзя. Автоматически новый мастер-агрегатор не назначается.
В отличие от VoltDB, в кластере можно держать несколько баз данных. При создании базы сразу создается определенное число партишенов (по умолчанию — по восемь на лист). Любопытно, что партишены на листах представлены отдельными базами данных, только с числовыми суффиксами. Например, если вы создали в кластере БД под именем “test”, то на листах будут созданы БД-партиции “test_1”, “test_2” и т.д.
Все коммуникации, как между клиентами и агрегаторами, так и между агрегаторами и листами, происходят по протоколу MySQL. Да, все клиенты, драйверы и библиотеки, которые работают с MySQL, будут работать с MemSQL. Если только вы не будете углубляться в хитрости MySQL, которые не поддерживаются MemSQL (например, не пытайтесь создать таблицу InnoDB, такого движка в MemSQL нет).
В MemSQL, как и в VoltDB, присутствует два типа таблиц: справочные (reference) таблицы, которые копируются на все узлы кластера (включая агрегаторы), и шардированные (sharded) таблицы, которые размазываются по партициям по хэшу ключа. Ключом шардирования может быть любой столбец таблицы, только этот столбец должен входить в первичный ключ этой таблицы. И в уникальный индекс, если такой понадобится. И во внешний ключ, конечно, тоже. Так в MemSQL решили проблему связности данных, на которую в VoltDB просто закрыли глаза.

Распределение ролей и данных в кластере MemSQL
MemSQL может определить, что запрос затрагивает только одну партицию, и оптимизирует его, отсылая только на один лист. Все запросы на изменение данных могут выполняться только на узлах агрегаторах, вне зависимости от того, сколько партиций затрагивает запрос. Кроме того явно запрещены любые манипуляции с первичным ключом (для защиты от изменения ключа шардинга).
Ключевой особенностью MemSQL является компиляция запросов. Любой SQL запрос (все DML и некоторые DDL) превращается в код на C++ (SELECT * FROM TEST превращается в две сотни строк чего-то нечитаемого). Этот код компилируется обычным GCC в разделяемую библиотеку, которая подключается к серверу.
Код — параметризованный, все значения, встречающиеся в WHERE секции запроса, являются параметрами. В дальнейшем все подобные запросы (с тем же набором параметров) уже выполняются в нативном коде. Проявляется это в том, что первый запрос выполняется десятки секунд, а последующие — со вполне нормальной скоростью. Пока не изменится структура базы. После этого все затронутые изменениями запросы будут перекомпилированы заново.
Кеширование планов запросов само по себе неплохо, если при этом следить за актуальностью статистики и структуры данных. Статистика и селективонсть выборок в MemSQL игнорируются. Для запроса можно посмотреть план выполнения, но там мало информации для оптимизации. Это скорее декларация решения оптимизатора.
В отличие от VoltDB, MemSQL не накладывает ограничений на сложность запросов и количество участвующих в них партицированных таблиц, но все промежуточные результаты хранит во временных таблицах на агрегаторе и честно предупреждает о возможности отказа из-за нехватки памяти на больших запросах.
Для репликации MemSQL умеет создавать только две копии данных: основную и резервную. При этом резервная копия не участвует в операциях и должна быть создана на отдельном листе. Если хотите включить резервирование, вам нужно в два раза больше листов в кластере. Не очень удобно.
Никаких хранимых процедур в MemSQL нет: база не поддерживает даже триггеры и представления. Ну, если не считать скомпилированные запросы за процедуры :)
Хотя MemSQL декларирует поддержку ACID транзакций, фактически каждый запрос выполняется в отдельной транзакции. А для некоторых изощренных случаев, таких как ошибка на вставке нескольких записей перечислением значений, может закоммититься только часть измененных записей. Вот такая атомарность.
Уровень изоляции read committed. Но разработчики признают, что при изменении данных на нескольких партициях возможно грязное чтение. Вот такая изолированность.
Надежность фиксации данных, как и в VoltDB, обеспечивается снапшотами и журналом транзакций. Да вот беда, по умолчанию журнал пишется на диск асинхронно.
Недостаточно кисло?
Не смотря на то, что MemSQL заявляется как lock-free база, блокировки используются, и на это явно указывается в документации, например, удаление индекса блокирует операции изменения данных. И да, есть команда для установки явной блокировки на таблицу.
Реверансом в сторону классических баз является разделение прав доступа. База поддерживает многопользовательскую работу. Набор привилегий достойный, но выдавать на отдельные объекты базы их нельзя.

NuoDB

Создатели NuoDB решили сделать развертывание кластера в облаке приятным и необременительным занятием. Кажется, удалось это им не очень. Зато уровень рекламного булшита в документации зашкаливает.
Это проприетарная СУБД, написанная премущественно на Java. Первый релиз состоялся в январе 2013. Есть бесплатные, но сильно ограниченные в функциональности версии.
Начнем с типов узлов, точнее типов процессов, которые запускаются в кластере (в терминологии NuoDB — домене). Есть один брокер — основной процесс, который собирает домен в единое целое. К нему клиент подключается в первую очередь и узнает о конфигурации домена. На каждом узле запущены агенты, которые общаются с брокером и докладывают о состоянии узлов.
База данных создается через брокер. В нее входит один или более движков транзакций (Transaction Engine) и один или более менеджеров хранения (Storage Manager). К первому подключаются клиенты для выполнения запросов, второй отвечает за хранение данных. При создании БД задается, сколько именно должно быть создано движков и менеджеров, и на каких именно узлах домена они должны быть расположены.

Распределение ролей в кластере NuoDB
Все это можно настроить через довольно удобную веб-админку, с красивым логотипом в виде зеленой птички. Однако на практике не все так просто. Это единственная БД в обзоре, которой понадобился корректно работающий DNS (или правильные /etc/hosts). Узел идентифицирует себя по доменному имени, другие узлы должны иметь возможность подключиться к нему по этому имени.
Внезапно, шардинга здесь нет. Хоть репликация есть, и на том спасибо. Все данные в БД дублируются на все менеджеры хранения данной БД. Утверждается, что БД остается частично работоспособной при падениях брокера, агента и даже движков транзакций и менеджера хранения. В качестве разнообразия можно настроить хранение данных в хадуповой HDFS (ну хоть тут появляется какой-то шардинг).
Никакой совместимости на уровне протокола нет, JDBC драйвер — свой уникальный. Было бы понятно, если бы это была какая-то консолька по развертыванию кластера обычных СУБД (вроде Amazon RDS). А так, в чем смысл существования NuoDB?
Не смотря ни на что, в NuoDB нормальная реализация транзакций: база поддерживает уровни изоляции от read committed до serializable (по умолчанию repeatable read), обеспечивает атомарность многопроцедурных транзакций.
Нет шардированных таблиц — нет проблем с ограничениями уникальности и ссылочной целостности. Присутствуют все привычные для реляционных баз констрейнты.
В целом, все как у людей — разграничение прав доступа, оптимизатор запросов на основе статистики, B-tree индексы. Обычный SQL двадцатилетней давности. Непонятно,что здесь нового.

FoundationDB

FoundationDB — самый молодой представитель нашего обзора. Первый публичный релиз состоялся в мае 2013 года.
Ребята из FoundationDB пошли совершенно уникальным путем. Они решили, что в СУБД механизм хранения данных, модель представления данных и язык запросов должны быть независимы. Например, если вы берете PostgreSQL, то вы вынуждены использовать его MVCC движок хранения, реляционную модель данных и SQL как язык запросов (впрочем, в MySQL движок хранения можно выбирать). Еще они сказали, что транзакции — это круто, и создали отдельную, универсальную, надежную, распределенную технологию хранения структурированных данных.
FoundationDB — это key-value хранилище, с упорядоченными ключами, с поддержкой ACID транзакций (в операциях на множестве ключей!), которое развертывается на кластере равнозначных узлов. Авторам неизвестны аналогичные NoSQL решения. Транзакции на key-value поддерживаются в BerkeleyDB, но это вообще-то встраиваемая СУБД. Упорядоченность ключей есть в HBase (при особых настройках и в Cassandra), но тут никто не говорит о транзакциях. Lightweight транзакции не в счет. Получается уникальное и мощное сочетание.
А поверх key-value находятся слои (так и называются: Layers), которые представляют более высокоуровневую модель хранения и языки запросов. Самим FoundationDB разрабатывается SQL Layer, раньше он был отдельным проектом под названием Akiban. Есть слои для хранения документов и графов.
Key-value хранилище — проприетарный продукт. Есть ограниченная бесплатная версия. А вот SQL Layer и многие другие слои — уже свободное ПО, под AGPL.
Хранилище написано на C++. Процессы однопоточны, асинхронны, работают на одном ядре CPU и обслуживают каждый свою порцию данных. На каждом узле кластера нужно запускать несколько процессов, согласно числу CPU, что делается вполне прозрачно. Все узлы кластера равнозначны, однако некоторые (требуется нечетное число) выполняют роль координаторов. Координаторы отвечают за поддержку топологии кластера для репликации данных, определение доступности сегмента кластера в случае разделения.
Репликация прозрачна. Кластер может хранить до трех копий данных. Учитывается распределение узлов между датацентрами.
Шардинг ключей осуществляется по диапазонам (примерно как в MongoDB), что требует постоянной перебалансировки кластера, которая проходит тихо и незаметно. Наличие и объем фоновой балансировки — важный параметр кластера. Он виден в выводе стандартной команды статуса.
Сетевой протокол и API — свои собственные. Поддерживаются операции записи и чтения по ключу, сканирования диапазона (помним, ключи упорядочены), начало и завершения транзакций.
Имеется два способа хранения данных. Либо B-деревья хранятся прямо в файлах на диске, что хорошо работает только на SSD. Либо копия данных живет в ОЗУ, а на диск пишутся логи и снапшоты.

Слои в FoundationDB
SQL Layer написан на Java. Это совершенно отдельное сетевое приложение. Клиенты подключаются к SQL Layer по сети, SQL Layer подключается к key-value хранилищу тоже по сети. Это увеличивает время обработки запроса, но разработчики и не утверждают, что latency у них лучше всех.
Слой SQL не хранит состояния, все данные записываются в хранилище, поэтому размещать этот процесс можно где угодно и в любом количестве. Разработчики рекомендуют запускать SQL Layer на каждом клиентском узле (на бэкенде вашего веб-приложения, например). Но можно разворачивать и на узлах с хранилищем, лишь бы памяти хватило.
C SQL Layer можно общаться по протоколу PostgreSQL, совместимость почти полная. Пожалуй, существенное отличие только в отсутствии pg_catalog и PL/pgSQL. Однако есть information_schema (все же в первую очередь разработчики руководствовались ANSI SQL). А хранимки можно писать на Java или JavaScript. Также доступен полноценный REST для оперирования группами таблиц.
У key-value хранилища имеется ряд интересных ограничений. Размер ключа — не более 10 килобайт. Размер значения — не более 100 килобайт. Длительность транзакции — не более 5 секунд. Одна транзакция может изменить не более 10 мегабайт данных.
Вообще, к транзакциям у FoundationDB отношение трепетное. Первым пунктом в white paper идет ода транзакциям: они удобные, понятные, полезные и не такие уж дорогие. После стольких грубых слов, сказанных о транзакциях разработчиками NoSQL решений, на глаза наворачивается скупая мужская слеза.
Транзакции честные. Атомарность распространяется на несколько операторов, укладывающихся в указанные выше ограничения по времени и объему транзакции.
Изоляция обеспечивается мультиверсионностью представления данных, кроме того, если две транзакции конфликтуют (например, одна изменяет те же ключи, которые читает другая), то более поздняя завершается с ошибкой. На клиенте рекомендуется повторять транзакции до достижения успеха. Зато обеспечивается уровень изоляции serialized для многопользовательского доступа. Есть оператор явного начала транзакции.
SQL Layer обеспечивает работу с реляционной моделью данных. Весь набор ограничений целостности доступен, включая опцию deferrable constraint.
SQL Layer расширяет реляционную модель группами таблиц. Цель та же, которой руководствовались создатели документо-ориентированных NoSQL — ускорение доступа к связанным данным. Вы находите в схеме ваших данных тесно связанные таблицы (типичный пример: заказ и товары в нем) и объединяете их с помощью специального внешнего ключа в группу таблиц. Не в документ, а в группу таблиц. В одну группу входят иерархически связанные таблицы, так же как в документ объединяются иерархически подчиненные данные.
Группа таблиц хранится целиком, все ключи в группе образуют непрерывный диапазон. (Например, после строки-заказа, сохраняются строки-товары этого заказа, затем следующая строка заказа и т.д.). Это значит, что с большой вероятностью группа таблиц хранится на одном узле кластера и все операции над ней могут быть выполнены на этом узле. Поэтому джойны внутри группы — более эффективны. Тем не менее таблицы внутри группы остаются полноценными SQL таблицами, их можно спокойно джойнить с таблицами из других групп. Жаль, что нет возможности делать реплицируемые табицы-справочники, поскольку добавлять их в группы не всегда возможно (в корне дерева таблиц может быть другой объект), а джойнить данные с разных партиций — дорого.

Порядок хранения записей группы таблиц в SQL Layer
Огромный плюс групп таблиц перед документами — иерархию подчинения можно поменять, просто переставив внешние ключи, несколькими ALTER TABLE. SQL слой сам озаботится пересозданием ключей и перераспределением данных. В документо-ориентированных NoSQL для этого приходится фактически перезаписать добрую половину базы, да обновить всех клиентов.
Кроме обычных B-tree индексов FoundationDB предоставляет spatial-индексы. Причем, в отличие от большинства СУБД (Oracle, PostgreSQL, MySQL), это не R-tree, а z-order индексы. Объясняется этот выбор тем, что под SQL слоем лежит key-value хранилище.
В стадии альфы находятся индексы для полнотекстового поиска на движке Lucene.
Кроме того FoundationDB позволяет делать индексы на группу таблиц. Аналогом в традиционных базах могут служить materialized view для Oracle и PostgreSQL или join-indexes для Teradata.
Кроме разнообразных индексов в FoundationDB есть продвинутый оптимизатор, который умеет не только сочетать несколько различных индексов для одной таблицы/группы таблиц, но и использовать покрывающие индексы (не обращаться к таблице, если всю необходимую информацию можно извлечь из индекса). Оптимизатор перестраивает план выполнения запроса при устаревании статистики, в отличие от всех рассмотренных выше СУБД. Статистика собирается автоматически, но может быть обновлена и вручную соответствующей командой ALTER TABLE … UPDATE STATISTICS. Сам план не содержит всей желаемой информации (ожидаемая мощность выборки, время CPU), но позволяет понять, что же происходит.
Соместимость с ANSI SQL потрясающая даже для реляционной СУБД. Синтаксис не только соответствует стандарту даже в мелочах (GROUP BY и RETURNING), но и имеет свои специфические расширения. К примеру, во внешнем ключе достаточно указать на какую таблицу идет ссылка, столбцы указывать не обязательно. Есть возможность получать строки результирующей выборки в JSON, достаточно написать SELECT ** FROM … (еще один реверанс в сторону документов). В индексе по табличной группе можно указывать порядок соединения таблиц.
Нет явного ограничения на сложность запроса и подзапросов: их можно использовать как в секциях FROM и WHERE, так и в SELECT и SET.
Апофеозом стало то, что фрагмент кода живого проекта на PostgreSQL (около тысячи строк), выбранный для тестирования особенностей диалекта, выполнился без единой синтаксической ошибки.

Заключение

Как видим, вариантов горизонтального масштабирования реляционной модели на кластер не так уж и много. Весьма популярно шардирование по хэшу ключа. Но это вносит заметные ограничения на эффективные джойны. Фактически, с тем же удобством, что и на одном старом добром SQL сервере, можно оперировать данными только в одной партиции. А в данном случае это данные, шардированные по одному-единственному значению ключа.
Гораздо интереснее смотрится подход FoundationDB/Akiban. Все-таки объединять таблицы в группы-документы — это гораздо естественнее, чем использовать для их хранения специальные типы колонок (например, JSON) внутри таблиц, как делают классические СУБД. Операции уже внутри этого агрегата становятся более эффективными, как в документо-ориентированных NoSQL, а самое приятное — состав агрегата можно менять.
Радует, что СУБД переходят на однопоточную асинхронную (событийную, акторную,...) архитектуру. Ту самую, которая может быть вам известна по Node.js или Erlang. Один поток, занимающий одно ядро CPU может обслуживать в таком варианте десятки тысяч одновременных запросов. Это действительно помогает существенно повысить throughput, т.е. количество обрабатываемых запросов в единицу времени.
Печалит, что почти все новейшие СУБД — проприетарные. Остается только надеяться, что это болезнь роста, и вскоре мы увидим их и под свободными лицензиями. Вообще, вырисовывается такая хронология БДшного стартапа: найти фатальный недостаток в существующих решениях, родить гениальную идею по преодолению этого недостатка, собрать с десяток единомышленников и докторов наук, два года тихо и незаметно пилить что-то гениальное (можно по ходу дела сменить идею), громко анонсироваться под проприетарной лицензией, развернуть широкую маркетинговую компанию, привлечь крупных клиентов, два года наращивать клиетскую базу, выпуститься под свободной лицензией и начать растить сообщество разработчиков.
Похоже, что рынок NewSQL еще более перегрет, чем NoSQL. Множество решений конкурируют друг с другом. Каждый обещает совершенно уникальную, а по факту, вполне известную, технологию. Поддержка реляционной модели изрядно хромает. Транзакционность снова воспринимается как конкурентное преимущество, но реализовать его под сиду далеко не всем. Действительно стоящих и прорывных решений — единицы. Будем ждать, когда недостойные вымрут, а достойные станут еще лучше. Мы будем болеть за FoundationDB ;)

Структура рынка СУБД

Clustrix

Очень интересная (и довольно старая, существует с 2008 года) NewSQL БД. Кластер, совместимый по протоколу с MySQL. Распределяет записи по узлам по диапазонам ключей, учитывая индексы на таблицу. Версионник, с нормальными транзакциями и оптимизатором запросов. К сожалению, очень проприетарная и поставляется только в виде RPM пакетов для RHEL.

Эдгар Кодд

Британский ученый. Создатель реляционной модели данных (60-е, 70-е годы 20 века). Работал в IBM. В 1985 году опубликовал «12 правил Кодда», которые должны выполняться в правильной реляционной СУБД. Ни тогда, ни, как видим, сейчас ни одна существующая СУБД не удовлетворяет всем этим правилам :)