По мотивам одного из последних проектов буду рассказывать об особенностях разработки систем с сложной бизнес-логикой, ощутимыми требованиями к производительности и высокими требованиями к надежности.
Расскажу об особенностях платежных систем, опишу причины выбора основных архитектурных решений (Java, PostgreSQL, сервисы) и особенности процесса разработки.
Основные интересные моменты, на которых остановлюсь подробнее:
1) как решать популярные проблемы с БД и что делать, если не хватает IOPS;
2) какую надежную очередь стоит использовать;
3) какие нестандартные практики были успешными: IDE driven development, JSON вместо ORM, Functional test вместо Unit test;
4) какие решения были неправильными: PyTest, Spring Security, docker, велосипеды, разработчики, магия;
5) зачем менять методологию разработки три раза за год — и почему во многих проектах это тоже стоит делать.
Надеюсь, часть из сказанного вызовет несогласие и дискуссию.
2. О чем этот доклад
Что такое платежные системы, какие сложности при их разработке
Как жить, когда много конкурирующих транзакций и сложная
предметная область
Как строить и менять процесс разработки
Удачные и неудачные практики и решения
(С) Дельгядо Филипп, ИТИС, 2016
3. Основные вызовы
Старт проекта с нуля
Нет команды
Нет постановок
Высокие требования к проекту
Надежность
Производительность
Безопасность
Сжатые сроки и ограниченный бюджет
Секретность
(С) Дельгядо Филипп, ИТИС, 2016
4. Проект
1 большая комната
8 программистов
8 не только программистов
1 год
(С) Дельгядо Филипп, ИТИС, 20164
5. Платежные системы
Что сложного в платежных системах и как это сказывается на архитектуре
проекта
(С) Дельгядо Филипп, ИТИС, 2016
8. Например
115-ФЗ – О противодействии отмыванию доходов
161-ФЗ – О национальной платежной системе
152-ФЗ – О защите персональных данных
222-ФЗ – Об азартных играх
PCI DSS – О безопасности кредитных карт
И это далеко не все….
(С) Дельгядо Филипп, ИТИС, 2016
11. Требования к архитектуре
Очевидность кода и всех решений
Команда активно развивается, некогда изучать.
Простота code review
Иначе его никто не будет делать.
(С) Дельгядо Филипп, ИТИС, 2016
12. Требования к архитектуре
Очевидность кода и всех решений
Команда активно развивается, некогда изучать.
Простота code review
Иначе его никто не будет делать.
Надежность решения
Нельзя терять деньги.
(С) Дельгядо Филипп, ИТИС, 2016
13. Требования к архитектуре
Очевидность кода и всех решений
Команда активно развивается, некогда изучать.
Простота code review
Иначе его никто не будет делать.
Надежность решения
Нельзя терять деньги.
Высокая доступность
Без простоев, планируемых и непланируемых
(С) Дельгядо Филипп, ИТИС, 2016
14. Требования к архитектуре
Очевидность кода и всех решений
Команда активно развивается, некогда изучать.
Простота code review
Иначе его никто не будет делать.
Надежность решения
Нельзя терять деньги.
Высокая доступность
Без простоев, планируемых и непланируемых
Производительность
(С) Дельгядо Филипп, ИТИС, 2016
17. Принципы архитектуры
KISS
Keep it simple, stupid
No magic
Не доверяем сложным (особенно чужим) решениям
(С) Дельгядо Филипп, ИТИС, 2016
18. Принципы архитектуры
KISS
Keep it simple, stupid
No magic
Не доверяем сложным (особенно чужим) решениям
IDE driven development
Если IDE не поддерживает фреймворк, то мы его не используем
(С) Дельгядо Филипп, ИТИС, 2016
19. Принципы архитектуры
KISS
Keep it simple, stupid
No magic
Не доверяем сложным (особенно чужим) решениям
IDE driven development
Если IDE не поддерживает фреймворк, то мы его не используем
Static typing
Лучшие тестеры – это компилятор и инспекции
(С) Дельгядо Филипп, ИТИС, 2016
20. Принципы архитектуры
KISS
Keep it simple, stupid
No magic
Не доверяем сложным (особенно чужим) решениям
IDE driven development
Если IDE не поддерживает фреймворк, то мы его не используем
Static typing
Лучшие тестеры – это компилятор и инспекции
All can crash
(С) Дельгядо Филипп, ИТИС, 2016
24. Особенности платежа
Платеж всегда сложный
много состояний и переходов
Платеж может быть долгим
нельзя запихнуть платеж в один поток или одну транзакцию
(С) Дельгядо Филипп, ИТИС, 2016
25. Особенности платежа
Платеж всегда сложный
много состояний и переходов
Платеж может быть долгим
нельзя запихнуть платеж в один поток или одну транзакцию
Платеж должен быть надежным
все промежуточные стадии надо гарантированно записать на диск
(С) Дельгядо Филипп, ИТИС, 2016
26. Особенности платежа
Платеж всегда сложный
много состояний и переходов
Платеж может быть долгим
нельзя запихнуть платеж в один поток или одну транзакцию
Платеж должен быть надежным
все промежуточные стадии надо гарантированно записать на диск
Платежи бывают разные
надо уметь быстро менять логику
(С) Дельгядо Филипп, ИТИС, 2016
29. Особенности платежа
Гарантии доставки
Хитрая логика дожатия всех внешних взаимодействий
Всюду таймеры
Ничто не должно быть вечным
Ручная проверка зависших операций
(С) Дельгядо Филипп, ИТИС, 2016
30. Особенности платежа
Гарантии доставки
Хитрая логика дожатия всех внешних взаимодействий
Всюду таймеры
Ничто не должно быть вечным
Ручная проверка зависших операций
Все можно поменять руками
Но со строгим аудитом
(С) Дельгядо Филипп, ИТИС, 2016
31. Основная идея
Платеж переходит между состояниями
Переходы вызываются
предыдущим переходом
внешними событиями
срабатыванием таймеров
Переход меняет
состояние платежа,
контекст,
таймеры
Переход может делать что-то полезное, но идемпотемптно
(С) Дельгядо Филипп, ИТИС, 2016
32. Первый вариант реализации
Каждый переход выполняется асинхронно и в транзакции
Переходы и таймеры выстраиваем в очередь задач на пуле нитей
После падения повторяем последние переходы и восстанавливаем
таймеры
В БД сохраняем состояние и контекст
Все сделано на стандартном java concurrency
(С) Дельгядо Филипп, ИТИС, 2016
33. Первый вариант реализации
Может работать только на одном сервере
Долгое восстановление после сбоя
Нужны ручные синхронизации
Код сложно отлаживать и модифицировать
(С) Дельгядо Филипп, ИТИС, 2016
34. Хотелось бы
Много обработчиков на разных серверах
На каждый платеж своя надежная очередь переходов
Очереди и таймеры с гарантией обработки
Все действия по платежу проводятся строго последовательно
Обработка каждого события - одна транзакция
(С) Дельгядо Филипп, ИТИС, 2016
38. Много операций
Один платеж – около 10 транзакций
Каждая транзакция изменяет несколько сущностей
контексты, лимиты, проводки, состояния, таймеры
Часто меняются общие для всех сущности
лимиты, аккумулирующие счета
По ТЗ нужно 100 платежей в секунду
Итого нужны тысячи IOPS
(С) Дельгядо Филипп, ИТИС, 2016
39. Очень много операций
Один платеж – около 10 транзакций
Каждая транзакция изменяет несколько сущностей
контексты, лимиты, проводки, состояния, таймеры
Часто меняются общие для всех сущности
лимиты, аккумулирующие счета
По ТЗ нужно 100 платежей в секунду
Итого нужны тысячи IOPS
(С) Дельгядо Филипп, ИТИС, 2016
Ой!
40. Простое решение
(С) Дельгядо Филипп, ИТИС, 2016
4KB Random
write
Цена
400GB SAS Enterprise SSD 40 000 IOPS 600$
400GB SAS 12Gb/s Enterprise SSD 85 000 IOPS 3000$
1.6TB SATA III Enterprise SSD 14 000 IOPS 3500$
41. Платежи
update – дорогая операция
блокировки, лишний seek
Особенно когда конкурентная
(С) Дельгядо Филипп, ИТИС, 2016
42. Платежи
update – дорогая операция
блокировки, лишний seek
Особенно когда конкурентная
Заменим на insert
Текущее значение кэшируем
Раз в N операций объединяем
(С) Дельгядо Филипп, ИТИС, 2016
43. Очень много таблиц…
Много таблиц и связей
Сложно менять структуру
Без ОРМа не выжить
(С) Дельгядо Филипп, ИТИС, 2016
контекст платежа
44. Как можно решить проблему
Храним объекты в виде JSON
Jackson позволяет гибко описывать сериализацию через аннотации
(С) Дельгядо Филипп, ИТИС, 2016
45. Как можно решить проблему
Храним объекты в виде JSON
Jackson позволяет гибко описывать сериализацию через аннотации
В специальном типе ::json или ::jsonb
(С) Дельгядо Филипп, ИТИС, 2016
46. Как можно решить проблему
Храним объекты в виде JSON
Jackson позволяет гибко описывать сериализацию через аннотации
В специальном типе ::json или ::jsonb
Выкидываем ORM
(С) Дельгядо Филипп, ИТИС, 2016
47. Как можно решить проблему
Храним объекты в виде JSON
Jackson позволяет гибко описывать сериализацию через аннотации
В специальном типе ::json или ::jsonb
Выкидываем ORM
Отдельные поля для индексов и PK
(С) Дельгядо Филипп, ИТИС, 2016
48. А еще…
Храним номер версии сериализации
он точно будет быстро расти
(С) Дельгядо Филипп, ИТИС, 2016
49. А еще…
Храним номер версии сериализации
он точно будет быстро расти
Заранее готовим код для обновления версий
он точно пригодится
(С) Дельгядо Филипп, ИТИС, 2016
50. А еще…
Храним номер версии сериализации
он точно будет быстро расти
Заранее готовим код для обновления версий
он точно пригодится
Учим десериализатор поддерживать конфликты
а лучше бы и полиморфизм
(С) Дельгядо Филипп, ИТИС, 2016
51. А еще…
Храним номер версии сериализации
он точно будет быстро расти
Заранее готовим код для обновления версий
он точно пригодится
Учим десериализатор поддерживать конфликты
а лучше бы и полиморфизм
Прописываем приоритет индексных полей над содержимым
или делаем записи иммутабельными
(С) Дельгядо Филипп, ИТИС, 2016
52. В результате
(С) Дельгядо Филипп, ИТИС, 2016
Что хорошо
Производительность
Оптимистическая блокировка
История изменений
Атомарность изменений
Защита
53. В результате
(С) Дельгядо Филипп, ИТИС, 2016
Что хорошо
Производительность
Оптимистическая блокировка
История изменений
Атомарность изменений
Защита
И какие будут проблемы
Атомарность изменений
Сложность администрирования
Не поддерживается ОРМами
54. Как это выглядит в БД
(С) Дельгядо Филипп, ИТИС, 2016
create table operations (
id bigint not null primary key,
refId bigint,
state varchar(20),
schema bigint not null,
data json );
55. Как это выглядит в БД
(С) Дельгядо Филипп, ИТИС, 2016
create table operations (
id bigint not null primary key,
refId bigint,
state varchar(20),
schema bigint not null,
data json ); select id,
data->'amount'->>'amount',
data->'amount'->>'currency'
from operations;
56. Как это выглядит в БД
(С) Дельгядо Филипп, ИТИС, 2016
create table operations (
id bigint not null primary key,
refId bigint,
state varchar(20),
schema bigint not null,
data json ); select id,
data->'amount'->>'amount',
data->'amount'->>'currency'
from operations;
57. NoSQL?
Активно используем join
Используем транзакции, блокировки
Используем все инструменты обеспечения надежности
Используем sequence
(С) Дельгядо Филипп, ИТИС, 2016
59. Вспомним, что нам хотелось
Много обработчиков на разных серверах
На каждый платеж своя распределенная очередь переходов и
таймеров с гарантией обработки
Все действия по платежу проводятся строго последовательно
Обработка каждого события - одна транзакция
(С) Дельгядо Филипп, ИТИС, 2016
61. Очередь на skip locked
(С) Дельгядо Филипп, ИТИС, 2016
ROW queue
1 alpha
2 beta
3 gamma
4 delta
queue event
alpha a1
alpha a2
beta b1
gamma g4
delta d2
delta d3
select q.queue, e.event
from q join e
order by e.event asc
limit 1
for update
skip locked;
62. Очередь на skip locked
(С) Дельгядо Филипп, ИТИС, 2016
ROW queue
1 alpha
2 beta
3 gamma
4 delta
queue event
alpha a1
alpha a2
beta b1
gamma g4
delta d2
delta d3
select q.queue, e.event
from q join e
order by e.event asc
limit 1
for update
skip locked;
P P
63. Очередь на skip locked
(С) Дельгядо Филипп, ИТИС, 2016
ROW queue
1 alpha
2 beta
3 gamma
4 delta
queue event
alpha a1
alpha a2
beta b1
gamma g4
delta d2
delta d3
select q.queue, e.event
from q join e
order by e.event asc
limit 1
for update
skip locked;
64. Очередь на skip locked
(С) Дельгядо Филипп, ИТИС, 2016
ROW queue
1 alpha
2 beta
3 gamma
4 delta
queue event
alpha a1
alpha a2
beta b1
gamma g4
delta d2
delta d3
select q.queue, e.event
from q join e
order by e.event asc
limit 1
for update
skip locked;
P
P
65. Замечания
Добавление события ничего не блокирует
Число обработчиков на производительность почти не влияет
Корректно работает на всех уровнях изоляции
(С) Дельгядо Филипп, ИТИС, 2016
66. Замечания
Добавление события ничего не блокирует
Число обработчиков на производительность почти не влияет
Корректно работает на всех уровнях изоляции
Удалять обработанные события надо в той же транзакции
(С) Дельгядо Филипп, ИТИС, 2016
67. Замечания
Добавление события ничего не блокирует
Число обработчиков на производительность почти не влияет
Корректно работает на всех уровнях изоляции
Удалять обработанные события надо в той же транзакции
Для событий можно указывать дату актуальности
и можно не делать других таймеров
(С) Дельгядо Филипп, ИТИС, 2016
68. Замечания
Добавление события ничего не блокирует
Число обработчиков на производительность почти не влияет
Корректно работает на всех уровнях изоляции
Удалять обработанные события надо в той же транзакции
Для событий можно указывать дату актуальности
и можно не делать других таймеров
В запросе нужно явно указывать время жизни транзакции
события не должно слишком долго обрабатываться
(С) Дельгядо Филипп, ИТИС, 2016
70. Тестирование
Функциональные тесты обязательны
дампы БД сильно ускоряют прохождение тестов
Интеграционные тесты для отладки
и с ними спокойнее
Unit-тесты в крайнем случае
и лучше без mock’ов
Все тесты должны быть запускаемы у программиста
а не только из CI
Без разрешения Test Lead продакшн не обновляется
(С) Дельгядо Филипп, ИТИС, 2016
71. Безопасное обновление
Первый шаг
Читаем старое
Пишем старое и новое
Второй шаг
Копируем старое в новое
Третий шаг
Читаем новое
Пишем новое
Четвертый шаг
Удаляем старое
(С) Дельгядо Филипп, ИТИС, 2016
V1
Old
New
V1
V2
Old
New
Old
New
V2 New
72. Как все это организовать
Чтобы и программисты не задолбались, и заказчик не плакал
(С) Дельгядо Филипп, ИТИС, 2016
73. Каждому проекту – своя методология
(С) Alistair Cockburn
Оригинал статьи http://goo.gl/PFnoe0
Перевод на http://goo.gl/SUCgzC
(С) Дельгядо Филипп, ИТИС, 2016
74. Разработка в вакууме
Нет проверенных постановок
Исследовательские задачи
Демонстрация продукта может быть в любое время
Много исследовательских задач
(С) Дельгядо Филипп, ИТИС, 2016
75. Разработка в вакууме
Нет проверенных постановок
Исследовательские задачи
Демонстрация продукта может быть в любое время
Много исследовательских задач
(С) Дельгядо Филипп, ИТИС, 2016
87. Люди
Никто не понимает транзакций
Любят сложный код
Поголовная любовь к магии
Доверие обещаниям вендоров
(С) Дельгядо Филипп, ИТИС, 2016
88. Нерешенные задачи
Как защитить данные от системного администратора
и при этом обеспечить автоматическую выкладку
(С) Дельгядо Филипп, ИТИС, 2016
89. Нерешенные задачи
Как защитить данные от системного администратора
и при этом обеспечить автоматическую выкладку
Как не терять транзакций при падении ДЦ
за разумные деньги
(С) Дельгядо Филипп, ИТИС, 2016
90. Нерешенные задачи
Как защитить данные от системного администратора
и при этом обеспечить автоматическую выкладку
Как не терять транзакций при падении ДЦ
за разумные деньги
Как найти много хороших разработчиков
и вписаться в бюджет
(С) Дельгядо Филипп, ИТИС, 2016
92. Ошибки
Активное использование Spring
Слишком притягательна магия Spring Security
И слишком много разработчиков его любят
Тесты пишутся на чужеродном стеке
Так получилось, что пишем на Python (PyTest)
(С) Дельгядо Филипп, ИТИС, 2016
93. Ошибки
Активное использование Spring
Слишком притягательна магия Spring Security
И слишком много разработчиков его любят
Тесты пишутся на чужеродном стеке
Так получилось, что пишем на Python (PyTest)
Погнались за модным Docker
Для наших условий – слишком много костылей
(С) Дельгядо Филипп, ИТИС, 2016
95. Вопросы и контакты
Дельгядо Филипп,
dph.main@gmail.com
phd@itasystems.ru
vk.com/dphil
(С) Дельгядо Филипп, ИТИС, 2016
http://itasystems.ru/
Editor's Notes
При
10 вспомогательных сущностей, списки, кортежи (map), списки кортежей, карты флагов и т.п.
Разница между json и jsonb – отдельный вопрос
Разница между json и jsonb – отдельный вопрос
Одесса, вокзал. Запарившаяся дама с горой чемоданов мечется в поисках такси. Подъезжает наконец какая-то машина: «Куда желаете?» Дама осматривает авто: «Позвольте, вы же не такси!» – «Я не понял, мадам, так вам ехать или шашечки?» Простите, что цитирую такой знаменитый фольклор; просто, как выяснилось, не все этот анекдот знают.
Одесса, вокзал. Запарившаяся дама с горой чемоданов мечется в поисках такси. Подъезжает наконец какая-то машина: «Куда желаете?» Дама осматривает авто: «Позвольте, вы же не такси!» – «Я не понял, мадам, так вам ехать или шашечки?» Простите, что цитирую такой знаменитый фольклор; просто, как выяснилось, не все этот анекдот знают.
Одесса, вокзал. Запарившаяся дама с горой чемоданов мечется в поисках такси. Подъезжает наконец какая-то машина: «Куда желаете?» Дама осматривает авто: «Позвольте, вы же не такси!» – «Я не понял, мадам, так вам ехать или шашечки?» Простите, что цитирую такой знаменитый фольклор; просто, как выяснилось, не все этот анекдот знают.
Одесса, вокзал. Запарившаяся дама с горой чемоданов мечется в поисках такси. Подъезжает наконец какая-то машина: «Куда желаете?» Дама осматривает авто: «Позвольте, вы же не такси!» – «Я не понял, мадам, так вам ехать или шашечки?» Простите, что цитирую такой знаменитый фольклор; просто, как выяснилось, не все этот анекдот знают.
Одесса, вокзал. Запарившаяся дама с горой чемоданов мечется в поисках такси. Подъезжает наконец какая-то машина: «Куда желаете?» Дама осматривает авто: «Позвольте, вы же не такси!» – «Я не понял, мадам, так вам ехать или шашечки?» Простите, что цитирую такой знаменитый фольклор; просто, как выяснилось, не все этот анекдот знают.
Одесса, вокзал. Запарившаяся дама с горой чемоданов мечется в поисках такси. Подъезжает наконец какая-то машина: «Куда желаете?» Дама осматривает авто: «Позвольте, вы же не такси!» – «Я не понял, мадам, так вам ехать или шашечки?» Простите, что цитирую такой знаменитый фольклор; просто, как выяснилось, не все этот анекдот знают.