Когда мы создавали облако, одной из сложных задач было написание биллинга. Мы решили пойти по пути максимального разделения компонент и ослабления связей (weak linking). Благодаря этому весь процесс делится на несколько независимых компонент: сбор информации о потреблении ресурсов компонентами виртуальной машины, хранение этой информации, списание средств со счёта клиентов, хранение истории списания средств (где-то рядом с историей пополнения и выписанными бумажными счет-фактурами и т.д.).

Как можно заметить, есть два больших этапа: сбор информации о потреблении (на этом этапе ещё нет никаких денег, а есть байты, секунды, запросы) и процесс превращения их в деньги (а так же всё с этим связанное — списание, хранение истории).

Вот тизер — недельный график суммарных списаний денег с клиентов:



Именно между «потреблением» и «списанием денег» и находится самая слабая связь. Для каждого объекта, принадлежащего клиенту мы храним поля с одним или более учитываемым ресурсом. Как уже раньше описывалось, память и процессорное время — это атрибут виртуальной машины, дисковые операции — vbd, хранение дисков — vdi, сеть — vif.

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

Сервис списания денег находится «поближе» к базе с деньгами и полностью независим от остальных компонент, он берёт потребление, цену, баланс и выдаёт на выходе новый баланс и списание, не задумываясь ни о чём прочем. Данные из сервиса он получает посредством SCAPI (о нём я буду писать много, когда мы его представим публике, пока это только внутренее средство взаимодействия между компонентами).

Когда мы делали систему, было решено раз и навсегда отказаться от дробных копеек на счету клиента. Минимальная единица списания — копейка. Для этого мы ввели нехитрое правило: средства за ресурс списываются только когда их набежит на копейку и списывается он так, чтобы дробных копеек не было (то есть остаётся мелкий неоплаченный остаток). В настоящий момент сервис списаний пробегает по машинам раз в минуту, хотя ничего не мешает (кроме вопросов излишней нагрузки на сервера) делать это с любой скоростью. Когда клиент видит в панельке «0» в графе потребление ресурса, это обычно означает, что ресурс пока не насчитал хотя бы на одну на копейку.

При этом, как понятно, идея «списания только целыми копейками» не имеет никакого отношения к точности учёта в сервисе consumption, где точность определяется размерами int64 (потому мы отказались от наносекунд в пользу микросекунд для процессорного времени) и осмысленностью величины (например, базовой величиной для оперативной памяти у нас является килобайт, а не байт, потому что выделить меньше килобайта, точнее, четырёх килобайт — размера страницы памяти, невозможно).

Разумеется, никаких «трафик округляется до мегабайт в большую сторону» и прочих опсосовских изобретений. Сколько посчиталось — столько посчиталось. Из этого вычитается потребление на целую сумму рублей/копеек, именно это списывается с счёта клиента. Остаток будет суммирован с последующим потреблением и будет списан тогда же.

В дальнейшем, при передаче по «списку» величины укрупняются, хотя точность не теряется, так как укрупнение не затрагивает сумматоры. Для интереса, вот цепочка укрупнений для процессорного времени: xen: наносекунда; consumption: микросекунда; сервис списаний: секунда; веб-интерфейс: час.