Здравствуйте. Меня зовут Олег Юрченко.

Здесь моя рецензия на эту книгу: «Клеппман М. Высоконагруженные приложения. Программирование, масштабирование, поддержка. — СПб.: Питер, 2018.»

Я видел много рекомендаций прочитать это творение, а начав читать, решил написать рецензию.

Содержание книги соответствует второй части оригинального названия «Designing Data‑Intensive Applications. THE BIG IDEAS BEHIND RELIABLE, SCALABLE, AND MAINTAINABLE SYSTEMS».

Можно утверждать, что многие «THE BIG IDEAS BEHIND...» рассмотрены с подробной библиографией. А вот название перевода вводит в заблуждение каждым словом. В книге только идеи, а не это вот всё.

Но главной идеи в книге нет. Мартин Клеппман не знает про основной способ устранения зависимости нагрузки на сервер баз данных от размера базы данных, главной проблемы с быстрорастущими базами данных. Эта идея важнее всего написанного в этой книге, я её объясню с примером из моей практики.

Содержательно комментировать можно только три главы из всей книги, там есть что-то от автора, всё остальное выглядит как реферат студента со ссылками на первоисточники по любому поводу.

Рекомендовать эту книгу к прочтению я не могу. Главная проблема этой книги в том, что она была издана. Возможно, другие рецензенты смогли бы помочь автору исправить недостатки текста. Но вот уровень самого автора...

Сначала про способ решения проблемы быстрорастущих данных, а потом мои комментарии по трём главам книги. 

Идея разделения оперативных и обработанных данных

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

Это лучше объяснять на примере.

Представим бизнес по доставке потерянного авиакомпаниями багажа. Есть авиакомпании, аэропорты, компании по доставке грузов и компания-владелец этого бизнеса, которая организует работу.

Есть сервисы-поставщики информации о потерянном багаже (WorldTracer, ARINC), также данные могут поступать из аэропортов и авиакомпаний.

Бизнес работает по всей территории США и Канады со всеми значимыми аэропортами и парой десятков крупнейших авиакомпаний из разных стран.

На один только аэропорт Майами (MIA) в обычный месяц приходится 30 тысяч ордеров о потерянных сумках, в сезон отпусков - 90 тысяч.

Все данные о потерянном багаже нужных авиакомпаний и аэропортов попадают в базу данных этого бизнеса. Вот пример базы данных с быстрорастущими таблицами для данных ордеров.

После доставки багажа ордера, ордер попадает в формируемый счёт к авиакомпании. Счета большие, ограничений на число ордеров нет, могут быть сотни тысяч ордеров и суммы оплаты могут быть в нескольких валютах.

Данные по выставленному авиакомпании счёту должны храниться в неизменном виде, возможен только просмотр для чтения. Все вычисляемые данные ордеров в таком счёте должны быть посчитаны в день выставления счёта и храниться в посчитанном виде.

Имеем две разных логики работы с ордерами. По одним идёт работа и данные редактируются, а другие уже обработаны, посчитаны и хранятся как есть. Объём текущих рабочих данных зависят от текущей ситуации с бизнесом (в COVID было сокращение в 2-4 раза), а объёмы данных по выставленным счетам быстро растут.

Вот и напрашивается физическое разделение этих данных, все данные по выставленным счетам хранить в отдельных таблицах.

Размер таблиц с оперативными данными в основном стабилен с учётом сезонных колебаний и территориального роста бизнеса. И 99% запросов поисков идёт по оперативным данным, работники работают с ними. Проблема зависимости нагрузки на сервер от размера базы данных решена!

Я попал на этот проект в 2006 году. К тому моменту уже приложение клиенту сделали, но вдруг оказалось, что с ним будут реально работать и будет очень много данных.

Пришлось менять состав работников, надо привести приложение в рабочий вид. Ситуация с уровнем прошлой разработки была такая, что первым делом у меня были работы по включению в базе данных MS SQLServer 2000 опций ANSI стандарта, что даёт полные возможности по индексированию и снимает обещанные MS будущие проблемы при отмене наследия Sybase.

В 2007 году, когда бизнес потихоньку расширился, объёмы данных начали бодро расти, заказчик запросил побыстрее разделить данные ордеров на две части, «горячие» за 2 последних месяца и «старые». Причём, хотел разнести сразу по разным базам данных. Он слышал про эту технику проектирования, но поверхностно. А мне только предстояло познакомиться, но сразу на практике и углублённо. Беспокойство заказчика вполне обоснованно: на сервере баз данных 2 процессора и MS SQL Server 2000 Work Group edition, и лишних денег нет, надо обойтись имеющимся железом и софтом.

Сначала сделал по заданию заказчика, но без разнесения по разным базам данных. Спешить с федеративной базой нельзя, сначала надо в одной базе по разным таблицам разнести и со всеми проблемами познакомиться.

Обнаружились проблемы быстро. Есть две разных логики работы с ордерами, и тут добавилась ещё логика разделения по таблицам. Какие‑то «горячие» данные могут быть уже оплачены и не редактируемы, а какие‑то «старые» могут оказаться редактируемыми и попадать в новые счета. Хаоса добавилось хорошо.

Подумав, решил, что надо разделять не по времени, а по смыслу. И не ордера, а счета. Все данные выставленного на оплату счёта переносятся в таблицы обработанных данных. Получился описанный выше вариант.

Разнесение в разные базы данных так и не понадобилось. Таблицы обработанных данных сделал в отдельной файловой группе для возможного бэкапа по группам файлов. Эта возможность не понадобилась.

Потом уже надо было бы ещё сделать разделение/секционирование быстрорастущих таблиц обработанных данных (partitioned tables, тогда уже редакция используемого SQL Server позволяла), но до этого дело не дошло. Примерно раз в три года бизнес менял владельцев и новые просто просили освободить место на дисках от самых старых и уже никому не нужных данных. Экономию денег никто не отменял.

В 2008 году потребовалось сделать хранилище данных с синхронизацией близкой к реальному времени. Имеющееся разделение данных облегчило работу. Сделал в хранилище такое же разделение данных.

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

Теперь можно обобщить.

Если есть быстрорастущие данные и небольшая часть этих данных обрабатывается/запрашивается намного больше остальной части, то эту часть данных имеет смысл хранить отдельно от остальных данных. Это сразу уменьшит нагрузку по поискам. Запросы по маленьким таблицам работают лучше запросов по таким же, но большим. Если почти все запросы уходят на маленькие таблицы, то будет хорошая экономия вычислительных мощностей. 

В моём случае обработанные данные были неизменяемы. Но перед этим я пару месяцев был в условиях с изменяемыми "старыми" данными. Можно жить и с изменениями, когда выполненная работа вдруг требует какой-то доделки и какие-то данные могут меняться. Этот случай сложен, но возможен.

Разделение по разным базам данных может и не понадобиться. Сначала надо разделить данные в одной базе данных, чтобы разобраться с возможными проблемами и посмотреть на результат.  Причём, делить надо по смыслу, а не по возрасту, разделение данных должно соответствовать логике работы с данными, так будет проще и проблем будет меньше.

Теперь сравнение с шардингом.

Предположим, что в быстрорастущих данных есть 1% оперативных, на которые приходиться 99% работы, обычный такой случай, какие-нибудь ордера покупок/поездок. В случае с разделением оперативных и обработанных данных будут отдельные таблицы для этого 1% данных и 99% запросов будут выполняться там. Если бизнес не растёт, то и размер оперативных данных не растёт, нагрузка на сервер стабильна, нужно будет только добавлять диски для хранения растущих обработанных данных, если они со временем не удаляются. Если все обработанные данные нужно хранить, то придётся для них использовать секционированные таблицы (partitioned tables) или как-то вручную разделять, чтобы работа по ним (1% от общей) не слишком напрягала сервер.

А вот если сделать шардинг на 10 серверов (плюс ещё 10 для отказоустойчивости), с равномерной нагрузкой на сервера, то будем иметь 10 серверов с 10% данных. Поиски оперативных данных будут выполняться на таблицах в 10 раз больших, чем при разделении данных. И, скорее всего, большинство поисков надо будет выполнять на каждом из серверов. Нагрузка на сервера будет увеличиваться даже при неизменном размере бизнеса. Одна радость - объём данных на каждом сервере будет расти в 10 раз медленнее общего роста объёма данных. Так и получается растущий дата-центр вместо серверной комнаты.

Теперь комментарии по главам книги.

Глава 5. Репликация

 Эта глава не про репликацию. Репликация в мире баз данных появилась в прошлом веке. Это набор технологий копирования и распространения данных и объектов баз данных между базами данных, а также синхронизации баз данных для поддержания согласованности. Т.е. какие-то данные и объекты баз данных переносятся куда-то далеко в сколько-угодно мест. Репликация не предназначена для реализации отказоустойчивости. Отказоустойчивость – это совсем другая задача для других инструментов.  Для отказоустойчивости в то время (в прошлом веке) применялось зеркальное отображение баз данных (database mirroring). В конце 20-го века появились кластера отказоустойчивости с разделяемыми ресурсами (например, SQL Server failover clustering в MS SQL Server 7.0). И вот в нынешнем веке появились реализации отказоустойчивости без разделяемых ресурсов (упоминаемые в главе MS SQL Server AlwaysOn Availability Groups и Oracle Data Guard). И вот их Мартин Клеппман почему-то называет «репликацией». Между тем репликация никуда не исчезла, ничем не заменена и продолжает использоваться по своему прямому назначению. Но развиваться репликации некуда, всё основное уже сделано в прошлом веке, в этом веке только небольшие доработки и улучшения. Если что-то писать про репликацию, то надо писать про технологии прошлого века.

Тему репликации Мартин Клеппман не понял и на протяжении всей книги вводит читателей в заблуждение, выдавая за репликацию отказоустойчивость без разделяемых ресурсов. Все примеры про «синхронную/асинхронную репликацию» в этой книге относятся к отличиям режима синхронной фиксации (synchronous-commit mode) от режима асинхронной фиксации (asynchronous-commit mode) в реализациях отказоустойчивости без разделяемых ресурсов.

В наше время можно в поиске Google найти ответы на эти вопросы: «What is the difference between AlwaysOn Availability Groups and replication in MS SQL Server?» и «What is the difference between Data Guard and replication in Oracle?»

Во время написания книги таких поисковых возможностей не было, а изучить тему репликации у Мартина Клеппмана не нашлось времени.

Репликация СУБД:

1. Не предназначена для отказоустойчивости.

2. Реплицируется только нужная часть объектов и данных. Можно выбирать в таблицах нужные для репликации колонки и устанавливать фильтры для записей.

3. Нет ограничений по расположению участников репликации. Ограничения на число участвующих серверов есть только в специализированных видах репликации (например, bidirectional transactional replication в MS SQL Server).

4. Высокая нагрузка по чтению данных на серверах‑подписчиках не влияет на состояние сервера‑издателя.

Отказоустойчивость без разделяемых ресурсов:

1. Предназначена для отказоустойчивости.

2. Все реплики являются полными копиями базы данных, содержат все объекты и данные.

3. Используются кластерные технологии. Есть ограничения на число узлов. Например, MS SQL Server 2016 Always On Availability: «Реплики доступности: Каждая группа доступности поддерживает одну первичную реплику и до восьми вторичных реплик. Все реплики могут выполняться в режиме асинхронной фиксации или до пяти из них могут работать в режиме синхронной фиксации».

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

Репликация и отказоустойчивость – это очень разные задачи, а программы должны делать что-то одно и делать это хорошо, потому для них должны быть разные инструменты СУБД.

Что получится, если потребовать от репликации, чтобы ещё и обеспечивала отказоустойчивость системы:

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

2. Синхронизация данных происходит только внутри кластера.

3. Базы данных внутри кластера являются полными копиями, содержат все объекты и все данные.

Вот и получилась отказоустойчивость без разделяемых ресурсов. Что потеряли:

1. Возможность распространять данные и объекты баз данных на сколько‑угодно серверов.

2. Возможность передавать данные и объекты куда угодно.

3. Возможность передавать только нужное подмножество данных и объектов.

Дополнительное требование к репликации убило суть репликации. Хороший пример влияния лишних требований на итоговый результат, надо быть поосторожнее с требованиями!

Настоящая репликация реализована в «большой тройке» СУБД: DB2, MS SQL Server и Oracle. Они пригодны для самых сложных проектов и лучше других соответствуют названию книги. Я смотрю со стороны MS SQL Server.

Вот пример совместного использования репликации и отказоустойчивости, задача из подраздела «5.3. Репликация с несколькими ведущими узлами». Требуется доставить данные ближе к пользователям в другие ЦОД и обеспечить синхронизацию обновлений в разных ЦОД. В MS SQL Server для этих целей подходят репликация слиянием и транзакционная репликация с обновляемыми подписчиками, выбирать по ситуации. В другой ЦОД можно передавать только нужные там данные, не всю базу данных (по соображеньям безопасности и оптимизации по объёму данных). Репликация СУБД занимается только своим делом: распространением и синхронизацией данных между ЦОД. Отказоустойчивость в каждом ЦОД настраивается отдельно с использованием подходящей технологии MS SQL Server: database mirroring, Always On failover cluster instances или Always On availability groups.

В книге нет примеров по проектированию приложений с применением репликации, впрочем, нет и самой репликации.

Приведу пример применения репликации для масштабирования чтения данных из статьи MSDN, которая появилась там в 2003 году и через неделю оттуда исчезла. Почему-то… Статья была про использование транзакционной репликации для реализации кеширования данных в приложении биржи NASDAQ. Удивляюсь появлению этой статьи в MSDN и жалею, что не сохранил текст на диск и не распечатал.

Есть большой кластер веб-серверов с огромной нагрузкой по запросам для показа данных, есть база данных с нужными данными, надо как-то решить проблему этой огромной нагрузки на чтение данных. Хочется иметь прямо на веб-серверах в оперативной памяти кэши с данными, и чтобы эти кэши синхронизовались с базой данных. Решили использовать в качестве кэша MS SQL Server 2000 Enterprise edition, а синхронизацию данных делать транзакционной репликацией. В MS SQL Server 2000 появилось отличное кэширование в оперативной памяти. В той же статье из MSDN утверждалось, что в случае Enterprise edition, если все данные базы данных помещаются в оперативной памяти, то все они там и будут (примерно так оно и есть, много чего кэшируются, отчего тестировать производительность запроса надо на тестовом сервере, предварительно почистив кэши перед каждым запуском). Надо полагать, что и поддержка восьми процессоров в Enterprise вместо четырёх в Standard тоже не лишняя. Это решение имеет отличную масштабируемость в ширину, минимальную нагрузку на сеть и относительно простое администрирование.

Такое невозможно сделать используя какую-либо реализацию отказоустойчивости без разделяемых ресурсов вместо репликации из-за присущих ограничений. Нужна именно репликация со всеми её возможностями.

Считаю, что эту главу Мартин Клеппман провалил. Так определил репликацию, что туда не попадает настоящая репликация MS SQL Server и Oracle, достаточная для самого крупного бизнеса, но зато попадают MS SQL Server AlwaysOn Availability Groups и Oracle Data Guard, которые сами производители этих уважаемых СУБД не относят к репликации. Рецензенты обязаны были объяснить ему, что репликация – это про другое, не про отказоустойчивость и современные новоделы, и напомнить про название книги, designing data-intensive applications – это не про подробности реализации чего-то в PostgeSQL, MySQL и, прости господи, Dynamo. Нужны примеры архитектурных решений с репликацией, а их нет. 

Глава 6. Секционирование

 Мартин Клеппман подробно и обстоятельно рассказывает про секционирование. Отмечает, что обычно секционирование идёт вместе с «репликацией» для отказоустойчивости. Всё правильно, без отказоустойчивости секционирование лучше не делать, есть проблема согласованности бэкапов секций секционированной базы. Сообщает, что идея секционирования появилась в 1980-х годах и вспомнили о ней недавно. Т.е. в 80-е годы её благополучно забыли и 20 лет почему-то не вспоминали. Так почему же секционирование не прижилось в 80-е и 90-е годы, во время жестокого дефицита вычислительных мощностей и огромной потребности распределять нагрузку по серверам? Казалось бы, самое время…

Что даёт секционирование? Хорошее распределение нагрузки по серверам для работы с данными по ключу. В чём проблема секционирования? В поисковых запросах. Или гонять запрос по всем секциям, чтобы использовались локальные индексы. Или поддерживать глобальные индексы ценой утяжеления операций изменения данных (а тут и распределённые транзакции в случае OLTP приложения, введённые данные должны сразу быть доступными для поисков) и сомнительными перспективами выгоды от этих глобальных индексов, особенно в OLTP.

В чём основная проблема OLTP приложений? В поисковых запросах, на них уходит ресурсов сильно больше, чем на всё остальное вместе взятое. И обычно нет проблем с работой с данными по ключу. Сильная сторона секционирования интереса не представляет, а проблема поисков никуда не уходит. Сокращения объёмов вычислений нет, та же нагрузка размазывается по большему числу серверов с перспективой увеличения этого числа серверов по мере роста базы данных. От такого счастья в 80-е и 90-е годы отказались, т.к. есть альтернативы лучше. Вот два варианта действий.

1.       Декомпозиция приложения и базы данных на независимые приложения со своими независимыми базами данных. Делать огромное приложение с огромной базой данных – не самая лучшая идея. Но нужно разделять так, чтобы было достаточно обмена данными между базами и не было необходимости в распределённых транзакциях.

2.       Физическое разделение оперативных и обработанных данных.

Секционирование – это частный случай проектирования федеративной базы данных, т.е. когда одна логическая база данных состоит из нескольких физических баз данных. Вместо рассмотрения только этого частного случая следовало бы рассказать вообще про проектирование федеративных баз данных. Чтобы читателям было понятно, что секционирование – это не единственный вариант проектирования распределённой базы данных для распределения нагрузки по серверам, часто есть более достойная альтернатива.

Известная проблема федеративных баз данных – это согласованность бэкапов всех её физических баз данных. При проектировании федеративной базы данных нужно как-то решать эту проблему. Лучше иметь возможность восстановления в согласованном виде из бэкапов, чем не иметь! При секционировании эта проблема не решается. А вот при разделении базы данных на оперативную часть и обработанный архив, если обработанные данные переносятся из оперативной части в архив в определённое время, проблема согласованности бэкапов решается.

Секционирование не решает проблему зависимости нагрузки на сервера от объёмов данных. Эта проблема решается разделением базы данных на оперативные данные и обработанные данные. Если это сделать, то нагрузка будет зависеть не от объёма данных, а от размера бизнеса. Т.е. если бизнес через год будет такого же размера, то и нагрузка на сервера будет примерно такая же.  Если имеете быстрорастущую базу данных, то секционирование – это плохое решение, намного хуже разделения оперативных и обработанных данных, будет постоянный рост затрат на сервера и администрирование.

Мартин Клеппман не знает как убрать зависимость нагрузки на сервера баз данных от объёмов данных. А это важнейшая проблема при проектировании быстрорастущих баз данных. Если применять секционирование, будет быстрорастущий дата-центр вместо серверной комнаты.  

Глава 7. Транзакции

Зачем эта глава нужна в таком объёме? Это не учебник по базам данных, если судить по названию. Все знания про транзакции есть в документации к СУБД и в учебниках по базам данных. Книжка сама себя не напишет, можно заполнить место и транзакциями...

И это самая удивительная глава книги. Казалось бы, чем тут можно удивить, если всё давно написано в учебниках, но Мартин Клеппман начинает удивлять уже во вступительной части. Оказывается, транзакции были созданы для упрощения модели программирования приложений, работающих с базами данных. А не для обеспечения правильности данных?! Может нужен был способ обеспечить выполнение сложных правил на данные при параллельной обработке данных, для этого должна быть возможность отката и блокировок на уровне СУБД с запретом грязного чтения и грязного изменения данных, вот и ввели транзакции?! Как можно в мире грязного чтения данных и грязного изменения данных что-то гарантировать при параллельной работе с данными… Что угодно может в данных оказаться.

Далее, Мартин Клеппман продолжает удивлять в разборе ACID. Начал с того, что ACID – это свойства транзакций, всё правильно. В разделе «Согласованность» справедливо отмечает, что формулировать транзакции таким образом, чтобы сохранялась согласованность, — обязанность приложения, база данных не в состоянии это гарантировать. И вдруг выдаёт:

«Атомарность, изоляция и сохраняемость — свойства базы данных, в то время как согласованность (в смысле ACID) — свойство приложения. Оно может полагаться на свойства атомарности и изоляции базы данных, чтобы обеспечить согласованность, но не на одну только базу. Следовательно, букве C на самом деле не место в аббревиатуре ACID.»

Три свойства транзакций вдруг оказались ещё и свойствами базы данных, а одно свойство транзакций не оказалось свойством базы данных, а вместо этого оказалось ещё и свойством приложения, и потому должно быть исключено из свойств транзакций! Логика! Одно свойство транзакций удаляем из свойств транзакций потому, что оно оказалось ещё и свойством приложения. Но три остальных свойства, оказавшиеся ещё и свойствами базы данных, не удаляем из свойств транзакций. Быть свойством приложения – это что-то порочащее?!

Между тем, согласованность – это самое главное свойство транзакций, остальные его дополняют. Смысл транзакций – гарантировать соблюдение правил на данные при сохранении данных, или всё с соблюдением правил сохраняется или все изменения откатываются. Из свойств транзакций выкинуть саму суть транзакций?!

Но самое удивительное в разделе «Обработка ошибок и прерывание транзакций» (страница 276). Этот шедевр тоже надо процитировать.

«Ошибки неизбежны, но многие разработчики программ склонны думать только о хорошем, вместо того чтобы углубляться в детали обработки ошибок. Например, популярные фреймворки объектно-реляционного отображения (ORM), такие как ActiveRecord для Rails и Django, не повторяют попытку выполнения прерванных транзакций — ошибка в них обычно приводит к «всплытию» исключения по стеку, так что все введенные пользователем данные теряются, а сам он получает сообщение об ошибке. Стыд и позор, ведь весь смысл прерывания транзакций как раз в обеспечении возможности безопасного их повторения.

Хотя повторение прерванных транзакций — простой и эффективный механизм обработки ошибок, он неидеален.

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

·         Если причина ошибки — в перегруженности, то повтор транзакции только усугубит проблему. Во избежание подобных циклов обратной связи можно ограничить количество повторов, воспользоваться экспоненциальной отсрочкой отправки и обрабатывать связанные с перегруженностью ошибки не так, как все остальные (при условии, что это возможно, конечно).

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

·         Если у транзакции есть побочные действия вне базы данных, то они могут выполняться даже в случае ее прерывания. Например, вряд ли вы захотите повторять отправку сообщения электронной почты при каждой попытке повтора транзакции. Для гарантии того, что несколько различных систем будут фиксировать изменения или прерывать транзакцию только все вместе, может оказаться полезна двухфазная фиксация транзакции (мы обсудим ее в подразделе «Атомарная и двухфазная фиксация (2PC)» раздела 9.4).

·         При сбое клиентского процесса во время повтора попытки выполнения все данные, которые он пытается записать в базу, теряются.»

 Прочитав такое, я нашёл английский оригинал и сравнил. Смысл передан правильно. Вопросов про уровень Мартина Клеппмана у меня не осталось.

Профессиональный программист такое написать не мог. Как можно от фреймворка требовать что-то, за что должно быть ответственно приложение, использующее этот фреймворк?! Кому надо по имеющейся ситуации что-то сделать, тот и делает, но сначала проверяет ситуацию! Ну и как такие претензии создателям упомянутых фреймворков?! Хорошо посмеялись, наверное.

Уровень понимания транзакций тоже ясен («повторение прерванных транзакций — простой и эффективный механизм обработки ошибок»), никогда с транзакциями на практике не работал. Если бы поработал с базой данных на проекте приложения для бизнеса, то ему бы на работе всё объяснили про этот «простой и эффективный механизм обработки ошибок». Большинство ошибок выполнения транзакций – это ошибки в данных, когда нарушаются правила на данные. Чего-то не хватает или есть противоречия с другими данными. Обычно это означает, что работа не доделана. Надо доделать работу и потом выполнить транзакцию уже с полными правильными данными. Эта та самая буква C из ACID, которая не нравится Мартину Клеппману.

Далее, рассказав про пессимистическую и оптимистическую изоляции, Мартин Клеппман мог бы и закончить эту главу. Но зачем-то стал подробно излагать тему изоляции снимков состояния, отчего у читателей может появиться мнение о её большой необходимости, а это совсем не так, особенно для приложений с интенсивной обработкой больших объёмов данных.

1. Изоляция снимков состояния появилась в нынешнем веке, 20+ лет обходились без неё.

2. Изоляция снимков состояния даёт дополнительную сильную нагрузку на процессоры, оперативную память и диски.

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

На моей практике был только один подходящий для применения случай. Требовалось сделать хранилище данных для отчётов с близкой к реальному времени синхронизацией. В то время использовалcя MS SQL Server 2005, где уже есть изоляция снимков. Изменения на рабочей базе накапливал триггерами в таблицы изменений.  Сделал синхронизацию хранилища в виде распределённой транзакции, ситуация позволяла, работала нормально раз в минуту. Была возможность использования изоляции снимков состояния, чтобы отчёты показывали снимки данных на момент запуска отчёта. Но это не понадобилось, отчёты должны были показывать самые последние данные и какие-то минутные расхождения по времени никого не волновали. И это понятно, обработанные данные не меняются совсем, отчёты по оплаченным счетам не изменяются, а оперативные данные меняются быстро, ничего интересного в снимке нет, через минуту будут другие числа. Смысла нет дополнительные ресурсы тратить.

В Резюме, в первом же абзаце, Мартин Клеппман напоминает, чтобы не забыли: «Широкий класс ошибок сводится к простому прерыванию транзакции, а приложению достаточно просто повторить выполнение транзакции.»

Это, несомненно, собственная мысль Мартина Клеппмана. Иначе бы добавил источник в библиографию и дал ссылку.

Рецензенты эту главу читали?! Очень нужен был рецензент со знаниями из курса по базам данных и опытом практической работы. Без знаний по базам данных читать такое вредно, а со знаниями – смешно.

Представим, что на экзамене по базам данных или на собеседовании на вакансию архитектора, кто-то рассказывает, что транзакции придумали для облегчения программирования, букву C надо выкинуть из ACID, транзакции надо повторять при ошибках и ORM фреймворки должны автоматически это делать. Какие перспективы у этого кадра? А если такой кадр пишет книгу и все месяцы/годы работы над книгой так думает и в книге это вот всё? Трудно в такое поверить, но такой человек есть, считает себя учёным и годами не может понять смысл транзакций. Ещё труднее поверить, что его книгу рецензенты пропустили в печать, книга стала бестселлером и её рекомендуют прочитать, называют «легендарным кабанчиком» и «учебником проектирования высоконагруженных приложений».

Комментарии (21)


  1. Ninil
    19.12.2024 17:19

    Меня "Кабанчик" разочаровал. Столько рекламы его, и ... Через призму своего почти 20-летнего опыта внедрения DWH/DataLake/DataPlatform могу сказать, что подача материала там ужасна! Как можно так нудно, долго и неинтересно, порою очень уж сложно, писать о простых и интересных (для меня) вещах? Иногда аж засыпал под нее... Странно прозвучит, но многие статьи на хабре на темы глав этой книги намного интереснее)))
    В общем, ИМХО, мне кажется книга сильно переоценина, просто когда-то почему-то из "инфлюэнсеров" ее начал рекомендовать. Плюс издание и автор известные. Поэтому и ругать ее как-то "моветон" стало. И дальше идет по инерции.


  1. sshikov
    19.12.2024 17:19

    в обычный месяц приходится 30 тысяч ордеров о потерянных сумках, в сезон отпусков - 90 тысяч.

    Вы 3 тысячи записей в день называете что-ли быстрорастущей базой? А если нет, то к чему был этот пример? Даже если умножить это на число аэропортов в мире (возьмем от фонаря 10000), даже это не будет большим объемом.


    1. yurchenko_oleg Автор
      19.12.2024 17:19

      Для слабого сервера и такой объём большой. Тут не Uber, труба пониже - дым пожиже. И необходимость привлекательных условия для авиакомпаний и перевозчиков делает рентабельность бизнеса непривлекательной для собственников. Экономия на всём и раз в три года развлечение "продать слона".


    1. yurchenko_oleg Автор
      19.12.2024 17:19

      2007 год, на сервере баз данных 2 процессора и MS SQL Server 2000 Work Group edition.


      1. sshikov
        19.12.2024 17:19

        Ну что это именно 2007 - вышло как-то не очевидно. Но в целом я вас понял, наверное не самая крупная база, но при ограничениях на память и процессор, и очевидно без SSD - не самая мелкая. Два ядра - это вообще ну очень фигово, я бы сказал.


        1. dph
          19.12.2024 17:19

          Да ладно, я в 2007 году спокойно делал 100 пишущих транзакций в секунду со сложной обработкой на тех же двух ядрах и не было с этим никаких проблем. Вот 10k пишущих транзакций в секунду на дешевом железе времен HDD - уже вызов, да.


          1. sshikov
            19.12.2024 17:19

            Не, слабый сервер (с учетом того что это Windows) - это конечно не оправдание, но я пытался значительно позже (2015 примерно) на виртуалке с 4 ядрами и 4Gb оперативки записать лог транзакций с мосбиржи, не так и много, где-то миллионы в сутки примерно было нужно. Оказалось, что дисковый массив у нас тоже общий, и всего выдерживает 4000IOPS примерно, а мы в одиночку с нашей базой захотели порядка 5000. При этом переезд на нормальный сервер через полгода сделал эту задачу почти тривиальной. В общем, для MS SQL мало ресурсов - иногда реально мало ресурсов.


  1. dph
    19.12.2024 17:19

    Ну и Клеппман пишет, по большей части, о более сложных сценариях, нежели описаны в статье. Возможно, автор никогда не сталкивался с действительно сложными и крупными базами и проблемами целостности в них.


    1. yurchenko_oleg Автор
      19.12.2024 17:19

      Какие сложные сценарии? У "кабанчика" уровень сложности для студентов, причём главы про репликацию и транзакции студентам читать нежелательно, чтобы потом не переучиваться. Рецензенты должны были помочь Клеппану доработать эти главы. Могли бы ещё посоветовать написать про альтернативы секционированию. Глава про секционирование написана хорошо. Но в книге нет альтернатив. И это плохо для учебника. Из-за незнания альтернатив ученики могут применять секционирование там, где оно неуместно.

      В моей статье только вступительная часть, старинный способ физического проектирования баз данных для оптимизации нагрузки на сервер и комментарии к трём главам "кабанчика". И никаких сценариев, только поясняющие примеры. Каждый пример поясняет какую-то идею или её полезность. Все примеры тут вторичны по отношению к идеям.


  1. zmiik
    19.12.2024 17:19

    На самом деле проблема разделения баз на 'холодные' и 'горячие' данные актуальна до сих пор. Я в своей практике сталкивался со многими системами, долго работал в чистом дата-инжиниринге. И что странно, люди вообще как будто не видят проблемы хранить данные вместе.

    Всего в одном месте я увидел полноценное разделение на DWH и OLTP. Причем со связью многие ко многим. Т.е абсолютно масштабируемая, можно сказать микросервисная архитектура БД, где каждый DWH или OLTP сегменты выполняют свою узкую задачу.

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

    Просто средства другие. А-ля Airflow вместо Kafka.


  1. TldrWiki
    19.12.2024 17:19

    А почему согласованность это свойство приложения? Разве это не о внешних ключах и констрейнтах бд?


    1. yurchenko_oleg Автор
      19.12.2024 17:19

      Не только о внешних ключах и констрейнтах бд (но в том числе и о них). Правила могут быть какие-угодно. Если правило на данные нельзя определить на уровне базы данных, то его реализует программист, пишет нужный код под свою ответственность.  

      Причём, не всегда правила на данные зависят от области деятельности. У компаний-конкурентов могут быть разные правила работы и разные требования к данным. Что разработчикам приложения закажут, то программисты и сделают. Какие транзакции в приложении нужны, такие и будут.

      Правила на данные - свойства приложений. Да и сами транзакции зависят от приложения, программисты их реализовывают, когда приложение делают. Делить свойства ACID на свойства СУБД и приложений не надо. Правила на данные частично задаются ограничениями целостности, а частично реализуются программистами, и куда их отнести :)


      1. TldrWiki
        19.12.2024 17:19

        По такой логике и изоляция свойство приложения. Ведь можно программно сделать транзакции идущими одна за другой?


        1. yurchenko_oleg Автор
          19.12.2024 17:19

          ACID - свойства транзакций. Транзакции реализовывают программисты, а выполняются транзакции на СУБД. Не надо ACID делить на свойства СУБД и приложений.


          1. TldrWiki
            19.12.2024 17:19

            Транзакции это прерогатива базы данных. У бд есть командная строка и она о целостности данных в приложении ничего не знает. Как вы реализовываете транзакции? Они уже реализованы в бд, вы их только используете.

            Так можно далеко зайти в переложении ответственности и вообще непонятно будет где код, а где инфраструктура.


            1. yurchenko_oleg Автор
              19.12.2024 17:19

              Вам нужен учебник. Рекомендую этот: "Дейт К.Д. Введение в системы баз данных."


              1. TldrWiki
                19.12.2024 17:19

                То есть ответить вы не в состоянии?


                1. yurchenko_oleg Автор
                  19.12.2024 17:19

                  Долго отвечать, это не формат комментариев. У Дейта тема транзакций разбита на две главы. Это самый авторитетный учебник, там всё правильно по теории.


                  1. TldrWiki
                    19.12.2024 17:19

                    Да, это известная отмазка когда нечего ответить. Кратко формулировать мысли вы умеете?


                    1. lear
                      19.12.2024 17:19

                      Как я понял, у вас проблема в том, что вы говорите о разных уровнях абстракции согласованности. На уровне бл и на уровне бд.


  1. lear
    19.12.2024 17:19

    Вот и у меня при прочтении сложилось впечатление что написана теоретиком для теоретиков. Всё описано слишком абстрактно, без конкретики, без примеров...