Удаление конфиденциальных данных
Бывает так, что встречаешь описание интересного подхода или алгоритма, а потом про него забываешь, так как теория не была подкреплена практикой или прошло очень много времени. И вот когда наступает момент, где можно блеснуть своими знаниями, приходится тратить время, чтобы вспомнить детали. 😃 В очередной раз, когда я поймал себя на подобной мысли, я решил, что было бы неплохо делиться подобными находками. 🤔 #tip
Сегодня расскажу о способе моментального удаления конфиденциальных данных.
Контекст
При удалении агрегата может потребоваться удаление всех связанных с ним данных. При наличии физических связей, таких как внешние ключи в реляционных базах данных, подобное требование реализовать нетрудно. Однако связь между агрегатом и его данными может быть не такой прямолинейной, тогда задача существенно усложняется. Например, реквизиты пользователя оказались в каком-либо журнале или таблице для построения отчетной формы.
Так или иначе, удаление может стать непростой задачей, особенно если система заранее не была рассчитана на подобные операции. Сложность или невозможность удаления может быть вызвана следующими причинами:
🕚 Удаление запрещено на уровне бизнес-логики. Например, в системе есть журнал событий, который является отражением действий пользователя. При удалении учетной записи пользователя мы должны удалить его персональную информацию, если она содержалась в событиях, но не можем удалить сами события, поскольку это запрещено юридически.
🕚 Архитектурные ограничения. Чаще всего являются прямым следствием бизнес-требований. Например, разработчики решили, что для реализации большей части требований к системе идеально подходит шаблон Event Sourcing.
🕚 Очень много данных для очистки. Возможно, что в системе накопилось очень много данных, которые должны быть подвержены анализу с целью зачистки. В случае отсутствия специализированных индексов, которые бы ускоряли процесс поиска, он может оказаться неприемлемо долгим и ресурсоемким.
🕚 Сложность физического удаления. Для многих баз данных операции В общем, большинство баз спроектировано, чтобы хранить данные, а не удалять. 😃
Проблема
Большие сложности или невозможность произвести физическое удаление данных, связанных с каким-либо агрегатом системы.
Решение
Хранить секретные данные в зашифрованном виде с использованием симметричного шифрования. Ключ шифрования хранить вместе с агрегатом. При удалении агрегата, удалять и ключ шифрования. Без ключа шифрования зашифрованные данные, в сущности, удалены, так как не могут быть расшифрованы.
Плюсы
Данные "удаляются" моментально, сразу, как только будет удален ключ шифрования. В этом смысле подход напоминает логическое удаление и, кстати, может использоваться с ним совместно.
Минусы
Данные продолжают занимать место на диске. Возникает необходимость в шифровании/дешифровании при каждой записи/чтении.
***
P.s. Сталкивались ли вы с подобной проблемой на практике и как решали? 😉 Однажды наблюдал ситуацию утечки данных, и выяснилось, что база, которая в общем случае деперсонифицирована, в некоторых атрибутах содержала персональную информацию в открытом виде. Эти данные попали туда в ходе интеграции с другими системами, разработчики которой не задумались о необходимости ее шифрования или поиска более подходящего места для ее хранения.
Бывает так, что встречаешь описание интересного подхода или алгоритма, а потом про него забываешь, так как теория не была подкреплена практикой или прошло очень много времени. И вот когда наступает момент, где можно блеснуть своими знаниями, приходится тратить время, чтобы вспомнить детали. 😃 В очередной раз, когда я поймал себя на подобной мысли, я решил, что было бы неплохо делиться подобными находками. 🤔 #tip
Сегодня расскажу о способе моментального удаления конфиденциальных данных.
Контекст
При удалении агрегата может потребоваться удаление всех связанных с ним данных. При наличии физических связей, таких как внешние ключи в реляционных базах данных, подобное требование реализовать нетрудно. Однако связь между агрегатом и его данными может быть не такой прямолинейной, тогда задача существенно усложняется. Например, реквизиты пользователя оказались в каком-либо журнале или таблице для построения отчетной формы.
Так или иначе, удаление может стать непростой задачей, особенно если система заранее не была рассчитана на подобные операции. Сложность или невозможность удаления может быть вызвана следующими причинами:
UPDATE и DELETE проблематичны. Например, для MariaDB удаление - это настолько нетривиальная задача, что ее разработчики подготовили целую инструкцию - "Big DELETEs". Аналогичные проблемы имеются и у MySQL. В PostgreSQL частые удаления могут быть одной из причин раздувания базы. У Cassandra удаление может приводить к большой загрузке узлов во время компоновки. Проблема
Большие сложности или невозможность произвести физическое удаление данных, связанных с каким-либо агрегатом системы.
Решение
Хранить секретные данные в зашифрованном виде с использованием симметричного шифрования. Ключ шифрования хранить вместе с агрегатом. При удалении агрегата, удалять и ключ шифрования. Без ключа шифрования зашифрованные данные, в сущности, удалены, так как не могут быть расшифрованы.
Плюсы
Данные "удаляются" моментально, сразу, как только будет удален ключ шифрования. В этом смысле подход напоминает логическое удаление и, кстати, может использоваться с ним совместно.
Минусы
Данные продолжают занимать место на диске. Возникает необходимость в шифровании/дешифровании при каждой записи/чтении.
***
P.s. Сталкивались ли вы с подобной проблемой на практике и как решали? 😉 Однажды наблюдал ситуацию утечки данных, и выяснилось, что база, которая в общем случае деперсонифицирована, в некоторых атрибутах содержала персональную информацию в открытом виде. Эти данные попали туда в ходе интеграции с другими системами, разработчики которой не задумались о необходимости ее шифрования или поиска более подходящего места для ее хранения.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5🔥4⚡1
Проблемы изоляции транзакций (1/3)
Меня все никак не отпускает тема транзакций и изоляции. 😃 Сегодняшний пост будет подводкой к двум последующим. Возможно, будет немного "душно", но попрошу потерпеть.😄
〰️ 〰️ 〰️
Конкурентный доступ к данным - одна из основных проблем при реализации изоляции транзакций. Способ решения этой проблемы определяет ключевые характеристики базы данных, на которые мы опираемся при выборе подходящего хранилища для своих проектов. И так повелось, что со времен изобретения SQL-стандарта так и не появилось однозначного определения "уровней изоляции транзакций", благодаря чему у каждой базы данных своё представление относительно этого вопроса. А разбираться с этим многообразием приходится нам - пользователям этих продуктов. 🤷🏻♂️
Если посмотреть на определение ACID, то оно не предполагает какой-либо вариативности относительно изоляции транзакций. Тем не менее, реализация таких жестких требований напрямую отражается на пропускной способности. Вследствие этого в SQL-стандарте имеется термин "уровень изоляции транзакции", предполагающий "компромисс", на который может пойти разработчик в случае, если ему потребуется большая пропускная способность. Иначе говоря, нам предлагают улучшить производительность приложения за счет корректности данных, с которыми оно работает. 😬 Ясно, что на подобный компромисс нужно идти осознанно, понимая, что именно может пойти не так при выборе определенного уровня изоляции и устраивает ли нас это.
SQL-стандарт определяет всего три феномена (phenomena), т.е. три типа нежелательных последствий, возможных при снижении уровня изоляции: грязное чтение (dirty read), неповторяющееся чтение (non-repeatable read) и фантом (phantom). Эта слабая категоризация послужила причиной вольных трактовок при реализации уровней изоляции. Вскоре этот момент был подвержен критике, и список возможных последствий был расширен и конкретизирован путем ввода нового понятия - аномалия (anomaly). Аномалии конкретизируют действия, которые могут привести к феномену. Существует замечательный исследовательский проект Hermitage, в котором определена современная классификация аномалий и в разрезе них приведено сравнение одних и тех же уровней изоляции в разных популярных базах данных (Oracle, PostgreSQL, MySQL и др.).
В качестве иллюстрации можно рассмотреть феномен неповторяющегося чтения. Транзакция
Краткая запись этого феномена выглядит так:
#dev #db
Меня все никак не отпускает тема транзакций и изоляции. 😃 Сегодняшний пост будет подводкой к двум последующим. Возможно, будет немного "душно", но попрошу потерпеть.
Конкурентный доступ к данным - одна из основных проблем при реализации изоляции транзакций. Способ решения этой проблемы определяет ключевые характеристики базы данных, на которые мы опираемся при выборе подходящего хранилища для своих проектов. И так повелось, что со времен изобретения SQL-стандарта так и не появилось однозначного определения "уровней изоляции транзакций", благодаря чему у каждой базы данных своё представление относительно этого вопроса. А разбираться с этим многообразием приходится нам - пользователям этих продуктов. 🤷🏻♂️
Если посмотреть на определение ACID, то оно не предполагает какой-либо вариативности относительно изоляции транзакций. Тем не менее, реализация таких жестких требований напрямую отражается на пропускной способности. Вследствие этого в SQL-стандарте имеется термин "уровень изоляции транзакции", предполагающий "компромисс", на который может пойти разработчик в случае, если ему потребуется большая пропускная способность. Иначе говоря, нам предлагают улучшить производительность приложения за счет корректности данных, с которыми оно работает. 😬 Ясно, что на подобный компромисс нужно идти осознанно, понимая, что именно может пойти не так при выборе определенного уровня изоляции и устраивает ли нас это.
SQL-стандарт определяет всего три феномена (phenomena), т.е. три типа нежелательных последствий, возможных при снижении уровня изоляции: грязное чтение (dirty read), неповторяющееся чтение (non-repeatable read) и фантом (phantom). Эта слабая категоризация послужила причиной вольных трактовок при реализации уровней изоляции. Вскоре этот момент был подвержен критике, и список возможных последствий был расширен и конкретизирован путем ввода нового понятия - аномалия (anomaly). Аномалии конкретизируют действия, которые могут привести к феномену. Существует замечательный исследовательский проект Hermitage, в котором определена современная классификация аномалий и в разрезе них приведено сравнение одних и тех же уровней изоляции в разных популярных базах данных (Oracle, PostgreSQL, MySQL и др.).
В качестве иллюстрации можно рассмотреть феномен неповторяющегося чтения. Транзакция
T1 делает два последовательных чтения значения x: сначала считывает значение 10, затем 20. Так происходит, потому что значение x успели поменять в параллельно выполняющейся транзакции T2.T1: r[x=10].........r[x=20].
T2: ........w[x=20].........
Краткая запись этого феномена выглядит так:
r1[x=10],w2[x=20],r1[x=20]. Данный феномен возможен, например, в режиме Read Committed. Аномалия для этого режима изоляции выглядит так: r1[x=10],w2[x=20],c2,r1[x=20] (т.е. после записи вторая транзакция фиксируется - c2). Примерно в таком стиле происходит формализация аномалий и их тестирование в разных базах данных.#dev #db
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4👍3⚡1
Проблемы изоляции транзакций (2/3)
Зная и понимая возможные аномалии, исключаем те из них, появление которых неприемлемо для нашего приложения, и таким образом выбираем подходящий уровень изоляции.
Рассмотрим классические уровни изоляции, которые есть в большинстве баз. 🧐
⭐️ Read Committed. Исключает грязное чтение и запись, т.е. транзакция может читать и изменять только зафиксированные изменения. Ключевой момент в том, что учитываются даже те изменения, которые были зафиксированы во время выполнения текущей транзакции. Обычно на уровне реализации грязную запись исключают за счет использования программных блокировок на уровне редактируемой строки (row-level lock); а грязное чтение - за счет хранения двух версий значения записи: последнее зафиксированное и еще не зафиксированное. Такой уровень изоляции установлен по умолчанию в PostgreSQL, MS SQL и Oracle. Допускает неповторяющееся чтение, потерю изменений (перезапись), искажение чтения/записи (read/write skew).
⭐️ ⭐️ Repeatable Read. Транзакция может читать только те изменения, которые были зафиксированы до ее начала. Обычно на уровне реализации грязную запись исключают за счет использования программных блокировок на уровне редактируемой строки (row-level lock); а грязное чтение - за счет хранения нескольких версий значения записи - алгоритм MVCC (Multi-Version Concurrency Control). Такой уровень изоляции установлен по умолчанию в MySQL и MariaDB. Допускает искажение чтения/записи (read/write skew), но в MySQL и MariaDB даже потерю изменений (перезапись). Идеально подходит для создания бэкапов или выполнения долгих read-only транзакций (например, сложные аналитические запросы).
⭐️ ⭐️ ⭐️ Serializable. Гарантирует, что даже при параллельном исполнении транзакций результат будет точно таким же, как если бы они исполнялись последовательно. Это определение полностью совпадает с определением изоляции в ACID. Позже этот уровень изоляции Daniel Abadi - один из соавторов протокола Calvin - назвал "идеальной изоляцией" (perfect isolation). Такой уровень изоляции установлен по умолчанию в CockroachDB и YDB. По определению он не должен допускать аномалий. (Примечательно, что в Oracle этот уровень изоляции на самом деле соответствует Repeatable Read.)
Можно подумать, что выбрав уровень Serializable, уйдут все проблемы. К сожалению, нет, если речь идет о распределенных базах данных. 😃 Там появляются дополнительные аномалии, большинство из которых связано с изменением порядка выполнения транзакций.
Например, последовательное выполнение трех транзакций
Решая эти проблемы, разработчики одних распределенных баз данных предпочитают скрыть от пользователя все эти сложности и особенности за привычными уровнями изоляции (CockroachDB, YugabyteDB, YDB); разработчики других, наоборот, изобретают свою классификацию уровней (CosmosDB), предоставляя максимальную гибкость в использовании.
#dev #db
Зная и понимая возможные аномалии, исключаем те из них, появление которых неприемлемо для нашего приложения, и таким образом выбираем подходящий уровень изоляции.
Рассмотрим классические уровни изоляции, которые есть в большинстве баз. 🧐
Можно подумать, что выбрав уровень Serializable, уйдут все проблемы. К сожалению, нет, если речь идет о распределенных базах данных. 😃 Там появляются дополнительные аномалии, большинство из которых связано с изменением порядка выполнения транзакций.
Например, последовательное выполнение трех транзакций
w1[x=1],w2[x=2],w3[x=3] может закончиться результатом x=2, поскольку реплика переупорядочила транзакции и в реальности выполнила w1,w3,w2. Такой исход вполне возможен, например, из-за рассинхронизации часов ⏰ на репликах в мультимастер-системах. Причем технически подобный исход будет считаться корректным, т.к. формальные требования упорядоченности транзакций (serializability) не нарушаются.Решая эти проблемы, разработчики одних распределенных баз данных предпочитают скрыть от пользователя все эти сложности и особенности за привычными уровнями изоляции (CockroachDB, YugabyteDB, YDB); разработчики других, наоборот, изобретают свою классификацию уровней (CosmosDB), предоставляя максимальную гибкость в использовании.
#dev #db
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3👍2⚡1
Проблемы изоляции транзакций (3/3)
Надеюсь, что у меня получилось сформировать более целостную картину относительно уровней изоляции транзакций. 😉
Попытаюсь подвести итоги.
1️⃣ Выбирая уровень изоляции, помним про аномалии, оцениваем возможные риски и способы их минимизации.
2️⃣ Одни и те же уровни изоляции у всех реализованы по-разному, поэтому смотрим тесты Hermitage, обязательно пишем свои и читаем документацию.
3️⃣ Указываем уровень изоляции транзакции явно, помня, что большинство баз данных в целях улучшения производительности по умолчанию использует более слабые уровни изоляции.
4️⃣ Не допускаем конкурирующих транзакций с разным уровнем изоляции, иначе есть риск привести данные в несогласованное состояние.
5️⃣ Помним, что базы данных гарантируют техническую целостность данных, но не их согласованность с точки зрения бизнеса. Однако первое может значительно упростить реализацию второго.
〰️ 〰️ 〰️
Согласитесь, что жить с таким грузом очень тяжело!😄 Поэтому в следующий раз я планирую рассмотреть подходы и техники обеспечения согласованности данных с учетом всего вышесказанного. А пока предлагаю делиться своими феноменами и аномалиями. 😉👇
#dev #db
Надеюсь, что у меня получилось сформировать более целостную картину относительно уровней изоляции транзакций. 😉
Попытаюсь подвести итоги.
🗂 Пока готовил этот пост, открыл для себя проект Jepsen. Это фреймворк для тестирования распределенных систем на предмет согласованности и корректности работы в условиях сбоев. Инструмент является стандартом де-факто для проверки многих популярных распределённых систем и баз данных. Интересно, насколько реально/удобно использовать этот инструмент для тестирования прикладных проектов... 🤔
Согласитесь, что жить с таким грузом очень тяжело!
#dev #db
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3⚡1👍1
Безопасное исполнение ненадежного кода
Лучший способ о чем-то рассказать - это сделать доклад. 😃 Поэтому я подал заявку на участие в конференции TechLeadConf 2025. Сейчас уже могу сказать, что меня включили в программу, поэтому жду вас 5 июня в Москве. Тема доклада - "Безопасное исполнение ненадежного кода". Приходите, будет интересно. 😉
#conf #untrusted_code
Лучший способ о чем-то рассказать - это сделать доклад. 😃 Поэтому я подал заявку на участие в конференции TechLeadConf 2025. Сейчас уже могу сказать, что меня включили в программу, поэтому жду вас 5 июня в Москве. Тема доклада - "Безопасное исполнение ненадежного кода". Приходите, будет интересно. 😉
#conf #untrusted_code
🔥9⚡2🏆1
Forwarded from Конференция ArchDays
Если пропустил конференцию или хочешь пересмотреть крутые доклады, у нас для тебя хорошие новости — плейлист с видео уже доступен!
Заряжаемся пользой, пересматриваем, делимся инсайтами! Какое выступление уже в твоём списке «посмотреть обязательно»?
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4👍3
Конкурентный доступ к данным (1/2)
Конкурентный доступ к данным — это драйвер🚀 всего того, что делается во имя изоляции транзакций. Конечно, речь о транзакциях, которые модифицируют данные (read/write, write/write), поскольку именно в этом случае возникают все те аномалии, о которых шла речь ранее.
За согласованность данных отвечает прикладной код, механизмы используемого хранилища могут лишь упрощать эту задачу, но не решать ее. И прежде, чем начну с рассмотрения техник согласования изменений, сделаю два утверждения, которые нужно держать в голове:
1️⃣ Чаще всего используются слабые уровни изоляции транзакций. И часто это делается под предлогом увеличения пропускной способности базы данных и, как следствие, разрабатываемого приложения.
2️⃣ Чаще всего прикладные сценарии реализуются по схеме "read-modify-write": чтение данных из базы, их анализ и модификация, запись данных в базу. Такая схема предполагает, как минимум, два обращения к хранилищу (read/write), которые разнесены по времени.
Согласитесь, что это идеальная среда для размножения багов.🐞 😃 Ошибки, вызванные нарушением изоляции, возникают иногда, при каком-то определенном стечении обстоятельств, поэтому их трудно обнаружить, воспроизвести и протестировать. При этом последствия могут быть крайне неприятными во всех отношениях. Более того, есть реальные жизненные примеры, когда подобные баги использовались как уязвимость.
Техники снижения вероятности возникновения конфликтующих изменений:
⭐️ Коммутативные операции. Изменение порядка выполнения транзакций не меняет итоговый результат. Например, увеличение счетчика.
⭐️ Атомарные операции. Все действия с данными выполняются с помощью одной операции, в рамках одного обращения к базе, которая создает эксклюзивную блокировку, исключая одновременный доступ к данным. Пример атомарной операции — SQL-оператор
⭐️ Переупорядочивание операций. Производится перестановка шагов алгоритма так, что возникновение конфликта становится невозможным или легко детектируемым. Например, вместо "если не существует, то создать" (
⭐️ Хранимые процедуры. Все необходимые действия производятся на стороне базы данных, следовательно, исключаются дополнительные сетевые издержки на взаимодействие между приложением и базой. Чем короче транзакции, тем меньше шансов, что они будут конфликтовать.
#dev #db
Конкурентный доступ к данным — это драйвер
За согласованность данных отвечает прикладной код, механизмы используемого хранилища могут лишь упрощать эту задачу, но не решать ее. И прежде, чем начну с рассмотрения техник согласования изменений, сделаю два утверждения, которые нужно держать в голове:
Согласитесь, что это идеальная среда для размножения багов.
Техники снижения вероятности возникновения конфликтующих изменений:
UPDATE. Метод работает идеально, но только в самых простых случаях.SELECT/INSERT) делается "создать, но если появился дубликат, тогда откатить создание" (INSERT/SELECT/ROLLBACK).#dev #db
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥6⚡1👍1
Конкурентный доступ к данным (2/2)
Техники исключения одновременных изменений:
☄️ Явная/пессимистичная блокировка. Данные блокируются до начала их изменения. Пример — SQL-оператор
☄️ Оптимистичная блокировка. Наличие конфликта доступа к данным производится непосредственно перед или вместе с записью в базу. Например, объект дополняется свойством
☄️ Материализация конфликтов. Если объект "спора" еще не существует, то для контроля доступа к нему используется вспомогательный объект, который позволяет обнаружить — материализовать — конфликтующие изменения. Например, при покупке билета на концерт блокируется сам билет (уникальная пара "место-концерт"), после чего создается запись о покупке ("место-концерт-зритель"). Блокировка может быть явной или на основе уникального индекса. Работает хорошо, если вспомогательный объект существует в предметной области, а не является синтетической выдумкой, которая служит лишь для контроля доступа.
☄️ Блокировка диапазона индекса. Предикативная блокировка диапазона изменяемых данных на основе индекса. Блокируемый диапазон может указываться явно, например, в условии
☄️ Последовательный доступ. Все изменения данных приложение производит последовательно, в рамках одного рабочего потока — обработчика. Метод эффективен в случае коротких транзакций и когда производительности одного обработчика достаточно, иначе он может быстро стать узким местом в системе.
☄️ Партиционирование данных. Развитие идеи с последовательным доступом, но в рамках какого-то подмножества данных. Каждое подмножество — партиция — обрабатывается последовательно, в рамках одного рабочего потока - обработчика. Например, финансовые движения партиционируются по номеру банковского счета, таким образом, все транзакции, связанные с одним счетом, попадут в одну и ту же партицию — одно и тоже подмножество. Метод эффективен в случае, когда прикладные сценарии не выходят за пределы одной партиции.
При этом в большинстве распределенных баз в общем случае неприменимы:
🕚 Явная/пессимистичная блокировка
🕚 Оптимистичная блокировка
🕚 Материализация конфликтов
🕚 Блокировка диапазона индекса
На мой взгляд, всё это выглядит крайне непросто. Поэтому стоит честно ответить на вопрос: "У меня реально такая конкурентность при записи?" Если ответ "нет", тогда стоит начать с самых простых техник (и до конца придерживаться принципа KISS). Если ответ "да", то стоит подумать о подходе работы с данными, например, посмотреть в сторону append-only-техник (шаблоны Event Sourcing, CQRS). Конечно, это не единственно возможные варианты, и очень важен контекст решаемой задачи. Однако всегда стоит подвергать сомнению и делать ревизию текущего решения и инструментов с учетом имеющегося опыта и появляющихся требований.
〰️ 〰️ 〰️
Делитесь своими техниками обеспечения согласованности с использованием архитектурных подходов или средств баз данных. 😉👇
#dev #db
Техники исключения одновременных изменений:
SELECT FOR UPDATE с указанием редактируемых строк в условии WHERE. Метод работает хорошо, но есть шанс заблокировать больше или меньше, чем нужно. Большая блокировка — низкая пропускная способность; недостаточная - нарушение изоляции.version, которое увеличивается на единицу при каждом редактировании; соответственно, в момент записи ожидается, что в базе значение version меньше — более старая версия. Метод работает хорошо в условиях низкой конкурентности, однако решение проблемы потери данных из-за перезаписи ложится на прикладной код.WHERE оператора SELECT FOR UPDATE, либо неявно при использовании уровня изоляции Serializable. Метод эффективен для борьбы с фантомными чтениями, но может заблокировать слишком большой диапазон объектов (при отсутствии подходящего индекса — всю таблицу).При этом в большинстве распределенных баз в общем случае неприменимы:
На мой взгляд, всё это выглядит крайне непросто. Поэтому стоит честно ответить на вопрос: "У меня реально такая конкурентность при записи?" Если ответ "нет", тогда стоит начать с самых простых техник (и до конца придерживаться принципа KISS). Если ответ "да", то стоит подумать о подходе работы с данными, например, посмотреть в сторону append-only-техник (шаблоны Event Sourcing, CQRS). Конечно, это не единственно возможные варианты, и очень важен контекст решаемой задачи. Однако всегда стоит подвергать сомнению и делать ревизию текущего решения и инструментов с учетом имеющегося опыта и появляющихся требований.
Делитесь своими техниками обеспечения согласованности с использованием архитектурных подходов или средств баз данных. 😉
#dev #db
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥5⚡1👍1
Bubblewrap
Представьте, что вам нужно запустить некоторый процесс Linux в изолированной среде. Так, чтобы он не смог нанести вред системе, на которой запускается. Какие инструменты мы используем в таких случаях? Первое, что приходит в голову, это технологии виртуализации или контейнеризации. Но что, если нам нужно что-то более легковесное и простое в использовании? На самом деле, такой инструмент есть — это утилита командной строки Bubblewrap, один из проектов сообщества Containers (знаменито авторством таких инструментов, как Podman, Buildah, Skopeo и др.).
Когда может быть полезен:
☑️ Невысокие требования к изоляции процесса и системы.
☑️ Недопустимы накладные расходы как у более сложных средств изоляции.
☑️ Тестирование (поведения) программы в разных условиях эксплуатации.
☑️ Запуск программ/скриптов/кода, полученных из ненадежных источников.
☑️ Лимитирование расхода ресурсов для отдельно взятого процесса.
☑️ Изменение структуры файловой системы для запускаемого процесса.
☑️ Разработка изолированных сред типа Flatpack.
Установка:
Использование:
В приведенном примере команда
выдаст ошибку:
На самом деле
Таким образом, используя аргументы командной строки, вы сами определяете уровень изоляции запускаемого процесса. При этом под капотом Bubblewrap использует стандартные средства Linux, которые не требуют повышения привилегий пользователя: chroot, clone, seccomp, cgroups, landlock.
Основные плюсы:
🕚 Не требуются root-привилегии.
🕚 Изолирует работу только одного процесса.
🕚 Может использоваться для изоляции приложений с графическим UI.
🕚 Предоставляет большой спектр настроек лимитирования.
🕚 Простая установка и использование.
🕚 Низкие накладные расходы на изоляцию.
Основной минус — это то, что ограничение на ресурсы системы устанавливаются только с использованием cgroup. Это значит, что для полноценной работы
Берегите себя, изолируйте свои процессы и будьте в безопасности! 😉❤️
#tools #devops #untrusted_code
Представьте, что вам нужно запустить некоторый процесс Linux в изолированной среде. Так, чтобы он не смог нанести вред системе, на которой запускается. Какие инструменты мы используем в таких случаях? Первое, что приходит в голову, это технологии виртуализации или контейнеризации. Но что, если нам нужно что-то более легковесное и простое в использовании? На самом деле, такой инструмент есть — это утилита командной строки Bubblewrap, один из проектов сообщества Containers (знаменито авторством таких инструментов, как Podman, Buildah, Skopeo и др.).
Когда может быть полезен:
Установка:
apt-get install bubblewrap
Использование:
bwrap --ro-bind / / echo 'Hello, Bubblewrap!'
В приведенном примере команда
echo выполнится в изолированной среде, в которой структура файловой системы один к одному повторяет структуру host-машины, однако файлы в ней будут доступны только для чтения (--ro-bind, read-only bind). Поэтому следующая команда:bwrap --ro-bind / / sh -c 'echo Hello, Bubblewrap! > /tmp/out.txt'
выдаст ошибку:
sh: 1: cannot create /tmp/out.txt: Read-only file system
На самом деле
bwrap имеет кучу настроек, используя которые можно ограничить очень многие аспекты запускаемого процесса, включая использование ресурсов системы и даже вызов указанных системных функций. Чтобы быстрей сориентироваться, как ей пользоваться, можно посмотреть примеры изоляции некоторых популярных программ.Таким образом, используя аргументы командной строки, вы сами определяете уровень изоляции запускаемого процесса. При этом под капотом Bubblewrap использует стандартные средства Linux, которые не требуют повышения привилегий пользователя: chroot, clone, seccomp, cgroups, landlock.
Основные плюсы:
Основной минус — это то, что ограничение на ресурсы системы устанавливаются только с использованием cgroup. Это значит, что для полноценной работы
bwrap в Docker/Kubernetes-контейнере последний придется запустить в privileged-режиме или лучше с указанием в capabilities привилегии SYS_ADMIN.Берегите себя, изолируйте свои процессы и будьте в безопасности! 😉
#tools #devops #untrusted_code
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥6👍1🤝1
Устранение уязвимостей в коде
Сегодня решил рассказать об одном аспекте в разработке ПО, про который очень часто несправедливо забывают. Это способы изоляции приложения и системы, в которой это приложение исполняется.📍
Какой бы классный и неуязвимый код мы ни писали ⚡️, мы используем внешние зависимости — сторонние библиотеки, которые могут содержать различные уязвимости. 🐞 Поэтому будет справедливо утверждать, что любой код имеет уязвимости. Раз так, то давайте разберемся с тем, как минимизировать свои риски.
Если код содержит уязвимости, он должен исполняться в изолированной среде, которая запретит доступ к тем частям системы, которые могут быть скомпрометированы. Классически выделяют три подхода изоляции процессов ОС.
1️⃣ Wrapper / Обертка. Изолируемый процесс запускается с помощью программы, которую называют "обертка" (wrapper). Перед запуском изолируемого процесса обертка создает необходимое окружение для его исполнения, что чаще всего заключается в ограничении прав и лимитировании ресурсов. После этого изолируемый процесс запускается внутри предоставленного окружения, т.е. "оборачивается" в него. Этот подход универсальный и простой, используется для изоляции сторонних приложений. По этому принципу сделаны cgroup (cgexec), ранее рассмотренный Bubblewrap, любые другие средства контейнеризации. 🐳 Основной недостаток — окружение создается со всеми правами, которые могут понадобится для работы изолируемого процесса (даже если они нужные ему только однажды, например, при старте или инициализации).
2️⃣ Self-sandboxing / Самоизоляция. Приложение самостоятельно накладывает ограничения на доступ к некоторым аспектам системы. Кто лучше всех знает, что именно нужно приложению для его работы? Правильно, разработчик этого приложения. 🧑💻 Если, например, мы знаем, что доступ к файловой системе нам нужен только на этапе запуска, когда мы считываем конфигурационный файл, то сразу после этого мы можем наложить самозапрет на доступ к файлам. Ясно, что такой подход уменьшает уязвимость всего остального кода приложения. Основной недостаток — динамическая самоизоляция может неожиданно нарушить работу других частей приложения, поэтому ее лучше применять для каких-то базовых ограничений (например, доступ к файловой системе) или в простых приложениях.
3️⃣ Broker / Брокер. Изолируемый процесс получает доступ к системе через посредника — брокера. 👨🏻⚖️ Таким образом, брокер контролирует все обращения к любым системным ресурсам. Этот подход отлично себя зарекомендовал, используется в Chromium, Firefox, мобильных платформах (Android, iOS). Большие усилия в развитии этого подхода для графических приложений Linux предприняты в рамках проекта D-Bus. Основной недостаток — сложность реализации и снижение производительности приложения из-за накладных расходов брокера на виртуализацию доступа к системным ресурсам.
Все представленные подходы могут комбинироваться друг с другом.
Как мне кажется, "Самоизоляция" — недооцененный подход. Был ли у вас опыт его использования? 😉
#arch #untrusted_code
Сегодня решил рассказать об одном аспекте в разработке ПО, про который очень часто несправедливо забывают. Это способы изоляции приложения и системы, в которой это приложение исполняется.
Какой бы классный и неуязвимый код мы ни писали ⚡️, мы используем внешние зависимости — сторонние библиотеки, которые могут содержать различные уязвимости. 🐞 Поэтому будет справедливо утверждать, что любой код имеет уязвимости. Раз так, то давайте разберемся с тем, как минимизировать свои риски.
Если код содержит уязвимости, он должен исполняться в изолированной среде, которая запретит доступ к тем частям системы, которые могут быть скомпрометированы. Классически выделяют три подхода изоляции процессов ОС.
Все представленные подходы могут комбинироваться друг с другом.
Как мне кажется, "Самоизоляция" — недооцененный подход. Был ли у вас опыт его использования? 😉
#arch #untrusted_code
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5🔥2🤝1
Структура, высеченная в камне
Сегодня будет небольшой манифест. :) Часто сталкиваюсь с одним очень интересным фактом. Большинство разработчиков с большей охотой идут на изменения в коде, нежели на изменение в структуре проекта. Даже самые очевидные аргументы в этом вопросе работают плохо.
Структура проекта должна отражать модель предметной области, указывая на крупные функциональные блоки: модули и компоненты системы. Структура должна строится на основе общих правил и соглашений. Удачно структурированный проект рассказывает о себе и архитектуре решения с самого начала.
Сначала я вижу общую картину — модули системы, названия которых указывают на их функциональную роль или служат заголовком соответствующего поддомена. На уровне модулей я вижу пакеты (или пространства имен), названия которых дают мне четкое представление о их содержимом и позволяют получить базовое представление о внутреннем устройстве модуля. Далее я вижу отдельно взятые компоненты, классы, записи и т.п. Их имена также должны являться отражением содержания и/или намерения. И так вплоть до названий переменных. Всё это должно задавать невидимый вектор, интуитивно направляющий исследователя вашего кода к тому, что он ищет или хочет понять. Всё должно работать, как содержание книги: если мне нужны детали, я перейду в нужный подраздел, но одного содержания мне должно быть достаточно, чтобы понять, о чем книга или глава.
Не меньшую роль в повествовании играет граф зависимостей. Он ярко иллюстрирует все недочеты существующей структуры. Постройте граф зависимостей для своих модулей, пакетов или компонентов; проследите все взаимосвязи и оцените, насколько они логичны и корректны. Здесь также желательно спускаться сверху вниз, углубляясь в контекст по мере необходимости.
Что задаёт структуру проекта и что она отражает?
🟢 Модель предметной области.
🟢 Архитектура и технические решения.
🟢 Внутренние соглашения команды/компании.
При изменении любого аспекта из этого списка должна меняться и структура проекта. Настолько масштабно, насколько было исходное воздействие. Если вы этого регулярно не делаете, вы нарушаете конструкцию своего сооружения. Можете воспринимать это так, как если бы из основания вашей постройки вынули несколько кирпичей. Сколько еще нужно вынуть кирпичей, чтобы всё начало рушиться?
Точно так же, как мы осуществляем рефакторинг кода, мы должны делать и рефакторинг структуры — реструктуризацию. В противном случае внешняя оболочка и содержимое очень быстро разъедутся, и развитие проекта будет усложняться пропорционально этому разрыву. В какой-то момент уровень входа разработчиков в проект станет слишком высоким, появятся "незаменимые" сотрудники, на которых держится всё. Это путь в один конец, и он достаточно печальный.
Структура проекта не высечена в камне, это такой же код, который должен подвергаться рефакторингу!
P.s. Кстати, залогом успеха в этом деле является унификация, стандартизация и тоталитарный контроль. Все эти вещи очень хорошо поддаются автоматизации и тестированию.
#view #arch #dev
Сегодня будет небольшой манифест. :) Часто сталкиваюсь с одним очень интересным фактом. Большинство разработчиков с большей охотой идут на изменения в коде, нежели на изменение в структуре проекта. Даже самые очевидные аргументы в этом вопросе работают плохо.
Структура проекта должна отражать модель предметной области, указывая на крупные функциональные блоки: модули и компоненты системы. Структура должна строится на основе общих правил и соглашений. Удачно структурированный проект рассказывает о себе и архитектуре решения с самого начала.
Сначала я вижу общую картину — модули системы, названия которых указывают на их функциональную роль или служат заголовком соответствующего поддомена. На уровне модулей я вижу пакеты (или пространства имен), названия которых дают мне четкое представление о их содержимом и позволяют получить базовое представление о внутреннем устройстве модуля. Далее я вижу отдельно взятые компоненты, классы, записи и т.п. Их имена также должны являться отражением содержания и/или намерения. И так вплоть до названий переменных. Всё это должно задавать невидимый вектор, интуитивно направляющий исследователя вашего кода к тому, что он ищет или хочет понять. Всё должно работать, как содержание книги: если мне нужны детали, я перейду в нужный подраздел, но одного содержания мне должно быть достаточно, чтобы понять, о чем книга или глава.
Не меньшую роль в повествовании играет граф зависимостей. Он ярко иллюстрирует все недочеты существующей структуры. Постройте граф зависимостей для своих модулей, пакетов или компонентов; проследите все взаимосвязи и оцените, насколько они логичны и корректны. Здесь также желательно спускаться сверху вниз, углубляясь в контекст по мере необходимости.
Что задаёт структуру проекта и что она отражает?
При изменении любого аспекта из этого списка должна меняться и структура проекта. Настолько масштабно, насколько было исходное воздействие. Если вы этого регулярно не делаете, вы нарушаете конструкцию своего сооружения. Можете воспринимать это так, как если бы из основания вашей постройки вынули несколько кирпичей. Сколько еще нужно вынуть кирпичей, чтобы всё начало рушиться?
Точно так же, как мы осуществляем рефакторинг кода, мы должны делать и рефакторинг структуры — реструктуризацию. В противном случае внешняя оболочка и содержимое очень быстро разъедутся, и развитие проекта будет усложняться пропорционально этому разрыву. В какой-то момент уровень входа разработчиков в проект станет слишком высоким, появятся "незаменимые" сотрудники, на которых держится всё. Это путь в один конец, и он достаточно печальный.
Структура проекта не высечена в камне, это такой же код, который должен подвергаться рефакторингу!
P.s. Кстати, залогом успеха в этом деле является унификация, стандартизация и тоталитарный контроль. Все эти вещи очень хорошо поддаются автоматизации и тестированию.
#view #arch #dev
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥6👍3🤝1
Теперь уже официальный анонс. 😊 Приходите, будет интересно! 😉 #conf #untrusted_code
🔥3
Forwarded from TechLead Conf Channel
Поговорим о теме безопасного исполнения ненадежного кода (untrusted code). Любого программного кода, утилит или скриптов, которые по каким-то причинам не вызывают доверия. Например, из-за ненадежности поставщика или из-за политик безопасности компании🔖
В своем докладе «Безопасное исполнение ненадежного кода» Александр Межов (Инфиннити) пройдется по всем уровням, начиная с оркестратора контейнеров до особенностей ядра и системных функций операционной системы. А также поделится техниками, которые на практике подтвердили свою пригодность.
Александр расскажет о подходах и инструментах, которые помогут сделать вашу разработку более безопасной. За подробностями о борьбе с уязвимостями не только на уровне внедрения зависимостей приходите на доклад.
🔗Узнать больше об этом и других докладах, а также купить билет можно на сайте.
📌До встречи на TechLead Conf X 2025!
В своем докладе «Безопасное исполнение ненадежного кода» Александр Межов (Инфиннити) пройдется по всем уровням, начиная с оркестратора контейнеров до особенностей ядра и системных функций операционной системы. А также поделится техниками, которые на практике подтвердили свою пригодность.
Александр расскажет о подходах и инструментах, которые помогут сделать вашу разработку более безопасной. За подробностями о борьбе с уязвимостями не только на уровне внедрения зависимостей приходите на доклад.
🔗Узнать больше об этом и других докладах, а также купить билет можно на сайте.
📌До встречи на TechLead Conf X 2025!
🔥7🏆1
Выбор UUID для первичного ключа таблицы
В базах данных, где в качестве алгоритма хранения первичного ключа используется B-tree, отдается предпочтение целочисленным типам данных. Это связано с рядом причин, включая производительность. Но что, если в качестве ключа нужно использовать UUID/GUID? Насколько сильно это повлияет на производительность? А можно ли сделать так, чтобы это влияние было сведено к минимуму?
Чтобы разобраться с этими вопросами и развеять всевозможные мифы, я сделал небольшое исследование и написал статью.⬇️ Как оказалось, что в ряде случаев, вопреки расхожему мнению, UUID может улучшить производительность как чтения, так и записи.
#db #dev
В базах данных, где в качестве алгоритма хранения первичного ключа используется B-tree, отдается предпочтение целочисленным типам данных. Это связано с рядом причин, включая производительность. Но что, если в качестве ключа нужно использовать UUID/GUID? Насколько сильно это повлияет на производительность? А можно ли сделать так, чтобы это влияние было сведено к минимуму?
Чтобы разобраться с этими вопросами и развеять всевозможные мифы, я сделал небольшое исследование и написал статью.
#db #dev
Please open Telegram to view this post
VIEW IN TELEGRAM
Дзен | Статьи
Выбор UUID для первичного ключа таблицы
Статья автора «Архитектоника в ИТ» в Дзене ✍: В базах данных, где в качестве алгоритма хранения первичного ключа используется B-tree, отдается предпочтение целочисленным типам данных.
🔥6⚡1👍1
Lamport Timestamp
В далёком 1978 году Leslie Lamport предложил простой и эффективный способ определения порядка событий в распределённой системе. Позже этот алгоритм так и назвали: Lamport Timestamp. Давайте разберёмся, как данный алгоритм можно использовать на практике. В качестве примера рассмотрим прикладную задачу.🛠
Контекст
Существует таблица, где в качестве первичного ключа используется целочисленный идентификатор. Это типичная и привычная ситуация для монолитных баз данных (PostgreSQL, MySQL и т.п.). Подобные базы имеют встроенный механизм автоматической генерации целочисленных идентификаторов, который можно подключить при создании таблицы. Многие распределённые базы не имеют подобной функциональности, также как и ACID-транзакций, а в качестве первичного ключа предлагают использовать UUID.
Предположим, что по каким-то причинам нет возможности использовать UUID в качестве первичного ключа. Например, производится миграция из существующей монолитной базы в распределенную.
Один из вариантов решения — внешний генератор последовательности. Например, всё та же монолитная база (см.
Проблема
Несколько экземпляров приложения (далее для краткости — клиентов) обращаются к распределённой базе данных с целью добавления новых записей, присваивая каждой уникальный целочисленный идентификатор. При этом база данных не поддерживает ACID-транзакции. В этих условиях нужно решить задачу генерации уникальной согласованной целочисленной последовательности в распределённой среде. Любые коллизии недопустимы.
Решение
В целевой базе данных создать таблицу
🕚
🕚
🕚
Таблица хранит именованные пары
В качестве примера будем использовать Apache Cassandra:
1️⃣ Увеличение счётчика
Каждый клиент знает имя последовательности, имеет уникальный идентификатор и хранит максимальное значение последовательности, о котором он знает. Для генерации нового значения выполняется UPDATE-запрос вида:
Запись обновится только в том случае, если текущее значение совпадает со значением, которое было известно клиенту.
2️⃣ Проверка счётчика
Для проверки успешности обновления счётчика выполняется SELECT-запрос:
Клиент запоминает полученное актуальное значение последовательности и сравнивает свой уникальный идентификатор с полученным. Если идентификаторы идентичны, значит, предыдущий запрос выполнен успешно и можно использовать полученное значение. Если идентификаторы отличаются, значит, нужно повторить все действия с первого шага.
Для минимизации обращений к базе данных и количества возможных конфликтов крайне рекомендуется, чтобы клиент резервировал диапазон значений.
📱 Реализация подхода умещается в один небольшой файл. Пример написан на Java для Apache Cassandra, но его легко можно портировать на любой другой язык и базу данных.
Плюсы
Наипростейшая реализация. База данных полностью самодостаточна.
Минусы
При запросе диапазонов значений возможны некоторые пропуски из-за перезапуска клиента.
〰️ 〰️ 〰️
P.s. Сталкивались ли вы с подобной проблемой на практике и как её решали? 😉
#tip #db
В далёком 1978 году Leslie Lamport предложил простой и эффективный способ определения порядка событий в распределённой системе. Позже этот алгоритм так и назвали: Lamport Timestamp. Давайте разберёмся, как данный алгоритм можно использовать на практике. В качестве примера рассмотрим прикладную задачу.
Контекст
Существует таблица, где в качестве первичного ключа используется целочисленный идентификатор. Это типичная и привычная ситуация для монолитных баз данных (PostgreSQL, MySQL и т.п.). Подобные базы имеют встроенный механизм автоматической генерации целочисленных идентификаторов, который можно подключить при создании таблицы. Многие распределённые базы не имеют подобной функциональности, также как и ACID-транзакций, а в качестве первичного ключа предлагают использовать UUID.
Предположим, что по каким-то причинам нет возможности использовать UUID в качестве первичного ключа. Например, производится миграция из существующей монолитной базы в распределенную.
Один из вариантов решения — внешний генератор последовательности. Например, всё та же монолитная база (см.
SEQUENCE); какой-либо координатор (ZooKeeper, etcd и т.п.); распределённые кэши. Обычно используют то, что уже есть в окружении. Основной недостаток — счетчик может быть сброшен независимо от базы, в которой он используется. Например, в следствии восстановления базы из бэкапа.Проблема
Несколько экземпляров приложения (далее для краткости — клиентов) обращаются к распределённой базе данных с целью добавления новых записей, присваивая каждой уникальный целочисленный идентификатор. При этом база данных не поддерживает ACID-транзакции. В этих условиях нужно решить задачу генерации уникальной согласованной целочисленной последовательности в распределённой среде. Любые коллизии недопустимы.
Решение
🗂 Иллюстрация шагов алгоритма приведена в заголовке статьи.🔼 Клиенты A и B пытаются последовательно увеличить на 1 значение счетчика c в базе, изначально полагая, что значение этого счетчика равно 0. При обращении к базе каждый из клиентов отправляет свой уникальный идентификатор n.
В целевой базе данных создать таблицу
counters с тремя колонками:name — имя последовательности;node — уникальный идентификатор клиента;value — последнее использованное значение последовательности.Таблица хранит именованные пары
(node,value), которые и являются Lamport Timestamps.В качестве примера будем использовать Apache Cassandra:
CREATE TABLE counters (
name text PRIMARY KEY,
node uuid,
value bigint
)
Каждый клиент знает имя последовательности, имеет уникальный идентификатор и хранит максимальное значение последовательности, о котором он знает. Для генерации нового значения выполняется UPDATE-запрос вида:
UPDATE counters SET
node = <client UUID>,
value = <new value> -- <old value> + 1
WHERE name = <seq name>
IF value = <old value>
Запись обновится только в том случае, если текущее значение совпадает со значением, которое было известно клиенту.
Для проверки успешности обновления счётчика выполняется SELECT-запрос:
SELECT node, value
FROM counters
WHERE name = <seq name>
Клиент запоминает полученное актуальное значение последовательности и сравнивает свой уникальный идентификатор с полученным. Если идентификаторы идентичны, значит, предыдущий запрос выполнен успешно и можно использовать полученное значение. Если идентификаторы отличаются, значит, нужно повторить все действия с первого шага.
Для минимизации обращений к базе данных и количества возможных конфликтов крайне рекомендуется, чтобы клиент резервировал диапазон значений.
Плюсы
Наипростейшая реализация. База данных полностью самодостаточна.
Минусы
При запросе диапазонов значений возможны некоторые пропуски из-за перезапуска клиента.
P.s. Сталкивались ли вы с подобной проблемой на практике и как её решали? 😉
#tip #db
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥5👍2⚡1
Чего не написано - того нет
Такую фразу неустанно повторял мой преподаватель в университете. Так он учил нас правильно воспринимать требования в задачах по электротехнике. 😃 Однако я благополучно применяю этот принцип до сих пор и в разных сферах. В нужный момент позволяет остановить галлюцинирование и пойти за уточнением требований. 😃 Особенно хорошо принцип работает в программировании: если вы долго не можете придумать название компоненту, наиболее вероятно, его не существует в природе, остановитесь!🚩 Это серьезный звонок, игнорирование которого чаще всего приводит к неоправданному усложнению решения. Если вы уверены, что это не так, то можно вас поздравить. В очень редких случаях подобная ситуация — свидетельство того, что вы открыли для себя то, чего не знали ранее. Эрик Эванс назвал это результатом переработки (дистилляции) знаний предметной области, который обычно сопровождается качественным скачком в архитектуре. Если это так, то стойте на своем! Однако и в том, и в другом случае нужно быть крайне осторожным, осмотрительным, сделать паузу, выдохнуть и еще раз проанализировать ситуацию. Лучше всего начать с обсуждения с кем-то из своей команды, а далее эскалировать возникший вопрос по мере необходимости.
#view
Такую фразу неустанно повторял мой преподаватель в университете. Так он учил нас правильно воспринимать требования в задачах по электротехнике. 😃 Однако я благополучно применяю этот принцип до сих пор и в разных сферах. В нужный момент позволяет остановить галлюцинирование и пойти за уточнением требований. 😃 Особенно хорошо принцип работает в программировании: если вы долго не можете придумать название компоненту, наиболее вероятно, его не существует в природе, остановитесь!
#view
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4💯2🔥1🤝1
Event Storming
Как и многим, мне нравятся простые визуальные нотации, с помощью которых можно быстро понять суть дела. Считаю, что за такими инструментами будущее. Поэтому на прошедшей онлайн-конференции NextConf в центре моего внимания был доклад и воркшоп по Event Storming (не путать с Event Sourcing). Сам метод мне знаком, но всегда была интересна практическая часть. Поделюсь своими открытиями.
Кто не в курсе, скажу, что Event Storming — это визуальная нотация для описания (бизнес- и не только) процессов. Описание делается на (онлайн) доске с помощью стикеров. Стикеры — разноцветные, каждый цвет имеет свой смысл. Стикеры выкладываются на доске слева направо, т.е. в направлении невидимой временной оси. Такая цепочка служит описанием последовательности действий какого-то процесса. За свою простоту, понятность и эффективность метод получил признание среди системных аналитиков и архитекторов и активно набирает популярность. Помимо этого, он очень хорошо сочетается с техниками DDD, что в том числе помогает визуально идентифицировать Bounded Contexts (и наглядно находить границы микросервисов).
Теперь важные моменты, на которые обратили внимание опытные пользователи Event Storming.
Этапы встреч
⭐️ Обсуждение проблематики (Big Picture). Состав: представители бизнеса, владелец продукта, бизнес- и системный аналитик, возможно, архитектор.
⭐️ ⭐️ Обсуждение бизнес-процессов (Processing Model). Состав: уменьшается количество представителей бизнеса, добавляются технические специалисты.
⭐️ ⭐️ ⭐️ Проектирование решения (Software Design). Состав: исключаются представители бизнеса, только архитекторы, разработчики и технические специалисты.
Перед встречей
☑️ Обозначить цель и ограничить время. Будет гораздо продуктивней, если до начала встречи каждый подумает на обозначенную тематику, а в идеале — подготовится.
☑️ Продумать и ограничить состав участников. Собираем только тех, у кого есть вопросы и у кого есть на них ответы. Меньше людей — больше фокус на результате.
Во время встречи
☑️ Выбрать ведущего. Не принципиально, кто им будет. Чаще всего - это наиболее опытный пользователь Event Storming.
☑️ Объяснить правила. Этот шаг необходим только в том случае, если имеется участник, который не знаком с Event Storming.
☑️ Напомнить про цель и ограничение по времени. Это коллективное мероприятие, своеобразный "мозговой штурм". Четко поставленная цель и жесткие временные рамки позволяют сфокусироваться на результате и не распыляться. Не успели, значит, планируем еще одну встречу. В итоге это дисциплинирует.
☑️ Убедиться в понимании поставленной цели. Опыт показывает, что многие приходят неподготовленными. Поэтому до начала нужно устранить все недопонимания. В противном случае некоторые участники не смогут внести свой вклад в результат, и их ресурс будет потрачен впустую.
☑️ Заставьте работать всех. Если кто-то молчал, пусть зачитывает стикеры, проверяя логичность цепочки действий. Это позволяет вовлечь в процесс всех, даже самых застенчивых. Они могут молчать, но иметь важное мнение.
☑️ Главное - добиться цели. Суть не в детализации, а в ответе на поставленный вопрос. По этой причине можно опускать детали (стикеры), которые очевидны или несущественны.
Шаги проведения "Big Picture"
1️⃣ Неструктурированное исследование. Выписываем все события предметной области, которые приходят в голову. Фокус — на позитивных сценариях. Каждое событие формулируется в прошедшем времени (заказ оформлен, оценка получена и т.п.). Порядок и структура не важны, фиксируем всё без фильтрации.
2️⃣ Выстраивание событий в хронологическом порядке. Упорядочиваем все собранные события в хронологическом порядке. Проверяем логичность последовательности; читаем цепочку слева направо и наоборот. Ориентируемся только на то, что написано на стикерах.
3️⃣ Поиск ключевых событий. Выбираем ключевые события. Определяем наиболее важные события с точки зрения бизнеса. Они влияют на ход процесса, меняют состояние системы, разделяют модель на смысловые блоки. Их визуально укрупняют или выделяют.
#conf #arch #tools
Как и многим, мне нравятся простые визуальные нотации, с помощью которых можно быстро понять суть дела. Считаю, что за такими инструментами будущее. Поэтому на прошедшей онлайн-конференции NextConf в центре моего внимания был доклад и воркшоп по Event Storming (не путать с Event Sourcing). Сам метод мне знаком, но всегда была интересна практическая часть. Поделюсь своими открытиями.
Кто не в курсе, скажу, что Event Storming — это визуальная нотация для описания (бизнес- и не только) процессов. Описание делается на (онлайн) доске с помощью стикеров. Стикеры — разноцветные, каждый цвет имеет свой смысл. Стикеры выкладываются на доске слева направо, т.е. в направлении невидимой временной оси. Такая цепочка служит описанием последовательности действий какого-то процесса. За свою простоту, понятность и эффективность метод получил признание среди системных аналитиков и архитекторов и активно набирает популярность. Помимо этого, он очень хорошо сочетается с техниками DDD, что в том числе помогает визуально идентифицировать Bounded Contexts (и наглядно находить границы микросервисов).
Теперь важные моменты, на которые обратили внимание опытные пользователи Event Storming.
Этапы встреч
Перед встречей
Во время встречи
Шаги проведения "Big Picture"
#conf #arch #tools
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4🔥2🤝1
Слабая или сильная изоляция транзакций (1/2)
Сегодня я решил затронуть больную и холиварную для многих тему. Но я не вижу иного варианта для пятницы, после очередного сезона Podlodka. Поэтому предлагаю ознакомиться с тезисами и приступить к обсуждению или, по крайней мере, задуматься. 😃
😮💨 Слабая изоляция
Использование более слабого уровня изоляции транзакций не всегда означает компромисс между согласованностью данных и производительностью выполнения запросов. Всё зависит от данных и выполняемых операций. Поэтому в некоторых случаях слабый уровень изоляции действительно может бескомпромиссно обеспечить лучшую производительность.😛
Наряду с этим считается, что более слабая изоляция подходит для большинства ситуаций, а разработчики приложений должны быть достаточно прозорливыми, чтобы выявлять случаи, когда требуется более сильная изоляция, и использовать её по мере необходимости.😏
На практике я не видел примеры удачного жонглирования уровнями изоляции. В лучшем случае запросы на чтение идут с одним уровнем, а на запись — с другим. Чаще всего ультимативно используется что-то типа Read Committed, а прозорливость начинает просыпаться только в момент, когда не проходит интеграционный тест или кто-то находит ошибку.
Данная ситуация усугубляется в том числе разночтениями SQL-стандарта при реализации уровней изоляции. Мартин Клеппманн также подмечает, что это создает неопределенность для разработчиков в понимании того, есть ли в коде приложения ошибки.
В свою очередь, гонки по данным — одна из самых противных ошибок. Её сложно обнаружить и воспроизвести. И зачастую совсем непросто понять, что пошло не так, чтобы исправить.❤️ Пока что не созданы надёжные и удобные инструменты для обнаружения подобных ошибок. А из этого можно сделать вывод, что слабый уровень изоляции — это очень большой кредит, который берет на себя разработчик.
💪 Сильная изоляция
Сильные уровни изоляции транзакций исключают многие аномалии. Возможные баги в большинстве своём будут связаны с бизнес-логикой, а не со спецификой работы базы данных. Такие ошибки хорошо отлавливаются на этапе автоматизированного тестирования. Сам же код становится более простым, не содержащим различных ухищрений, нацеленных на борьбу с аномалиями.😛
Да, старые версии реляционных баз данных приучили нас, что сильные уровни изоляции, такие как Serializable, ставят крест на производительности. Но время не стоит на месте, и технологии хранения развиваются. Например, с появлением SSI, MVCC, Calvin и т.п. реализация Serializable стала намного производительней, чем на базе блокировок.💪
Также не стоит забывать, что концепция ACID-транзакций не подразумевает компромиссов относительно изоляции. Уровни изоляции — это компромисс между согласованностью и производительностью, на который разработчик должен пойти осознанно. Но зачем идти на компромиссы до того, как будет сделано первое нагрузочное тестирование и собраны первые метрики? Как мне кажется, это резонный вопрос.👔
#db #arch
⬇️ ⬇️ ⬇️ ⬇️ ⬇️
Сегодня я решил затронуть больную и холиварную для многих тему. Но я не вижу иного варианта для пятницы, после очередного сезона Podlodka. Поэтому предлагаю ознакомиться с тезисами и приступить к обсуждению или, по крайней мере, задуматься. 😃
Использование более слабого уровня изоляции транзакций не всегда означает компромисс между согласованностью данных и производительностью выполнения запросов. Всё зависит от данных и выполняемых операций. Поэтому в некоторых случаях слабый уровень изоляции действительно может бескомпромиссно обеспечить лучшую производительность.
Наряду с этим считается, что более слабая изоляция подходит для большинства ситуаций, а разработчики приложений должны быть достаточно прозорливыми, чтобы выявлять случаи, когда требуется более сильная изоляция, и использовать её по мере необходимости.
На практике я не видел примеры удачного жонглирования уровнями изоляции. В лучшем случае запросы на чтение идут с одним уровнем, а на запись — с другим. Чаще всего ультимативно используется что-то типа Read Committed, а прозорливость начинает просыпаться только в момент, когда не проходит интеграционный тест или кто-то находит ошибку.
Данная ситуация усугубляется в том числе разночтениями SQL-стандарта при реализации уровней изоляции. Мартин Клеппманн также подмечает, что это создает неопределенность для разработчиков в понимании того, есть ли в коде приложения ошибки.
В свою очередь, гонки по данным — одна из самых противных ошибок. Её сложно обнаружить и воспроизвести. И зачастую совсем непросто понять, что пошло не так, чтобы исправить.
Сильные уровни изоляции транзакций исключают многие аномалии. Возможные баги в большинстве своём будут связаны с бизнес-логикой, а не со спецификой работы базы данных. Такие ошибки хорошо отлавливаются на этапе автоматизированного тестирования. Сам же код становится более простым, не содержащим различных ухищрений, нацеленных на борьбу с аномалиями.
Да, старые версии реляционных баз данных приучили нас, что сильные уровни изоляции, такие как Serializable, ставят крест на производительности. Но время не стоит на месте, и технологии хранения развиваются. Например, с появлением SSI, MVCC, Calvin и т.п. реализация Serializable стала намного производительней, чем на базе блокировок.
Также не стоит забывать, что концепция ACID-транзакций не подразумевает компромиссов относительно изоляции. Уровни изоляции — это компромисс между согласованностью и производительностью, на который разработчик должен пойти осознанно. Но зачем идти на компромиссы до того, как будет сделано первое нагрузочное тестирование и собраны первые метрики? Как мне кажется, это резонный вопрос.
#db #arch
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🔥1
Слабая или сильная изоляция транзакций (2/2)
Естественно, существуют разные данные, разные профили нагрузок, и нет единственно верного решения. Решая задачу, мы также принимаем во внимание свой опыт, без этого никуда. Поэтому вышенаписанное — это не призыв к использованию сильных уровней изоляции транзакций по умолчанию, это, скорей, повод задуматься.
#db #arch
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6🔥2
Инструменты Techlead Crew
Прошедший выпуск Podlodka Techlead Crew был посвящен межсервисному взаимодействию. Как и всегда, были и знакомые вещи, и не очень. В конечном счете, невозможно знать и помнить обо всём. Вот и я выписал себе очередной набор инструментов, которые показались мне крайне интересными. Чем и спешу поделиться.🚀
🤝 Contract First
Продолжаю наблюдать популяризацию идеологии Contract First. С ростом компании растет количество сервисов и продуктов, что неминуемо приводит к необходимости их интеграции и согласования работ между командами разработки. Идеология Contract First — один из инструментов для решения этих проблем, масштабирования компании и процессов. Тема абсолютно не новая, но с популяризацией микросервисов и изобилием протоколов и технологий межсервисного взаимодействия вновь начала набирать популярность.
Взяв курс на Contract First, очень важен вопрос выбора инструментов, поскольку они должны унифицировать процесс, контролировать его и ускорять. Иначе говоря, хотелось бы иметь какой-то стандарт или общий подход к описанию API (хотя бы на уровне компании), возможность верифицировать спецификации (делать ревью, архитектурный контроль, тестирование контрактов) и создавать их максимально быстро и удобно.
⚙ TypeSpec
⚙ AsyncAPI
⚙ OpenAPI-diff
⚙ Spring Cloud Contract
⚙ Pact
Несмотря на существование достойной инструментальной поддержки, ряд коллег высказывают озабоченность. Во-первых, остается вопрос, кто именно должен создавать и развивать спецификации (архитектор, системный аналитик, техлид, разработчик). Во-вторых, спецификация сильно оторвана от кода (реальной жизни), что может вызвать проблемы на уровне её реализации. Отсюда вытекает третье сомнение: при большом числе интеграций создание спецификаций вручную выглядит ресурсоемким и опять же может разъехаться с реализацией. Иначе говоря, было сделано предположение, что в идеале спецификация должна определяться на языке, на котором ведется разработка, тогда это сократит "разрыв" до нуля. В конечном счете сошлись на том, что универсального решения не существует и каждый ведёт дела так, как ему удобно.
✍️ Implementation
В современной практике очень часто можно встретить необходимость гетерогенного хранения данных или необходимость в транзакционной логике при выполнении бизнес-процессов с участием нескольких (микро)сервисов. Всё выглядит так, что Temporal позволяет достаточно быстро добавить Saga в существующий проект, особенно если он написан без использования таких структурообразующих фреймворков, как Spring.
Еще одна интересная, но очень сложная задача — это сервис push-уведомлений. Если вы не можете или не готовы использовать какой-нибудь облачный сервис, то писать свою реализацию, пожалуй, не самый лучший вариант. В такой ситуации на помощь приходит Centrifugo.
👀 Observability
Было очень много сказано про техники обеспечения прочности и устойчивости. Два ярких момента, на которые я обратил внимание.
1️⃣ Крупные компании стараются перекладывать всё больше ответственности с кода на инфраструктуру. Например, выполнение некоторых повторов (Retry) можно возложить на инфраструктуру. Однако на этом уровне имеет смысл повторять только HTTP 5xx.
2️⃣ Распределенная трассировка на базе OpenTelemetry — это неотъемлемая часть современного приложения.
При этом было отмечено, что очень важно не маскировать бизнес-ошибки за кодами HTTP 2xx (например, "HTTP 200 Недостаточно средств"). Это создает серьезные проблемы при сборе статистики о работе системы на уровне инфраструктуры.
Что касается распределённой трассировки, то справедливо подмечено, что её хранение может обойтись очень дорого, причём большая часть событий будет неинтересна. По этой причине следует рассматривать данный подход в сочетании с сэмплированием — выдергивать из потока и сохранять только те события, которые потенциально могут представлять интерес.
〰️ 〰️ 〰️
💬 Предлагаю в комментариях делиться опытом работы с этими инструментами. 😉
#tools
Прошедший выпуск Podlodka Techlead Crew был посвящен межсервисному взаимодействию. Как и всегда, были и знакомые вещи, и не очень. В конечном счете, невозможно знать и помнить обо всём. Вот и я выписал себе очередной набор инструментов, которые показались мне крайне интересными. Чем и спешу поделиться.
🗂 Более подробный разбор см. в моей статье на Дзен.
🤝 Contract First
Продолжаю наблюдать популяризацию идеологии Contract First. С ростом компании растет количество сервисов и продуктов, что неминуемо приводит к необходимости их интеграции и согласования работ между командами разработки. Идеология Contract First — один из инструментов для решения этих проблем, масштабирования компании и процессов. Тема абсолютно не новая, но с популяризацией микросервисов и изобилием протоколов и технологий межсервисного взаимодействия вновь начала набирать популярность.
Взяв курс на Contract First, очень важен вопрос выбора инструментов, поскольку они должны унифицировать процесс, контролировать его и ускорять. Иначе говоря, хотелось бы иметь какой-то стандарт или общий подход к описанию API (хотя бы на уровне компании), возможность верифицировать спецификации (делать ревью, архитектурный контроль, тестирование контрактов) и создавать их максимально быстро и удобно.
Несмотря на существование достойной инструментальной поддержки, ряд коллег высказывают озабоченность. Во-первых, остается вопрос, кто именно должен создавать и развивать спецификации (архитектор, системный аналитик, техлид, разработчик). Во-вторых, спецификация сильно оторвана от кода (реальной жизни), что может вызвать проблемы на уровне её реализации. Отсюда вытекает третье сомнение: при большом числе интеграций создание спецификаций вручную выглядит ресурсоемким и опять же может разъехаться с реализацией. Иначе говоря, было сделано предположение, что в идеале спецификация должна определяться на языке, на котором ведется разработка, тогда это сократит "разрыв" до нуля. В конечном счете сошлись на том, что универсального решения не существует и каждый ведёт дела так, как ему удобно.
✍️ Implementation
В современной практике очень часто можно встретить необходимость гетерогенного хранения данных или необходимость в транзакционной логике при выполнении бизнес-процессов с участием нескольких (микро)сервисов. Всё выглядит так, что Temporal позволяет достаточно быстро добавить Saga в существующий проект, особенно если он написан без использования таких структурообразующих фреймворков, как Spring.
Еще одна интересная, но очень сложная задача — это сервис push-уведомлений. Если вы не можете или не готовы использовать какой-нибудь облачный сервис, то писать свою реализацию, пожалуй, не самый лучший вариант. В такой ситуации на помощь приходит Centrifugo.
👀 Observability
Было очень много сказано про техники обеспечения прочности и устойчивости. Два ярких момента, на которые я обратил внимание.
При этом было отмечено, что очень важно не маскировать бизнес-ошибки за кодами HTTP 2xx (например, "HTTP 200 Недостаточно средств"). Это создает серьезные проблемы при сборе статистики о работе системы на уровне инфраструктуры.
Что касается распределённой трассировки, то справедливо подмечено, что её хранение может обойтись очень дорого, причём большая часть событий будет неинтересна. По этой причине следует рассматривать данный подход в сочетании с сэмплированием — выдергивать из потока и сохранять только те события, которые потенциально могут представлять интерес.
#tools
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5🔥1
Миграция данных и HLL
Сегодня я хочу поделиться опытом и наблюдениями в рамках одной очень сложной и интересной задачи. Производим тестирование механизма миграции данных из одной базы данных в другую. Объем очень большой, а модели источника и приемника сильно отличаются. Вместе с этим стоит вторая задача — убедиться, что копирование выполнено корректно.
1️⃣ Самое первое, что приходит в голову, — сравнить количество строк/записей/документов источника и приемника. Действительно, с одной стороны, если модели хранения источника и приемника подобны, то вариант вполне рабочий. Например, перенос простой таблицы из Oracle в PostgreSQL. С другой стороны, на больших объемах могут возникнуть серьезные затруднения. Некоторые базы могут выполнить
2️⃣ Если модели хранения источника и приемника отличаются, то задача подсчета строк становится нетривиальной. В качестве примера можно рассмотреть задачу организации журнала аудита, который содержит события изменения документов. Каждое событие описывается совокупностью атрибутов: ID документа, метка времени, действие, пользователь и т.п. В SQL-базах такой журнал наверняка будет представлен обычной таблицей с колонками, соответствующими атрибутам события. В NoSQL-базах этот же журнал может выглядеть по-разному, и многое будет зависеть от характера запросов (query). Например, если основной сценарий — это запросить все события по документу, тогда в Cassandra журнал может выглядеть в виде записей вида "ID документа - список событий". Ясно, что в этом случае количество записей источника (SQL) и приемника (NoSQL) перестает совпадать.
3️⃣ Сложность подсчета количества строк и разница в моделях данных — это не последняя проблема. Как известно, при миграции данных (обычно) используется механизм CDC (Change Data Capture). Это значит, что в топике приемника может оказаться несколько сообщений для каждой строки источника (по сообщению на каждую операцию Create/Update/Delete). В процессе обработки этих событий могут возникать сбои, что будет приводить к redelivery. Следовательно, вариант с подсчетом количества входящих сообщений и использования этого количества в качестве результирующего числа строк также отпадает. Между тем, эта идея мне показалась вполне интересной, и я задумался, а есть ли способ подсчета количества уникальных элементов в бесконечном потоке данных⁉️
➡️ Конечно, есть, кто бы сомневался! 😃 Один из таких алгоритмов — HyperLogLog (HLL). Это вероятностный алгоритм, оценивающий количество уникальных элементов в потоке данных (data stream). Да, мы не получим точное количество, но мы получим его оценку с погрешностью около 1% (подтверждаю, проверял на практике). В некоторых случаях этого может быть достаточно. Более того, этот алгоритм расходует невероятно мало памяти (используется фиксированный массив размером в несколько Кб). Я не буду описывать детали реализации, лучше расскажу, как можно использовать подобную вероятностную структуру данных на практике (будет псевдокод).
Оценка числа уникальных элементов в потоке данных:
В примере функция
💬 Миграция большого объема данных — это вызов. Конечно, это не только сравнение количества строк, но и сравнение содержимого, много других интересных и сложных вещей. Сегодня я рассказал об одном эпизоде, но задача еще не решена до конца. Друзья, был ли подобный опыт у вас и как вы решали данную задачу? 😉
#db #tip
Сегодня я хочу поделиться опытом и наблюдениями в рамках одной очень сложной и интересной задачи. Производим тестирование механизма миграции данных из одной базы данных в другую. Объем очень большой, а модели источника и приемника сильно отличаются. Вместе с этим стоит вторая задача — убедиться, что копирование выполнено корректно.
count() очень быстро, для других — это смерти подобно. Более того, если в выборке появляется фильтр (копируем не все данные или сложная логика копирования), то при выполнении count() сканирование файлов данных гарантированно (full scan). В таком случае подобное мероприятие слишком рискованно.Оценка числа уникальных элементов в потоке данных:
var hll = new HLL();
hll.add( hash(element1) );
hll.add( hash(element2) );
hll.add( hash(element3) );
...
hll.add( hash(elementN) );
var count = hll.cardinality(); // N±1%
В примере функция
hash() возвращает целочисленный хэш-код элемента. К реализации этой функции нужно подойти ответственно, т.к. от этого во многом будет зависеть получаемая погрешность. Мы на практике использовали MurmurHash, но тут нужно экспериментировать.#db #tip
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8✍1🔥1