Русский
Русский
English
Статистика
Реклама

Базы данных

Представь, что ты нашел решение, про которое можешь сказать оно лучшее в мире интервью с создателем ClickHouse

22.03.2021 20:04:15 | Автор: admin

Алексей Миловидов работал инженером в Яндекс.Метрике, и перед ним стояла непростая задача.

Яндекс.Метрика работала с петабайтами данных это был третий по популярности сервис веб-аналитики в мире. Для него нужна была база данных, которая может обрабатывать огромное количество данных в реальном времени, очень быстро, при этом не сжигая миллиарды денег.

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

Мы поговорили с Алексеем о том, как он стал разработчиком, почему ClickHouse намного быстрее всех аналогов и как так получилось, какова цена производительности, почему ClickHouse стал опенсорсным и куда вообще движется индустрия.


Расскажи про себя, про учебу, и про первый опыт в разработке.

Наверное, надо начинать очень издалека. В семье было еще два брата, я самый младший. Году в 90-м родители купили старшему брату бытовой компьютер БК-0010. Это была реально классная машина. Интересные игры, встроенные языки программирования: Бейсик Вильнюс 86 и Фокал (Focal, акроним от англ. formula calculator).

Мои братья играли в эти игры, я в основном смотрел, мне было года 3-4. Отец с работы приносил стопки ксерокопий. На них можно было рисовать. Я рисовал компьютерные игры, лабиринты. И все время пытался заставить старшего брата все это запрограммировать. И я его так достал, что он взял и выполнил мою просьбу. Оказывается, я стал менеджером гораздо раньше, чем программистом.

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

Брат рассказал про цикл и дал мне в руки толстую книгу под названием Язык программирования Бейсик. Уже позже папа принес домой IBM 286, и я стал что-то ковырять на нем. Мое первое достижение я написал игру и смог уговорить поиграть в нее маму. Мне было чуть меньше 10 лет.

В школе преподавали Паскаль. Писал на Паскале, потом на Делфи. Потом решил изучить С. С++ всегда был мощным, таким он и остался так было написано на диске старшего брата. Тем более я где-то услышал, что если писать игры то только на С++, другого варианта нет.

Это история была общей для всех программистов. Все учили С++, чтобы делать игры, а в итоге делают КликХаус.


Почему КликХаус настолько быстрее аналогов?

Тому есть две причины. Одна причина это просто более-менее подходящие технические решения. А вторая внимание к деталям. КликХаус был создан для обработки данных веб-аналитики в Яндекс.Метрике. Я решал задачи производительности. Что-нибудь подкрутить, как-нибудь схитрить, чтобы КликХаус работал хотя бы в 2 раза быстрее. Как только ты сделал его в 2 раза быстрее, сразу думаешь: а как еще в полтора раза быстрее сделать? А еще бы на 30%, и вообще было бы идеально! Приходится много ковыряться в деталях.

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

В такой работе должен быть инженерный кайф.

Да, он определенно присутствует. Представь, что ты наконец-то сделал решение, (может быть, впервые в жизни!), про которое ты можешь сказать: да, это лучшее решение в мире.

Я иронизирую, но, допустим, на работе ты делаешь круды. Или другие мемы индустрии: клепаешь формочки, перекладываешь джейсоны. Однажды ты настолько хорошо переложил джейсоны, что понял: ты сделал лучший в мире Перекладыватель Джейсонов! Он тратит мало оперативки, его можно быстро прикрутить. И главное, прийти в другую компанию, сказать, что с помощью этого классного инструмента я вам сейчас переложу все джейсоны из одного места в другое.

Но компаниям на самом деле наплевать, как ты перекладываешь джейсоны. Жрет оно в 5 раз больше оперативы, и бог с ним!

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

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

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

Работа над кодом, главная черта которого производительность, накладывает большие требования на тестирование этой производительности?

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

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

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

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

И как такие проблемы решаются? Ты их один решаешь, или у вас там консилиум собирается? Бывают ли тупики?

А теперь раскрою секрет, вот такие проблемы мы не решаем! Мы еще до этого не дошли. Мы просто научились наблюдать.

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

Есть и другие способы измерять производительность. Что называется, always on profiling. То есть всегда включенное профилирование на продакшене. В КликХаусе встроен семплирующий профайлер. С небольшим интервалом семплирования, всего лишь один раз в секунду, собираются стектрейсы во всех потоках. А потом по большому кластеру мы агрегируем все за неделю и делаем выводы, стал ли продукт быстрее.

Есть же дилемма чтобы написать более быстрый код, ты пишешь сильно менее читаемый код. Для вас это проблема в кодовой базе КликХауса?

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

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

В C# или Java хороший код это максимум 200 строк на файл. Ты заходишь в любой файлик, и он умещается в экран. На C++ это вообще нереально, особенно на больших проектах. У вас есть файлы на 5.000 строк?

Сейчас посмотрю мой любимый StorageReplicatedMergeTree.cpp, сколько там строк...

Кстати, сейчас я использую Linux, и в моей любимой среде разработки что-то напутали со шрифтами, и номера строк отображаются так, что первая цифра наполовину не видна.

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

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

...итак в нашем файле 6.368 строк. Это один из самых больших файлов в нашем проекте. Таких немного. В основном у нас небольшие файлы.


Зачем КликХаус в опенсорсе?

Изначально технология возникла для задач вообще одной команды Яндекс.Метрики. Идея состояла в том, чтобы создать чуть более общее решение для обработки данных. И она казалась безумной, я держал эту идею в уме как мечту, которую как-нибудь все-таки удастся воплотить. Постепенно начал пилить. Где-то десять лет назад уже можно было посмотреть на рабочий вариант. Показал его коллегам, им понравилось. Артур Суилин из Вебвизора тогда был руководителем Яндекс.Метрики. Он человек технический, ему эта штука понравилась. Мы ее внедрили в Метрику для одной из задач, и я продолжил спокойно допиливать.

Постепенно к технологии появился интерес и в других отделах Яндекса. Наша разработка давала возможность использовать систему внутри Метрики напрямую для аналитиков, чтобы можно было выполнить обобщенный запрос по данным всего интернета. На самом деле, это очень круто. Потому что метрика стояла на 70% сайтов: покрывается совершенно нереальное количество трафика и можно делать глобальный запрос, чтобы посчитать что-нибудь важное.

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

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

В конце 2015-го года я перешел в группу Кликхаус (ее выделили из группы Яндекс.Метрики). Мы обсуждали идею КликХауса на опенсорсе с моим руководителем, он предложил ее Михаилу Парахину (тогда он был СТО в Яндексе). Тот дал добро. Я тогда написал мотивационное письмо: какие возможности, какие риски. Мы стали пробовать, несколько месяцев ушло на подготовку, но летом 2016 года все было готово, и мы выложили КликХаус в опенсорс. Серьезных надежд не было. У меня был такой принцип: если ничего не получится, то ну и ладно, не надо будет тратить силы. Но если полетит и все будет хорошо, то у нас будет некоторое достижение. Возможность получить это достижение перекрывала риски.

Взлетела технология?

Уверенно растет. Будем стараться дальше.

Опенсорс принес какие-то плоды? Контрибьюторы со стороны приходят?

Да, принес. Сейчас у нас 46% пул-реквестов внешние. И даже есть случай, когда человек реализует внешнюю функциональность или исправляет баг, и вот эту функциональность как раз хотели попросить у нас внутренние заказчики. А баг исправлен до того, как вообще кто-либо внутри мог его заметить.

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

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

Почему Кликхаус так сильно отличается от других БД, что даже появилось выражение "ClickHouse way"?

Мы исходили из своих задач и брали самое простое естественное их решение, которое не обязательно соответствовало стандарту SQL. Я приведу простой пример: у нас в Кликхаусе можно написать не селект каунт звездочка, а просто: каунт и все! И я до сих пор думаю, что это логичнее.

Не страшно принимать такие решения? Привычка сообщества это тоже часть простоты инструмента.

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

Говорят, Яндекс сначала занимается собственными внутренними заказчиками и только потом сообществом и нуждами внешних пользователей. Что думаешь про это?

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

Что думаешь про компании, которые строят целые бизнесы на внедрении КликХауса?

Я всецело это приветствую. Чем больше будет этот бизнес, тем лучше.

Если КликХаус вырастет на рынке, не будешь переживать, что многие решения, которые ты принял в самом начале, уже не исправить?

Ничего, придется объяснять. Такое часто бывает. Взять линукс или BSD, в котором многие вещи тянутся из допотопных времен. Например, спрашивают, почему язык csh такой ужасный? Чем разработчик думал, когда его писал? Автора нашли (хотел сказать откопали). Он ответил: у меня просто не было опыта. Какую технологию не возьми, везде есть какие-нибудь грабли.


Как тебе индустрия прямо сейчас?

Индустрия растет вширь и покрывает все большие области. Сейчас даже возникает вопрос: писать код это новый признак грамотности? Это как читать и писать? Мне кажется, скоро будет нормальным программировать раньше, чем читать, программировать раньше, чем ходить.

Ты видишь проблему в том, что порог входа снизился? В индустрии много программистов разного уровня.

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

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

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

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

Да, это плохо. Можно понять, почему так получилось. Старые инструменты были слишком глубоко запрятаны. Если люди не видят старое, они просто изобретают новое.

Недавно все дружно похоронили технологию flash. Раньше, если ты хотел сделать что-то интерактивное на своем сайте, ты не мог обойтись без flash. Зато ты был уверен, что на всех платформах, которые поддерживают эту технологию, все работает одинаково. Когда начался переход на HTML5, я думал так: HTML5 совокупность слабосвязанных технологий, которые кое-как взаимодействуют друг с другом. Технология реализована частично и везде работает по-разному. Как вообще можно говорить, что она заменит flash? Но прошло время, и теперь почти все работает.

Эти процессы не остановить. Люди пишут новые инструменты просто потому, что хотят. Все-таки в нашей профессии есть доля творчества. Человек может плохо разбираться в вопросе, но в процессе разработки очередного велосипеда он получит много пользы лично для себя. Возможно, все, что он сделает, нанесет вред индустрии. Но он точно станет более квалифицированным специалистом. Пусть свет увидит еще 10 новых конкурирующих JavaScript фреймворков, но один из них когда-нибудь да привнесет новую идею. Людям нельзя запрещать пытаться только потому, что большинство их разработок не будут нужны.

Современные языки программирования что ты о них думаешь?

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

Вспомнить JavaScript, который вырос из пережитков прошлого, или С++, рост которого был продиктован разными практическими соображениями. Принципы, по которым он построен, почти все разумные. Конечно, есть и неконсистентности. С++ сложный язык и это его основная проблема. Rust, кстати, тоже находится в группе риска, он не менее сложный и становится все труднее. Чтобы программировать простые вещи, нужно чувствовать себя уверенно при использовании языка.

По пятницам мы с ребятами из КликХауса собираемся и читаем чужой код ради смеха. Возможно, когда-нибудь выйдет юмористическое видео Разработчики КликХауса читают код и ржут. Вот недавно смотрели код на Rust. При этом никто из нас не знает этот язык. Можно открыть код КликХауса и тоже поугорать над этим.

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

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

Чуть ли не единственное решение, которое следит за производительностью по максимуму это КликХаус. Мы действительно докапываемся, чтобы каждая штука в нашем коде работала так, как позволяет железо. А современное железо, поверьте, очень крутое! Для многих это обыденность, я и сам воспринимаю эту силу как некую магию. История человечества, вся совокупность знаний привела нас к тому, что мы имеем процессор, который обрабатывает миллиарды операций. Это нереальное достижение. Ты приезжаешь в аэропорт, видишь там авиалайнер и восторгаешься тем, как он сделан. В кармане каждый день лежит обычный телефон, и на его счет я испытываю те же самые чувства.

Невероятно важно осознавать все то, что привело к этому.


От редакции Rebrain

Что вы сами думаете про ClickHouse? Лично мы накопили уже очень много опыта в работе с ним и постоянно этим опытом делимся рассказываем про все тонкости, где, как и что лучше применять.

Заходите к нам в сообщество там мы постоянно разбираем различные проблемы и задачи из сферы Devops, обсуждаем вещи, которые пригодятся и на собеседованиях, и в работе.

Подробнее..

Ещё один шаг в сторону open source как и почему мы внедрили Arenadata DB

22.04.2021 10:12:30 | Автор: admin

Привет, Хабр! Меня зовут Станислав Маскайкин, я архитектор аналитических систем ВТБ. Сегодня я расскажу о том, почему мы перевели нашу систему подготовки отчётности с Oracle SuperCluster на российскую Arenadata DB. Как мы выбирали решение, почему не взяли чистый опенсорс, а также о некоторых результатах такой миграции под катом.

Зачем нужен был переход?

Несколько лет назад банк ВТБ объединился с Банком Москвы и ВТБ 24. Каждый из банков имел собственную ИТ-инфраструктуру с отдельным аналитическим контуром. После объединения получилось, что в банке одновременно существуют три разных ИТ ландшафта.

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

С точки зрения законодательства любой банк должен регулярно сдавать отчётность проверяющим органам. После объединения эту отчётность мы должны были сдавать уже по единому ВТБ. Имея три разрозненные системы, решать эту задачу можно было разве что вручную.

Исторически ВТБ24 был ориентирован на работу с физическими лицами, ВТБ на работу с юридическими лицами, а Банк Москвы на работу и с первыми, и со вторыми.

На момент объединения этих банков обязательная отчётность формировалась в следующих системах:

  1. Единое хранилище данных (ЕХД) хранилище данных Банка Москвы, реализованное на SuperCluster M8 и ETL-инструменте Informatica Power Center.

  1. Система подготовки отчётности хранилище данных ВТБ24, реализованное на Oracle SuperCluster M8 с программным обеспечением Diasoft Flextera BI. Данные для этой системы готовились в другом хранилище корпоративном хранилище данных (КХД), реализованном на СУБД Teradata и ETL-инструменте SAS Data Integration. КХД, в свою очередь, получало данные из оперативного хранилища данных, реализованного на Oracle SuperCluster M8. А туда они реплицировались из автоматизированных банковских систем при помощи инструмента Oracle Golden Gate.

  1. Корпоративное информационное хранилище хранилище данных ВТБ, реализованное на Oracle Exadata X8-2 и ETL-инструменте Informatica Power Center.

Чтобы не формировать обязательную отчётность по объединённому ВТБ в ручном режиме, были созданы интеграции между хранилищами данных.

Это привело к ещё двум большим проблемам:

  1. Увеличилось время получения данных, что часто приводило к срыву сроков предоставления информации.

По правилам ЦБ РФ отчётность за очередной месяц сдаётся в течение первых четырех дней следующего месяца. В банке под это задействуются огромные вычислительные мощности. Если первые дни месяца попадают на рабочие дни (как в марте), мы даже не имеем возможности вернуться и что-то пересчитать это риск для банка не сдать отчётность вовремя. Повышение доступности данных на этом уровне играет огромную роль.

  1. Из-за большого количества расчётов в хранилищах и копирования данных выросло количество инцидентов с качеством этих данных, что тоже очень сильно влияет на сроки предоставление отчётности.

Ещё один момент: многие компоненты нашей инфраструктуры, такие как Oracle SuperCluster, на котором у нас реализована большая часть аналитического ландшафта, попали под End of life. Они были сняты с поддержки производителем и больше не развивались, т.е. обновление необходимо было в любом случае.

Проблема окончания поддержки коснулась не только системы подготовки отчётности, но и озера данных на платформе Oracle Big Data Appliance. К тому моменту а происходило все в 20182019 годах сотрудники ВТБ уже в полной мере оценили data-driven подход и потребляли достаточно много данных. Поэтому с точки зрения бизнеса банка система была критичной. Т.е. перед нами стояла более глобальная задача масштабов всей инфраструктуры.

Параллельно в объединённом ВТБ началась масштабная цифровая трансформация, охватившая все уровни IT, начиная от создания новых ЦОДов и объединения сетей, и заканчивая унификацией автоматизированных банковских систем и созданием омниканальной платформы для фронтальных решений. Всё это кардинально меняло внутренний IT-ландшафт банка.

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

Логичная идея создание единого аналитического контура, в рамках которого все данные будут находиться в едином хранилище данных и доступны для всех пользователей в удобном виде. Это привело нас к решению о построении платформы данных ВТБ.

Что такое платформа данных? Для себя мы определили её так: это набор сквозных интегрированных технологических решений (технологическое ядро), которые являются основой для разработки и функционирования сервисов по работе с данными банка ВТБ.

Главная часть платформы данных технологическое ядро. Это системные компоненты, которые переиспользуются на всех уровнях платформы данных.

Мы выделили 6 компонентов технологического ядра:

  1. Управление данными.

  2. Управление качеством данных.

  3. Управление доступом.

  4. Аналитические справочники.

  5. Корректировки.

  6. ETL Framework.

Концептуальная архитектура платформы данных выглядит следующим образом:

Ядром платформы данных является СУБД, на которой реализовывается хранилище данных. Далее расскажу об этом подробнее.

Выбор новой платформы

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

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

Мы рассматривали всех крупнейших производителей подобных решений: Oracle Exadata, Teradata, Huawei. Оценили отечественные разработки практически все, что есть на рынке. Нам показался интересным опенсорс, тем более для банка это не первый заход в тему открытого исходного кода.

В принципе, можно было бы купить железо, скачать открытый код и собрать собственную платформу данных. Но для нас это означало серьёзные риски: нужны были компетенции, чтобы доработать всё это до уровня энтерпрайза. А на момент старта проекта у нас не было уверенности в успешном завершении подобного мероприятия. Поэтому мы сформулировали ещё один критерий поставка решения в виде программно-аппаратного комплекса (ПАК), чтобы совместная работа железа и программных инструментов была протестирована с учётом версий. Так мы хеджировали риски, связанные с недостаточной экспертизой в опенсорсных решениях внутри ВТБ на момент старта проекта.

При выборе платформы мы учитывали следующие критерии:

  • функциональность

  • качество поддержки

  • отсутствие санкционных рисков

  • возможность гибкого масштабирования

  • надёжность

  • безопасность

  • наличие Road Map развития платформы и возможность на него влиять

  • стоимость владения совокупные затраты на программно-аппаратный комплекс на горизонте 10 лет (TCO5).

Если взять последний критерий, то даже с учётом стоимости всех контуров Arenadata DB и самого проекта миграции мы получали существенную экономию на фоне Oracle SuperCluster.

В итоге по совокупности факторов мы выбрали Arenadata DB.

Тестирование платформы

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

В итоге нами были проведены следующие проверки.

  • Функциональное тестирование:

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

  • Отказоустойчивость и отключение компонентов:

  • Отключение дисковых устройств на уровне БД для проверки стабильности работы кластера.

  • Отключение питания одного блока питания на серверах для проверки стабильности работы кластера.

  • Отключение сетевого подключения для имитации выхода из строя узла кластера в ходе тестирования отмечается, продолжил ли работать кластер, и фиксируется степень деградации производительности).

  • Совместимость со средствами резервного копирования банка:

    • Проведение цикла резервного копирования и восстановления БД на СРК Veritas Netbackup:

      • Полное резервное копирование БД

      • Инкрементальное резервное копирование БД

      • Восстановление БД

  • Управление и качество работы системы:

    • Перезагрузка кластера: фиксация успешного выполнения процедуры перезагрузки.

    • Мониторинг и управление: субъективная балльная оценка от 0 до 5.

    • Генерация тестового отчёта: прогон запроса изсистемы подготовки отчётности в Arenadata DB для анализа качества результата генерируемого отчёта результаты выполнения отчёта должны быть идентичны.

  • Нагрузочное тестирование:

    • Скорость загрузки данных

  • Интеграционное тестирование:

    • Интеграция с ПО Infomatica Power Center

    • Интеграция с Oracle BI

    • Интеграция с QlikView 12.

Результаты тестирования

В ходе тестирования кластера Arenadata DB показал высокую производительность как на синтетических тестах, так и на реальных нагрузках.

Ниже приведено сравнение скорости выполнения запросов по сравнению с текущим Oracle Super Cluster T5-8.

Тестирование проводил системный интегратор IBS Platformix.

Скорость выполнения синтетического запроса Jmeter (сек)

Arenadata DB (сек.)

Oracle (сек.)

160.3

1291

Кластер показал высокую скорость загрузки данных через ETL-инструмент Informatica Power Center: 200 Мбит/с.

В ходе тестирования была также осуществлена интеграция с основными BI-инструментами, используемыми в Банке ВТБ (Oracle BI и QlikView), и протестирован их функционал.

В QlikView на простейших SQL-запросах протестированы соединение с БД и выборка данных с последующей загрузкой в модель BI-инструмента.

Результаты выполнения представлены в таблице ниже.

Тест 1

Тест 2

Драйвер

ODBC PostgreSQL35W

ODBC PostgreSQL35W

Запрос

select * from user.test1

// 3 коротких поля

select e.* from dds.accounts e

where

e.entry_dt ='2019-02-03'

-and e.partition_source_system_cd ='00006'

and e.src_deleted is null

Строк

20480000

45 920

Затраченное время

0:58

2:59

Скорость загрузки в модель, строк в сек.

353103

257

При выполнении тестов была замечена особенность: получение первых строк данных из БД происходило с задержкой примерно в 23 секунды. После этого скорость выборки данных из БД и их доставки в QlikView становилась очень высокой.

Предположительно данная особенность связана c неоптимальной настройкой коннектора.

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

Цель теста

Предварительные условия

Процедура

Результат

Тестирование отказа диска с данными

Отказ диска эмулируется физическим извлечением диска или логическим отключения дискового уст-ва из работающего сервера.

Подключиться к серверу

Провести процедуру unmount для физического диска

Проверить доступность данных

Данные доступны

Тестирование отказа кэширующего диска

Отказ диска эмулируется физическим извлечением диска или логическим отключения дискового устройства из работающего сервера

Подключиться к серверу

Провести процедуру unmount для физического диска

Проверить доступность данных

Данные доступны

(Отказ кэширующего диска не приводит к потере данных)

Тестирование включения кластера после эмуляции аварии

Отказ сервера при выполнении SQL запроса к базе данных, эмулируется отключением электропитания работающего сервера

Подключиться к серверу

Выполнить SQL запрос

Выключить 1 ноду

Перезапустить выполнение SQL

Данные получены при повторном SQL запросе

Благодарю Дениса Степанова и Никиту Клименко, экспертов IBS Platformix, за предоставленные результаты тестирования.

Сбор отчётности как пилот

Наша цель это миграция на Arenadata DB всех существующих хранилищ банка ВТБ.

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

Arenadata новая для нас платформа, а в перспективе она должна была стать стратегически важным элементом архитектуры. Поэтому мы выстроили партнёрство с компанией-производителем максимально плотно, вплоть до выравнивания планов по развитию. В рамках этого партнерства часть функционала, который был нам необходим для сбора обязательной отчетности, реализовали чуть раньше. Доработки позволили нам развернуть Arenadata на разных ЦОДах, обеспечив таким образом георезервирование.

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

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

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

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

Поскольку мы уже видим результаты и детали взаимодействия, около полугода назад стартовал наш основной проект - миграция на продукты Arenadata центрального единого хранилища данных и озера данных. Помимо Arenadata DB, мы используем Arenadata Streaming на базе Apache Kafka и Arenadata Hadoop на базе Apache Hadoop. В ближайшее время первые результаты пойдут в продакшен.

Целевая архитектура платформы данных к концу 2022 годаЦелевая архитектура платформы данных к концу 2022 года

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

Подробнее..

Перевод Автоматические миграции в Room

05.05.2021 00:09:59 | Автор: admin

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

Начиная с версии 2.4.0-alpha01, миграция структуры базы данных в Room существенно упростилась появились автоматические миграции (auto-migration).

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

В более сложных (и неоднозначных) случаях Room'у потребуется помощь: вам нужно будет указать, какой столбец или таблица была переименована или удалена. На основании этих данных Room автоматически сгенерирует и запустит миграцию.

Давайте рассмотрим несколько примеров, чтобы лучше понять, как это работает.

Включаем автоматические миграции

Предположим, мы добавляем новый столбец в таблицу (переходим от версии 1 к версии 2). В результате мы должны будем обновить аннотацию @Database, увеличив номер версии и прописав конкретные версии в AutoMigration:

И каждый раз, когда версия вашей базы данных будет меняться, просто обновляйте список автоматических миграций, добавляя новый элемент AutoMigration:

В каких случаях Room справится сам? Когда сможет разобраться, что поменялось в схеме БД. Для него не составит труда выявить:

  • добавление нового столбца,

  • обновление первичного ключа, внешнего ключа или индекса,

  • изменение значения по умолчанию и т.д.

Важно: автоматические миграции полагаются на сгенерированную схему базы данных. Если собираетесь использовать автоматические миграции, убедитесь, что значение параметра exportSchema равно true. Иначе получите ошибку: Cannot create auto-migrations when export schema is OFF.

Когда потребуется ручное вмешательство?

Повторюсь, иногда Room'у требуется помощь. Он не в силах обнаружить все возможные варианты изменений, вносимых в базу данных.

Типичный пример: Room не сможет достоверно определить, была ли таблица (или столбец) переименована или удалена. А когда это выяснится, Room выбросит ошибку компиляции и попросит реализовать AutoMigrationSpec, который описывает внесённые изменения.

При реализации интерфейса AutoMigrationSpec используйте следующие аннотации:

  • @DeleteTable(tableName) имя таблицы, которую мы удалили,

  • @RenameTable(fromTableName, toTableName) старое и новое имя таблицы, которую мы переименовали,

  • @DeleteColumn(tableName, columnName) имя столбца, который мы удалили,

  • @RenameColumn(tableName, fromColumnName, toColumnName) старое и новое имя столбца, который мы переименовали, с указанием конкретной таблицы.

Предположим, мы решили переименовать таблицу Doggos в GoodDoggos. Room не может определить:

  • или GoodGoggos , действительно, новая таблица, а старую таблицу (Doggos) мы удалили,

  • или же старая таблица Doggos была переименована, и всю содержащуюся в ней информацию нужно сохранить.

Поэтому пропишем все изменения вручную:

Обычные миграции vs. автоматические миграции

Когда стоит использовать миграции

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

Предположим, мы решили разбить одну таблицу на две. Room не может определить, как было выполнено это разделение. Соответственно, он не может автоматически определить, какие данные нужно переместить.

В этом случае необходимо реализовать класс Migration, добавив в него databaseBuilder() с помощью метода addMigrations():

Комбинируем обычные и автоматические миграции

Room позволяет комбинировать обычные и автоматические миграции. Например, миграция между версиями 1 и 2 может быть выполнена с помощью Migration, а переход с 2 на 3 с помощью автоматической миграции и т.д.

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

Как это работает?

Автоматические миграции работают так: сначала создаётся обычный класс Migration, логика его работы известна и описана здесь.

Если вкратце, при первом обращении к базе данных Room проверяет, отличается ли текущая версия от той, что есть в @Database. Если отличается, Room ищет соответствующие миграции и последовательно их запускает.

Тестирование автоматических миграций

Для тестирования автоматических миграций используйте MigrationTestHelper и вызывайте helper.runMigrationsAndValidate() так же, как при использовании класса Migration.

Подробнее о тестировании миграций читайте в документации.

Заключение

Автоматические миграции позволяют обрабатывать простые изменения схемы базы данных. Включение этой функции не составляет труда добавьте параметр autoMigrations в аннотацию @Database (не забудьте про exportSchema = true).

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

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

Полезные ссылки

  1. Документация по @AutoMigration.

  2. Список коммитов, которые вошли в версию 2.4.0-alpha01.

Подробнее..

Используем очереди совместно с БД обсуждение проблем, возможные способы решения

30.04.2021 18:12:21 | Автор: admin

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

О подводных камнях такого пути поговорим в этой статье.

Рано или поздно, применяя очереди, пользователь сталкивается с вопросом использования их совместно с каким-то сервисом, базой данных и т.п.

  • Заказ выполнен, нужно отправить СМС-уведомление пользователю.

  • Поступил новый заказ, нужно отправить Push-уведомление исполнителям.

  • Работа выполнена, нужно списать деньги со счёта клиента.

Во всех перечисленных примерах изменения в какой-то бизнес-сущности фиксируются в БД (или сервисе с БД), и возникает большой соблазн соблазн отправлять уведомления с использованием очередей.

Что мы имеем в данной ситуации? Первоначальная, простейшая структура кода:

  • Сервис (наша программа) фиксирует изменение данных в БД.

  • Затем сервис кладёт задание в очередь.

Фактически в данном случае требуется реализовать событийный триггер на изменение записи данных.

И в общем случае, получается, здесь мы имеем две записи в две различные БД: сервиса и очереди.

Теперь перенесёмся в реальный мир и рассмотрим, какие ситуации могут возникнуть:

  • Всё в порядке. БД доступна, БД очереди доступна;

  • БД недоступна, БД очереди доступна;

  • БД доступна, БД очереди недоступна;

  • БД недоступна, БД очереди недоступна.

Наиболее опасен для бизнеспроцессов тут третий случай: после того, как данные были зафиксированы в БД, уведомить заинтересованные стороны не получилось.

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

Но со сложными решениями всё понятно, давайте подумаем о более простых.

Неправильные решения

Очень многие, впервые столкнувшись с этой проблемой (как правило, происходит это, когда система реализована и уже значительное время работает), начинают с того, что предлагают следующее решение:

  1. Открываем транзакцию в БД.

  2. Изменяем запись в БД.

  3. Записываем в очередь.

  4. Закрываем транзакцию в БД.

В этом случае, если одна из БД недоступна, то и в другую запись не запишется.

Однако тут имеются следующие проблемы:

  • Данное решение условно работоспособно только в том случае, если обработчик очереди никогда не будет обращаться к записи в БД. Поскольку обработчик очереди может взять задачу на исполнение между пунктами 3 и 4 (Записи в БД ещё не зафиксированы).

  • Закрытие транзакции также может завершиться с ошибкой. В этом случае задание в очереди останется неотменённым.

Выполнение всей работы в обработчике

Простейшее решение.

Вместо изменения записи кладём в очередь задание о том, что надо это сделать. Обработчик очереди выполняет оба действия: и изменения записи в БД, и триггерное. Это решение сложнее, чем первоначальное, но всё равно довольно простое.

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

Дополнительная таблица в БД

Более простое решение, нежели двухфазный коммит.

В игре у нас появляется дополнительный процесс/демон, перекладывающий задачи из дополнительной таблицы БД (назовём её queue) в очередь.

В этом случае запросы, модифицирующие БД, пишут записи в две таблицы в той же транзакции (или в том же запросе):

/* было */UPDATE   "orders"SET   "status" = 'complete'WHERE   "order_id" = $1RETURNING   *
/* стало */WITH "o" AS (    UPDATE       "orders"    SET       "status" = 'complete'    WHERE       "order_id" = $1    RETURNING       *),"q" AS (   INSERT INTO        "queue"   (      "key",      "data"   )   SELECT      "o.order_id",      "o.status"   FROM      "o")SELECT   *FROM   "o"

Примечание: Код записи в таблицу queue можно вынести в триггер уровня БД, а также спроектировать так, чтобы сообщения были универсальными и подходили не только для orders.

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

Простейший алгоритм цикла демона:

  • Взять задачу из таблицы queue.

  • Положить её в очередь.

  • Удалить задачу в таблице.

Локальный лог/кеш сообщений

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

  • резко понизить вероятность неуспеха при отправке сообщения в очередь.

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

  • сервис (наша программа) фиксирует изменение данных в БД,

  • затем сервис кладёт задание в локальный лог.

  • Дополнительный демон перекладывает сообщения из этого лога (или локальной очереди) в общую очередь.

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

Итоги

Как видим, вариантов решения проблемы немного. Если мы хотим сохранять систему простой (KISS-принцип), то введение дополнительного демона и кеша/лога сообщений в БД или локальном файле/БД даст небольшое увеличение сложности. При этом очень важно сохранять обработчик идемпотентным, поскольку при сбоях в момент перекладывания заданий из локального кеша в общую очередь возможно появление дублей.

А обобщённое решение предполагает использование двухфазного коммита.

Подробнее..

Транзакции. Часть 1. Конспект книги Designing Data-Intensive Applications

16.05.2021 10:11:39 | Автор: admin

Эта статья является конспектом книги Designing Data-Intensive Applications.

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

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

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

Скользкая концепция транзакции

В конце 2000-х годов приобрели популярность нереляционные (NoSQL) базыданных. Их целью было улучшить существующее положение дел с реляционными БД с помощью новых моделей данных, репликациии секционирования. Транзакции оказались главной жертвой этогоновшества: многие базы нового поколения полностью от них отказались или поменяли значение термина: теперь он стал у них означать намного более слабый наборфункциональных гарантий, чем ранее.

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

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

Обеспечиваемые транзакциями гарантии функциональной безопасности частоописываются известной аббревиатурой ACID (atomicity, consistency, isolation,durability атомарность, согласованность, изоляция и сохраняемость).

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

Системы, не соответствующие критериям ACID, иногда называются BASE: как правило, доступна (BasicallyAvailable), гибкое состояние (Soft state) и конечная согласованность (Eventualconsistency). Это понятие еще более расплывчатое, чем ACID.

Давайте посмотрим на определения атомарности, согласованности, изоляции и сохраняемости. Это позволит уточнить наши представления о транзакции.

Атомарность

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

Напротив, в контексте ACID атомарность не связана с конкурентным доступом.Этот термин не описывает, что происходит, когда несколько процессов пытаютсяобратиться к одним и тем же данным одновременно, поскольку относится к понятию изоляции, то есть букве I в аббревиатуре ACID.

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

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

Согласованность

Слово согласованность ужасно перегружено.

  • Согласованность реплик и вопрос конечной согласованности, возникающий в асинхронно реплицируемых системах.

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

  • В теореме CAP слово согласованность используется для обозначения линеаризуемости (linearizability).

  • В контексте ACID под согласованностью понимается то, что база данных находится, с точки зрения приложения, в хорошем состоянии.

О первых трех пунктах можно подробнее прочесть в книге. Для репликации и секционирования автор книги выделил по целой главе.

К сожалению, одно и то же слово применяется как минимум в четырех различныхсмыслах.

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

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

Изоляция

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

Рис. 1 - Состояние гонки между двумя клиентами, конкурентно увеличивающимизначение счетчикаРис. 1 - Состояние гонки между двумя клиентами, конкурентно увеличивающимизначение счетчика

Изоляция в смысле ACID означает, что конкурентно выполняемые транзакции изолированы друг от друга они не могут помешать друг другу. Классические учебники по базам данных понимают под изоляцией сериализуемость (serializability).То есть каждая транзакция выполняется так, будто она единственная во всей базе.БД гарантирует, что результат фиксации транзакций такой же, как если бы они выполнялись последовательно, хотя в реальности они могутвыполняться конкурентно.

Сохраняемость

Сохраняемость (durability) обязательство базы не терять записанных (успешно зафиксированных) транзакций данных, даже в случае сбоя аппаратного обеспечения илифатального сбоя самой БД.

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

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

Выводы по ACID

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

  • Атомарность. Если посередине последовательности операций записи происходит ошибка, то транзакцию необходимо прервать, а выполненные до тогомомента операции аннулировать.

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

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

Обработка ошибок и прерывание транзакций

Отличительная особенность транзакций возможность их прерывания и безопасного повторного выполнения в случае возникновения ошибки. На этом принципепостроены базы данных ACID: при возникновении риска нарушения гарантий атомарности, изоляции или сохраняемости БД скорее полностью отменит транзакцию,чем оставит ее незавершенной.

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

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

  • Если причина ошибки в перегруженности, то повтор транзакции толькоусугубит проблему.

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

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

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

Слабые уровни изоляции

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

Базы данных долгое время пытались инкапсулировать вопросы конкурентного доступа от разработчиков приложений путем изоляции транзакций(transaction isolation). Теоретически изоляция должна была облегчить жизнь разработчиков, которые смогли бы сделать вид, что никакого конкурентного выполненияне происходит: сериализуемая изоляция означает гарантию базой данных такогорежима выполнения транзакций, как будто они выполняются последовательно.

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

Автор книги не стал отдельно рассматривать уровень изоляции чтение незафиксированных данных (read uncommitted). Он предотвращает грязные операции записи,но не грязные операции чтения.

Чтение зафиксированных данных

Этот уровень изоляции обеспечивает две гарантии:

  • При чтении из БД клиент видит только зафиксированные данные (никакихгрязных операций чтения).

  • При записи в БД можно перезаписывать только зафиксированные данные (никаких грязных операций записи).

Если транзакция записала данные в базу, но еще не была зафиксирована или была прервана и другая транзакция увидела эти незафиксированные данные, то такая операция чтения называется грязной (dirty read).

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

Рис. 2 - Никаких грязных операций чтения: пользователь 2 видит новое значение x только после фиксации результатов транзакции, выполняемой пользователем 1Рис. 2 - Никаких грязных операций чтения: пользователь 2 видит новое значение x только после фиксации результатов транзакции, выполняемой пользователем 1

Если более ранняя операция записи представляет собой часть еще не зафиксированной транзакции и более поздняя транзакция перезаписывает незафиксированное значение, то такая операция называется грязной операцией записи.

Чтение зафиксированных данных предотвращает казусы, например, как на рис. 3, связанные с грязной операцией записи.

Рис. 3 - В случае грязных операций записи конфликтующие операции записи различных транзакций могут оказаться перепутаныРис. 3 - В случае грязных операций записи конфликтующие операции записи различных транзакций могут оказаться перепутаны

Чтение зафиксированных данных очень популярный уровень изоляции. Он используется по умолчанию в Oracle 11g, PostgreSQL, SQL Server 2012, MemSQLи многих других базах данных.

Чаще всего базы используют блокировки строк для предотвращения грязныхопераций записи: прежде чем модифицировать конкретный объект (строку илидокумент), транзакция должна сначала установить блокировку на этот объект.Данная блокировка должна удерживаться вплоть до фиксации или прерываниятранзакции. Удерживать блокировку на конкретный объект может только однатранзакция одновременно, другой транзакции, желающей выполнить операциюзаписи в этот объект, придется дождаться фиксации или прерывания первойтранзакции и лишь затем получить на него блокировку и продолжить свою работу.Подобные блокировки выполняются базами автоматически в режиме чтения зафиксированных данных (и на более сильных уровнях изоляции).

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

Изоляция снимков состояния и воспроизводимое чтение

На первый взгляд уровня изоляции чтения зафиксированных данных вполнедостаточно для транзакций.

Однако на этом уровне изоляции все еще существует множество возможных ошибок конкурентного доступа. Например, на рис. 4 показана одна из вероятныхпроблем при чтении зафиксированных данных.

Рис. 4 - Асимметрия чтения: Алиса видит базу данных в несогласованном состоянииРис. 4 - Асимметрия чтения: Алиса видит базу данных в несогласованном состоянии

Подобная аномалия носит название невоспроизводимого чтения (nonrepeatable read)или асимметрии чтения (read skew): если Алиса прочитала бы баланс счета 1 опятьв конце транзакции, то увидела бы значение ($600), отличное от прочитанного предыдущим запросом. Асимметрия считается допустимой при изоляции уровнячтения зафиксированных данных: видимые Алисе балансы счетов были, безусловно, зафиксированы на момент их чтения.

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

  • Резервное копирование. Резервная копия представляет собой копию всей базыданных, и ее создание на большой БД может занять несколько часов. Операциизаписи в базу продолжают выполняться во время создания резервной копии.Следовательно, может оказаться, что одни части копии содержат старые версииданных, а другие новые. В случае восстановления БД из подобной резервной копии упомянутые расхождения (например, пропавшие деньги) станут извременных постоянными.

  • Аналитические запросы и проверки целостности. Иногда приходится выполнятьзапросы, просматривающие значительные части базы данных. Они также могут быть частьюпериодической проверки целостности (мониторинга на предмет порчи данных).Если подобные запросы будут видеть разные части БД по состоянию на различные моменты времени, то их результаты будут совершенно бессмысленными.

Изоляция снимков состояния чаще всего используемое решение этой проблемы. Ее идея состоит в том, что каждая из транзакций читает данные из согласованного снимка состояния БД, то есть видит данные, которые были зафиксированыв базе на момент ее (транзакции) начала.

В других источниках данный уровень изоляции может называться повторяющимся чтением (repeatable read)

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

Базы данных применяют для реализации изоляции снимков состояния похожий механизм, действие которого по части предотвращения грязных операций чтения мы наблюдали на рис. 2. БД должна хранить для этого несколько различныхзафиксированных версий объекта, поскольку разным выполняемым транзакциямможет понадобиться состояние базы на различные моменты времени. Вследствиехранения одновременно нескольких версий объектов этот метод получил названиемноговерсионного управления конкурентным доступом (multiversion concurrencycontrol, MVCC).

Если базе необходима только изоляция уровня чтения зафиксированных данных,но не уровня изоляции снимков состояния, достаточно было бы хранить толькодве версии объекта: зафиксированную версию и перезаписанную, но еще не зафиксированную версию. Однако поддерживающие изоляцию снимков состояния подсистемы хранения обычно используют MVCC и для изоляции уровня чтениязафиксированных данных. При этом обычно при чтении таких данных применяется отдельный снимок состояния для каждого запроса, а при изоляции снимковсостояния один и тот же снимок состояния для всей транзакции.

Рис. 5 - Реализация изоляции снимков состоянияс помощью многоверсионных объектовРис. 5 - Реализация изоляции снимков состоянияс помощью многоверсионных объектов

В каждой строке таблицы есть поле created_by, содержащее идентификатор транзакции, вставившей эту строку в таблицу. Более того, в каждой строке таблицы естьполе deleted_by, изначально пустое. Если транзакция удаляет строку, то строка на самом деле не удаляется из базы данных, а помечается для удаления путемустановки значения этого поля в соответствии с идентификатором запросившейудаление транзакции. В дальнейшем, когда уже никакая транзакция точно не обратится к удаленным данным, процесс сборки мусора БД удалит все помеченныедля удаления строки и освободит занимаемое ими место.

Более подробно о правилах видимости для согласованных снимков состояния можно прочесть в книге.

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

Ссылки на все части

Подробнее..

Транзакции. Часть 2. Конспект книги Designing Data-Intensive Applications

23.05.2021 14:09:40 | Автор: admin

Эта статья является конспектом книги Designing Data-Intensive Applications.

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

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

Асимметрия записи и фантомы

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

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

Рис. 1 - Пример асимметрии записи, вызванной ошибкой в коде приложенияРис. 1 - Пример асимметрии записи, вызванной ошибкой в коде приложения

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

Такая аномалия носит название асимметрии записи (write skew). Это не грязная операция записи и не потеря обновления, поскольку две наши транзакции обновляют два различных объекта. Наличие конфликта тут менее заметно, но это, безусловно, состояние гонки:если две транзакции выполнялись бы одна за другой, то второй врач не получил быотгула.

Асимметрию записи можно рассматривать как обобщение проблемы потери обновлений. Эта асимметрия может происходить при чтении двумя транзакциямиодних и тех же объектов с последующим обновлением некоторых из них (различныетранзакции могут обновлять разные объекты). В частном случае, когда различныетранзакции обновляют один объект, происходит грязная операция записи илипотеря обновления (в зависимости от хронометража).

Если использовать уровень сериализуемости невозможно, то вторым лучшим решением будет, вероятно, явная блокировка строк, необходимых для транзакции. В примере с врачами можно написать примерно следующиезапросы:

BEGIN TRANSACTION;SELECT * FROM doctorsWHERE on_call = trueAND shift_id = 1234 FOR UPDATE;UPDATE doctorsSET on_call = falseWHERE name = 'Alice'AND shift_id = 1234;COMMIT;

Предложение FOR UPDATE указывает базе установить блокировкуна все возвращенные данным запросом строки.

В примере с дежурными врачами измененная строка была одной из строк,возвращенных в первом запросе, так что можно было обезопасить транзакцию и избежатьасимметрии записи, блокируя строки (SELECT FOR UPDATE). Однако если выполняется проверка на отсутствиеудовлетворяющих определенному условию поиска строк, а операция записи вставляет соответствующую этому же условию строку, то первый запрос на получения данных не вернетничего и SELECT FOR UPDATE не на что будет устанавливать блокировки.

Такой эффект, при котором операция записи в одной транзакции меняет результатзапроса на поиск в другой, называется фантомом (phantom).

Для предотвращения таких ситуация в большинстве случаев предпочтительнее использоватьизоляцию уровня сериализуемости.

Сериализуемость

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

Но если сериализуемость настолько лучше более слабых уровней изоляции, почему бы не использовать ее повсеместно? Для ответа на данный вопрос рассмотримвероятные реализации сериализуемости и их производительность. Большинство современных БД, обеспечивающих сериализуемость, применяют один из трех методов:

  • действительно последовательное выполнение транзакций;

  • двухфазную блокировку;

  • методы оптимистического управления конкурентным доступом, например,сериализуемую изоляцию снимков состояния (SSI).

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

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

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

Этот пересмотр концепции был вызван двумя факторами.

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

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

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

  • Все транзакции должны быть маленькими и быстрыми, поскольку одна-единственная медленная транзакция может застопорить всю работу.

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

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

Двухфазная блокировка (2PL). В течение долгого времени в базах данных широко использовался только один алгоритм сериализуемости: двухфазная блокировка (two-phase locking, 2PL).

Обратите внимание, что, хотя название двухфазной блокировки (2PL) оченьсхоже с названием двухфазной фиксации транзакций (2PC), это две совершенно разные вещи.

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

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

Из-за столь большого количества блокировок часто встречается ситуация, когдатранзакция A ждет снятия блокировки транзакции B и наоборот. Такая ситуацияназывается взаимной блокировкой (deadlock). База данных автоматически обнаруживает взаимные блокировки между транзакциями и прерывает одну из них, чтобыостальные могли продолжить работу.

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

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

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

Сериализуемая изоляция снимков состояния (SSI). В этом конспекте мы увидели довольно мрачную картину управления конкурентнымдоступом в БД. С одной стороны, имеющиеся реализации сериализуемости или демонстрируют низкую производительность (двухфазная блокировка), илиплохо масштабируются (последовательное выполнение). С другой слабые уровниизоляции показывают высокую производительность, но подвержены различнымсостояниям гонки (потеря обновлений, асимметрия записи, фантомы и т. д.). Такчто же, сериализуемая изоляция и хорошая производительность вещи принципиально взаимоисключающие?

Очень многообещающим представляется алгоритм под названиемсериализуемая изоляция снимков состояния (serializable snapshot isolation, SSI).Он обеспечивает полную сериализуемость за счет лишь небольшого сниженияпроизводительности по сравнению с обычной изоляцией снимков состояния. SSI относительно новый метод: он был впервые описан в 2008 году.

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

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

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

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

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

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

  • выявление операций чтения устаревших версий MVCC-объектов (перед чтением произошла незафиксированная операция записи);

  • выявление операций записи, влияющих на предшествующие операции чтения(операция записи произошла после чтения).

Выявление операций чтения устаревших версий MVCC объектов. Изоляция снимков состояния обычно реализуется с помощьюMVCC.Транзакция, читающая из согласованного снимка состояния в базе данных MVCC,игнорирует все операции записи, которые были выполнены транзакциями, ещене зафиксированными на момент получения снимка состояния. На рис. 2 транзакция 43 видит, что Алиса находится на дежурстве (on_call = true), посколькутранзакция 42 не зафиксирована. Однако на моментфиксации транзакции 43 транзакция 42 уже зафиксирована. Это значит, что операция записи, проигнорированная при чтении из согласованного снимка состояния,теперь уже вступила в силу и исходные условия транзакции 43 более не соответствуют действительности.

Рис. 2 - Выявление чтения транзакцией устаревших значений из снимка состояния MVCCРис. 2 - Выявление чтения транзакцией устаревших значений из снимка состояния MVCC

Для предотвращения этой аномалии база данных должна отслеживать случаи игнорирования транзакцией вследствие правил видимости MVCC операций записидругих транзакций. При попытке фиксации транзакции база проверяет, были лизафиксированы какие-либо из проигнорированных операций записи. Если да, тотранзакцию необходимо прервать.

Выявление операций записи, влияющих на предшествующиеоперации чтения. Второй случай когда другая транзакция модифицирует данные после их чтения.

Рис. 3 - Выявление при сериализуемой изоляции снимков состояния случаев модификации транзакцией данных, прочитанных другой транзакциейРис. 3 - Выявление при сериализуемой изоляции снимков состояния случаев модификации транзакцией данных, прочитанных другой транзакцией

На рис. 3 обе транзакции, 42 и 43, выполняют поиск дежурящих в смену 1234 врачей. При наличии индекса по shift_id база может воспользоваться его записью 1234, чтобы отметить факт чтения транзакциями 42 и 43 этих данных (еслииндекса нет, информацию можно отслеживать на уровне таблицы). Сама информация требуется только временно: после завершения выполнения транзакции(ее фиксации или прерывания) и завершения всех конкурентных транзакций БДможет спокойно забыть о том, какие данные читались этой транзакцией.

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

На рис. 3 транзакция 43 уведомляет транзакцию 42, что прочитанные ею в предыдущей операции чтения данные устарели и наоборот. Первой выполняется фиксация транзакции 42, и она проходит успешно: хотя операция записи транзакции 43влияет на 42, транзакция 43 пока еще не зафиксирована, поэтому данная операциязаписи еще не вступила в силу. Однако на момент фиксации транзакции 43 конфликтующая операция записи транзакции 42 уже была зафиксирована, так чтотранзакцию 43 придется прервать.

Вывод

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

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

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

  • Грязные операции чтения. Клиент читает записанные другим клиентомданные до их фиксации. Уровень изоляции чтения зафиксированных данныхи более сильные предотвращают грязные операции чтения.

  • Грязные операции записи. Клиент перезаписывает данные, которые другойклиент записал, но еще не зафиксировал. Практически все реализации транзакций предотвращают грязные операции записи.

  • Асимметрия чтения (невоспроизводимое чтение). Клиент видит различные части базы данных по состоянию на разные моменты времени. Чаще всего такуюпроблему предотвращают с помощью изоляции снимков состояния, при которойтранзакция читает данные из согласованного снимка состояния, соответствующего определенному моменту времени. Обычно это реализуется благодарямноговерсионному управлению конкурентным доступом (MVCC).

  • Потерянные обновления. Два клиента выполняют в конкурентном режиме циклчтения изменения записи. Один переписывает записанные другим данныебез учета внесенных им изменений, так что данные оказываются потеряны.Некоторые реализации изоляции снимков состояния предотвращают эту аномалию автоматически, а в других требуется установка блокировки вручную(SELECT FOR UPDATE).

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

  • Фантомные чтения. Транзакция читает объекты, соответствующие определенному условию поиска. Другой клиент выполняет операцию записи, котораякаким-то образом влияет на результаты этого поиска. Изоляция снимков состояния предотвращает непосредственно фантомные чтения, но фантомы в контексте асимметрии записи требуют отдельной обработки, например, блокировокпо диапазону значений индекса.

Слабые уровни изоляции защищают от некоторых из этих аномалий, но другиепридется обрабатывать вручную (например,с помощью явной блокировки). Только изоляция уровня сериализуемых транзакций предотвращает все подобные проблемы. Рассмотрели три различных подходак реализации сериализуемых транзакций.

  • По-настоящему последовательное выполнение транзакций. Если вы можетесделать отдельные транзакции очень быстрыми, причем количество транзакций,обрабатываемых за единицу времени на одном ядре CPU, достаточно невелико,то для обработки этот вариант окажется простым и эффективным.

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

  • Сериализуемая изоляция снимков состояния (SSI). Довольно свежий алгоритм,лишенный практически всех недостатков предыдущих подходов. В нем используется оптимистический подход, благодаря чему транзакции выполняются без блокировок. Перед фиксацией транзакции выполняется проверка,и если выполнение было несериализуемым, то транзакция прерывается безфиксации.

Ссылки на все части

Подробнее..

Про uuid-ы, первичные ключи и базы данных

16.06.2021 00:20:01 | Автор: admin

Статья посвящена альтернативным версиям Qt-драйверов для работы с базами данных. По большому счету отличий от нативных Qt-драйверов не так много, всего пара: 1) Поддержка типа UUID; 2) Работа с сущностью "Транзакция" как с самостоятельным объектом. Но эти отличия привели к существенному пересмотру кодовой реализации исходных Qt-решений и изменили подход к написанию рабочего кода.

Первичный ключ: UUID или Integer?

Впервые с идеей использовать UUID в качестве первичного ключа я познакомился в 2003 году, работая в команде дельфистов. Мы разрабатывали программу для автоматизации технологических процессов на производстве. СУБД в проекте отводилась существенная роль. На тот момент это была FireBird версии 1.5. По мере усложнения проекта появились трудности с использованием целочисленных идентификаторов в качестве первичных ключей. Опишу пару сложностей:

  • Проблема архитектурная: периодически заказчики присылали справочные данные с целью включения их в новую версию дистрибутива. Иногда справочники содержали первичные ключи уже имеющиеся в нашей базе. Приходилось устранять коллизии в процессе агрегирования данных. На этом проблемы не заканчивались: при разворачивании нового дистрибутива периодически возникали обратные коллизии.

  • Проблема программная: чтобы получить доступ к вставленной записи нужно было выполнить дополнительный SELECT-запрос, который возвращал максимальное значение первичного ключа (значение для только что вставленной записи). Причем этот процесс должен был проходить в пределах одной транзакции. Далее можно было обновлять или корректировать запись. Это сейчас я знаю, что некоторые драйверы БД возвращают значение первичного ключа для вставленной записи, но в 2003 году мы такими знаниями не обладали, да и не припомню что бы Делфи-компоненты возвращали что-то подобное.

Использование UUID-ов в качестве первичных ключей сводило к минимуму архитектурную проблему, и полностью решало программную. UUID-ключ генерировался перед началом вставки записи на стороне программы, а не в недрах сервера БД, таким образом дополнительный SELECT-запрос стал не нужен, и требование единой транзакции утратило актуальность. FireBird версии 1.5 не имел нативной поддержки UUID-ов, поэтому использовались строковые поля длинной в 32 символа (дефисы из UUID-ов удалялись). Факт использования строковых полей в качестве первичных ключей нисколько не смущал, нам не терпелось опробовать новый подход при работе с данными.

У UUID-ов есть свои минусы: 1) Существенный объем; 2) Более низкая скорость работы по сравнению с целочисленными идентификаторами. В рамках проекта достоинства оказались более значимы, чем указанные недостатки. В целом, опыт оказался положительным, поэтому в последующих решениях при создании реляционных связей предпочтение отдавалось именно UUID-ам.

Примечание: Более подробный анализ UUID vs Integer для СУБД MS SQL можно посмотреть в статье "Первичный ключ GUID или автоинкремент?"

Первый драйвер для FireBird

В 2012 году мне снова довелось поработать с FireBird. Нужно было создать небольшую программу по анализу данных. Разработка велась с использованием QtFramework. Примерно в это же время у FireBird вышла версия 2.5 с нативной поддержкой UUID-ов. Я подумал: "Почему бы не добавить в Qt-драйвер для FireBird поддержку типа QUuid?" Так появилась первая версия Qt-драйвера с поддержкой UUID-ов. Этот вариант не сильно отличался от оригинальной версии драйвера и, в основном, был ориентирован на использование в однопоточных приложениях.

Появление сущности "Транзакция"

Следующая модификация Qt-драйвера для FireBird произошла в конце 2018 года. Наша фирма взялась за разработку проекта по анализу данных большого объема. Для фирмы выросшей из стартап-а эта работа была очень важна, как с финансовой, так и с репутационной точек зрения. Сроки исполнения были весьма жесткие. В качестве СУБД была выбрана FireBird, несмотря на определенные сомнения в ее пригодности. Хорошим вариантом могла бы стать PostgreSQL, но у нашей команды на тот момент отсутствовал опыт эксплуатации данной СУБД.

Архитектура программы предполагала подключение к базе данных из разных потоков. Нативный Qt-FireBird драйвер не очень подходил для такого режима работы. К тому же, у концепции Qt-драйверов, на мой взгляд, есть один существенный недостаток: управление транзакциями (точнее одной транзакцией) происходит на уровне объекта подключения к базе данных (сущность "Транзакция" инкапсулирована внутри объекта Driver). То есть при работе с нативным Qt-драйвером в один момент времени можно оперировать только одной транзакцией. Почему так сделано, в принципе, понятно: достаточно много популярных СУБД работают по этой схеме (одно подключение - одна транзакция). В качестве примера можно назвать Oracle, PostgreSQL, MS SQL при работе через ODBC. Но FireBird не такой, его API позволяет оперировать сразу несколькими транзакциями в контексте одного подключения. Обладая этими знаниями, я начал адаптацию Qt-FireBird драйвера для работы в многопоточном приложении.

Первоначальная концепция предполагала небольшое (2-3) подключений к базе данных, при этом каждый поток приложения должен был иметь возможность работать со своей персональной транзакцией. Таким образом у одного подключения могло быть несколько активных транзакций выполняющихся в разных потоках. Для достижения этой цели код драйвера пришлось основательно переработать. К сожалению, в конце меня ждал неприятный сюрприз: при нагрузочном тестировании выяснилось, что если от одного подключения создать несколько транзакций, и выполнять их в разных потоках, то фактически, в один момент времени будет выполняться только одно sql-утверждение в рамках одной транзакции, а остальные транзакции будут находиться в состоянии ожидания. В общем, концепция параллельных транзакций с треском провалилась. Пришлось в срочном порядке переходить к решению "один поток - одно подключение". В плане параллельного выполнения sql-запросов этот вариант работал значительно бодрее.

Новая концепция не нуждалась в сущности "транзакция", как в самостоятельной единице, тем не менее, я не стал ее упразднять. Дальнейшая эксплуатация показала, что наличие объекта "транзакция" делает работу с базой данных более гибкой, дает больше инвариантов при написании кода. Например, разработчик может передать объект "Транзакция" в функцию в качестве параметра, явно говоря таким образом, что внутри нужно работать в контексте указанной транзакции. В функции можно проверить активна транзакция или нет, можно выполнить COMMIT или ROLLBACK. Для вспомогательных операций можно создавать альтернативную транзакцию, не затрагивающую основную. Таких возможностей нет у нативных Qt-драйверов.

Ниже приведен пример с тремя функциями, которые последовательно вызываю друг друга. Каждая функция запрашивает объект подключения к базе данных (Driver) у пула коннектов. Так как функции вызываются в одном потоке, они получают объект коннекта, ссылающийся на одно и тоже подключение к БД. Далее в каждой функции создается собственный независимый объект транзакции и все последующие запросы будут выполняются в его контексте.

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

Кто-то скажет: "Зачем все так усложнять?! Вот есть же понятная концепция 'Один коннект - одна транзакция', с ней легко и просто работать!" Действительно, в большинстве случаев этого будет достаточно, но я хорошо помню, еще по команде дельфистов, как эта "простота" выходила нам боком при работе с ODBC драйверами. Поэтому считаю, что наличие инвариантов - лучше, чем их отсутствие.

void function3(int value3){    db::firebird::Driver::Ptr dbcon = fbpool().connect();    db::firebird::Transaction::Ptr transact3 = dbcon->createTransact();    QSqlQuery q3 {db::firebird::createResult(transact3)};    if (!transact3->begin())        return;            if (!q3.prepare("INSERT INTO TABLE3 (VALUE3) VALUES (:VALUE3)"))        return;            sql::bindValue(q3, ":VALUE3" , value3);        if (!q3.exec())         return;    transact3->commit();}void function2(int value2){    db::firebird::Driver::Ptr dbcon = fbpool().connect();    db::firebird::Transaction::Ptr transact2 = dbcon->createTransact();    QSqlQuery q2 {db::firebird::createResult(transact2)};    if (!transact2->begin())        return;    if (!q2.prepare("SELECT * FROM TABLE2 WHERE VALUE2 = :VALUE2"))        return;             sql::bindValue(q2, ":VALUE2 " , value2);          if (!q2.exec())         return;             while (q2.next())    {        qint32 value3;        sql::assignValue(value3, q2.record(), "VALUE3");        function3(value3);    }}void function1(){    db::firebird::Driver::Ptr dbcon = db::firebird::pool().connect();    db::firebird::Transaction::Ptr transact1 = dbcon->createTransact();    QSqlQuery q1 {db::firebird::createResult(transact1)};        if (!transact1->begin())        return;            if (!sql::exec(q1, "SELECT * FROM TABLE1"))        return;            while (q1.next())    {        QSqlRecord r = q1.record();        QUuidEx  id;        qint32   value1;        qint32   value2;        sql::assignValue(id     , r, "ID     ");        sql::assignValue(value1 , r, "VALUE1 ");        sql::assignValue(value2 , r, "VALUE2 ");        ...        function2(value2);    }}

В примере экземпляры транзакций (1-3) созданы для наглядности. В рабочем коде их можно опустить. В этом случае транзакции будут создаваться неявно внутри объекта QSqlQuery. Неявные транзакции всегда завершаются ROLLBACK-ом для SELECT-запросов и попыткой COMMIT-а для всех остальных.

Ниже показано как можно использовать одну транзакцию для трех sql-запросов. Подтвердить или откатить транзакцию можно в любой из трех функций.

void function3(db::firebird::Transaction::Ptr transact, int value3){    QSqlQuery q3 {db::firebird::createResult(transact)};    // Тут что-то делаем}void function2(db::firebird::Transaction::Ptr transact, int value2){    QSqlQuery q2 {db::firebird::createResult(transact)};    // Тут что-то делаем    function3(transact, value3);}void function1(){    db::firebird::Driver::Ptr dbcon = db::firebird::pool().connect();    db::firebird::Transaction::Ptr transact = dbcon->createTransact();    QSqlQuery q1 {db::firebird::createResult(transact)};        if (!transact->begin())        return;            while (q1.next())    {        // Тут что-то делаем        function2(transact, value2);    }    transact->commit();}

Драйвер для PostgreSQL

В начале 2020 года мы приступили к новому объемному проекту. Требования к СУБД заказчик сформулировал однозначно: PostgreSQL. В отличие от ситуации с проектом 18-го года, сейчас время на изучение матчасти было. Для PostgreSQL хотелось создать драйвер похожий по поведению и функционалу на FireBird. Первой мыслью было подглядеть решение в Qt, но взять оттуда получилось только знание о том, что этот вариант нам не подходит. Работа Qt-драйвера построена на двух командах: PREPARE и EXECUTE. Эти команды могут работать только со строковым представлением, а это означает, что любые бинарные данные придется преобразовывать к строкам. Подумав, что "это не наш путь", я принялся отсматривать существующие решения и документацию по PostgreSQL API. Из библиотеки libpqxx получилось взять концепцию уровней изоляции для транзакций, все остальное было написано с нуля. Не обошлось без "ложки дегтя". Выяснилось, что для одного подключения к БД в один момент времени можно создать только одну транзакцию. Ограничение концептуальное, существует на уровне движка СУБД. Пример с тремя функциями, описанный выше, в PostgreSQL работать не будет. Были попытки посмотреть в сторону субтранзакций, но элегантного решения найти не удалось. Нивелировать проблему получилось переработкой пула коннектов. В него было добавлено свойство singleConnect(), определяющее режим создания нового подключения к базе данных. По умолчанию пул коннектов создает в одном потоке исполнения только одно подключение к БД. Свойство singleConnect() установленное в FALSE позволяет создавать новое подключение при каждом обращении к пулу. Таким образом, ограничение в одну транзакцию на одно подключение удалось обойти. Обратной стороной этого решения является большое количество подключений к базе. Но так как пул коннектов может использовать повторно уже существующие подключения, их количество не будет расти неконтролируемо и со временем придет к равновесному состоянию. Теперь пример с тремя функциями работает.

Драйвер для MS SQL

Пару раз на переговорах с заказчиками возникал вопрос по поводу интеграции с их системами через MS SQL. Чтобы опять не оказаться в ситуации цейтнота при разработке нового драйвера, было принято решение сделать его загодя. Драйвер для MS SQL реализован с использованием ODBC. В плане работы с транзакциями он похож на PostgreSQL: одно подключение - одна транзакция. Возможно, в драйвере OLE DB для MS SQL этого ограничения нет, но ODBC инвариантов не допускает. Сейчас драйвер имеет статус демонстрационного решения, в "бою" не применялся. На текущий момент, не реализованным остался биндинг и запись NULL-значений. Полагаю, к очередному проекту эту недоработку закроем.

Чего нет в классе Driver

Описываемые здесь драйверы не повторяют один в один функционал Qt-решений. В классе оставлены следующие методы:

  • beginTransaction();

  • commitTransaction();

  • rollbackTransaction().

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

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

  • tables();

  • record();

  • primaryIndex();

  • formatValue();

  • escapeIdentifier().

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

Еще один момент, на который хотелось бы обратить внимание: по умолчанию все драйверы работают в режиме "Forward Only". Точнее сказать это единственный режим, в котором работаю драйверы, при получении данных с сервера СУБД. Тем не менее, механизм кэширования имеется, он реализован при помощи класса SqlCachedResult. Основное назначение механизма - отображение данных в визуальных Qt-компонентах.

Новые функции

В классе Driver добавлена функция abortOperation(), она дает возможность асинхронно прерывать тяжелые sql-запросы, тем самым предотвращая "замерзание" приложения. В классе Result появилась сервисная функция size2(), она возвращает количество записей для подготовленного sql-запроса. Функция size2() расположена с защищенной секции, доступ к ней осуществляется через глобальную функцию resultSize(const QSqlQuery&). Знание о количестве записей бывает полезно при реализации механизма пагинации.

Лицензионные ограничения

Все драйверы могу использоваться только под лицензиями GPL/LGPL 2.1. Драйверы зависят от класса SqlCachedResult, который принадлежит компании Qt и распространяется под вышеозначенными лицензиями. Это налагает запрет на прямое включение кода драйверов в закрытые коммерческие проекты. Даже драйвер PostgreSQL, который фактически написан с нуля, подпадает под указанное ограничение (заражен копилефтом). Тем не менее, ситуация вполне разрешима: можно собрать драйвер как динамическую библиотеку и использовать ее в коммерческом продукте под лицензией LGPL. В этом случае, все формальности будут соблюдены.

Зависимости

В реализации драйверов используется система логирования ALog, которая является составной частью библиотеки общего назначения SharedTools.

Демо-примеры

Специально для этой статьи был создан демонстрационный проект. Он содержит примеры работы с тремя СУБД: FireBird, PostgreSQL, MS SQL. Репозиторий с драйверами расположен здесь, он подключен в проект как субмодуль. Библиотека SharedTools так же подключена как субмодуль.

Проект создан с использованием QtCreator, сборочная система QBS. Есть четыре сборочных сценария:

  1. db_demo_project.qbs - демо примеры для всех СУБД (содержит пункты 2-4);

  2. db_demo_firebird.qbs - демо пример для FireBird (требуется FireBird-клиент);

  3. db_demo_postgres.qbs - демо пример для PostgreSQL (требуется пакет libpq-dev);

  4. db_demo_mssql.qbs - демо пример для MS SQL.

Драйвера в первую очередь разрабатывались для работы в Linux, поэтому эксплуатационное тестирование выполнялось именно для этой ОС. В Windows будет работать FireBird-драйвер (проверено), для остальных драйверов тестирование не проводилось.

Демо-примеры записывают следующие логи:

  • /tmp/db-demo-firebird.log

  • /tmp/db-demo-mssql.log

  • /tmp/db-demo-postgres.log

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

Заключение

Черновой вариант статьи не предполагал наличие этого раздела, за что старый товарищ и, по совместительству, корректор подверг меня критике: "Мол, непонятна мотивация, целеполагание неясно. Зачем ты вообще писал эту статью?!" Что ж, исправляюсь!

В создание драйверов вложено много моего труда и труда коллег, потрачено время жизни. Зная нелюбовь программистов к внешним зависимостям, я не питаю иллюзий по поводу того, что представленные решения будут использоваться "как есть". Допускаю, что кто-то решит "выжечь каленым железом" ALog и заменит его на нечто свое - я не буду против (сам так поступаю с другими логгерами ;) В любом случае, если наши решения сэкономят кому-то время, или послужат отправной точной для новых идей - будет хорошо!

Подробнее..

Что нам стоит дом построить? (часть 2)

21.06.2021 12:17:59 | Автор: admin

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

Напомню, что в результате анализа мы пришли к следующей структуре объекта:

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

Таким образом, есть большое количество сущностей с разнообразным атрибутивным составом, который нужно хранить, тратя как можно меньше ресурсов, и расширять который хотелось бы также с минимальными усилиями.

Какие есть варианты?

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

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

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

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

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

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

Также стоит упомянуть про еще один способ организации данных, с которым я сталкивался. Он используется не в пример реже и больше напоминает "велосипед" - назовем его вертикальный. В хранилище создается таблица, которая содержит, условно, название поля, данные в нем, тип данных и связь с объектом. Возможно создание нескольких таких таблиц для разных типов данных или несколько колонок в одной таблице. Далее, при извлечении такие данные трансформируются из списка в плоский набор полей. Понимаете, почему я упомянул про велосипед?

А что у нас?

Второй вариант - это использование специализированных документоориентированных (или документных, как больше нравится) баз данных, реализующих NoSQL-подход к хранению и обработкенеструктурированной или слабоструктурированной информации. Наиболее часто данные хранятся в виде JSON объектов, но с предоставлением производителями СУБД инструментария для доступа к данным внутри этих структур.

У такого подхода, применительно к проектируемой системе, можно выделить несколько плюсов:

  • можем управлять структурой объектов без программирования, в том числе в реальном времени, путем администрирования. Администратор сможет зайти в некий интерфейс справочника полей и задать новые.

  • ограничиваем ситуации случайного воздействия на данные - модифицировать json объект гораздо сложнее, чем данные в колонке.

  • проще описывать объекты в коде - иногда можно вообще не описывать структуру документа в коде, а работать прямо с полями в JSON.

Но есть и минусы:

  • невозможно нативно реализовать проверки данных при размещении в хранилище.

  • валидацию данных придется проводить в коде.

  • невозможно организовать внутреннюю связность подчиненных данных, например, справочников.

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

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

Делаем прототип

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

Создадим следующие таблицы:

Для описания объектов в табличном виде:

  • r_objects, базовые данные по объектам: тип, дата создания и ссылка на хранилище атрибутов.

  • r_attributes. атрибуты, во всех возможные колонки для всех объектов. В идеале конечно же хранилища атрибутов для разных объектов лучше разбить по разным таблицам, но для прототипа это не критично.

Для описания объектов в виде JSON:

  • objects. Данные по объектам, где в поле data формата jsonb хранятся искомые атрибуты.

Остальные таблицы - это различные вспомогательные хранилища.

Реализуем в коде 5 разных типов объектов, для каждого из них описав программную структуру, механизм извлечения данных из хранилища и наполнения этими данными самих объектов.

Методика тестирования

Для тестирования обоих подходов хранения данных используем следующие методы:

  • добавление данных по объекту. Критерий успешности: объект с данными появился в хранилище, метод вернул в ответе его идентификатор.

  • обновление данных по объекту. Критерий успешности: данные в хранилище были изменены, метод вернул отметку об успехе. Удаление данных в системе не предусматривается, поэтому удаление объекта является операцией, аналогичной обновлению.

  • извлечение данных по объекту. Критерий успешности: объект с данными возвращен в ответе на запрос. Извлечение объекта происходит по конкретному идентификатору, по критериям поиска и постранично (пагинация).

Генерация запросов будет происходить в 20 параллельных потоков по 50 запросов в каждом потоке. Для того, чтобы тестирование было действительно показательным с точки зрения производительности, предварительно наполним базу 200 млн. объектов.

Тестирование показало следующие результаты:

График по тестированию табличного хранилищаГрафик по тестированию табличного хранилищаГрафик по тестированию NoSQL-хранилищаГрафик по тестированию NoSQL-хранилища

Первая (высокая) часть графика - это получение объектов по случайной странице - пагинация. Здесь в обоих случаях пришлось применить небольшой трюк - так как Postgres не агрегирует точное число строк в таблице, то узнать, сколько всего записей на объеме данных теста простым count - это долго, и для получения количества записей пришлось брать статистику данных по таблице. Также время получения данных на страницах свыше 10000-й неприлично велико, поэтому верхняя планка для получения случайного номера страницы была установлена в 10000. Учитывая специфику нашей системы, получение пагинированных данных не будет частой операцией, и поэтому такое извлечение данных применяется исключительно в целях тестирования.

Вторая (средняя) часть графика - вставка или обновление данных.

Третья (низкая) часть графика - получение данных по случайному идентификатору.

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

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

Результаты тестов на 40000 запросов приведу в виде таблицы:

Табличная

NoSQL

Объем хранилища

74

66

Среднее количество операций в секунду

970

1080

Время тестирования, секунды

42

37

Количество запросов

40000

40000

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

Что получилось?

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

Подробнее..

DataGrip 2021.1 Редактирование прав, контекстные шаблоны, предсказуемая навигация и не только

01.04.2021 20:04:02 | Автор: admin

Привет!

Сегодня мы выпустили DataGrip 2021.1: наш самый мощный релиз за последние годы. И это не шутка!

Самое важное:

  • Интерфейс для работы с правами доступа

  • Контекстные шаблоны Live Templates

  • Упрощенная навигация

  • Легкое копирование источников данных

  • Улучшенная сортировка

  • Редактирование данных в MongoDB

  • Поддержка Azure MFA

Редактирование прав

В окне редактирования объекта теперь можно изменять права на объект.

Также права можно добавлять и изменять в окне редактирования пользователя или роли. Напомним, что вызываются окна редактирования нажатием Cmd/Ctrl+F6.

Это работает в PostgreSQL, Redshift, Greenplum, MySQL, MariaDB, DB2, SQL Server и Sybase.

Контекстные шаблоны

Нас давно просили сделать как у других: чтобы для таблицы в проводнике можно было быстро сгенерировать простой запрос, например SELECT TOP 100 FROM %tableName%.

Мы всегда отвечали, что как только вы научитесь пользоваться шаблонами кода Live Templates, ваша жизнь изменится навсегда, и не надо будет тыкать мышкой во вложенные меню.

И это правда. Но бывает, что у пользователя открыт проводник и он находится в контексте нужного объекта в таком случае сгенерировать запрос к этому объекту на месте будет проще, чем переключаться в консоль, а потом находить этот объект, используя автодополнение. К тому же, многие все еще часто пользуются мышкой :)

В итоге мы сделали новый вид шаблонов кода контекстные шаблоны. Работают они так:

Посмотрим шаблон Select first N rows from a table. Найдите его в настройках:

Он выглядит, как обычный шаблон, и его можно использовать в таком качестве. Но у переменной $table$ есть специальное выражение dbObjectName(). Вы увидите это, нажав на Edit Variables. Так вот, именно это выражение делает шаблон контекстным, то есть значение в эту переменную можно подставить автоматически кликнув на любой объект в проводнике базы данных.

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

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

Редактор данных

Редактирование данных в MongoDB

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

<img src="http://personeltest.ru/aways/habrastorage.org/webt/1t/ux/8y/1tux8yvfyrs2eiha3byoj0ss2tu.png" />

Еще можно менять тип ячейки: из контекстного меню или панели инструментов при просмотре больших значений.

Сортировка

Сортировка стала более удобной:

  • Мы добавили поле ORDER BY, а <Filter> переименовали в WHERE. Впишите в ORDER BY условия сортировки, чтобы получился работающий запрос.

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

Если хотите отсортировать данные на стороне DataGrip, отключите Sort via ORDER BY. Конечно, в этом случае, сортируются данные только на текущей странице.

Теперь вы можете по умолчанию открывать таблицы отсортированными по числовому первичному ключу.

Панель инструментов

Мы немного обновили панель инструментов в редакторе данных: добавили кнопки Revert Changes и Find. Кнопки Rollback и Commit в режиме автоматического подтверждения транзакций скрываются.

Транспонирование однострочных результатов

Если в полученном результате одна строка, его удобно сразу смотреть в транспонированном виде. Мы сделали для этого настройку:

Навигация

Предсказуемые действия

Мы удалили эти настройки:

Если вы никогда не меняли их значения по умолчанию, то главное изменение в новой версии для вас такое: действие Go to declaration (Ctrl/Cmd+B) теперь открывает DDL этого объекта. Раньше оно подсвечивало объект в проводнике базы данных.

Для перемещения к объекту в проводнике мы представили новое сочетание клавиш: Alt+Shift+B для Windows/Linux и Opt+Shift+B для macOS.

Теперь нет сложной логики, и каждое нажатие горячих клавиш ведет в туда, куда вы ожидаете попасть:

  • Ctrl/Cmd+B открывает DDL.

  • F4 открывает данные.

  • Alt/Opt+Shift+B подсвечивает объект в проводнике.

Удаление настроек всегда ломает привычки некоторому количеству людей. Мы постарались учесть это. Вот наши советы таким пользователям:

  • Все сочетания клавиш можно менять. Если, например, вы не хотите отвыкать от того, что Ctrl/Cmd+B подсвечивает объект в проводнике, назначьте это сочетание клавиш действию Select in database tree.

  • В то же время, если вам нравится, что внутри скрипта Ctrl/Cmd+B и Ctrl/Cmd+Click открывает определение CREATE, не убирайте эти сочетания с действия Go to declaration, если вы последовали предыдущему совету.

  • Если вам нравилось, что при отключенной настройке Preview data editor over DDL editor двойной клик по таблице открывал DDL, это можно вернуть через ключ в реестре. Он называется database.legacy.navigate.to.code.from.tree. Но мы не советуем менять значения в реестре и надеемся, что те полпроцента людей, у которых эта галочка была снята, быстро привыкнут к новому поведению :)

Если мы про что-то забыли, пишите, пожалуйста, в комментах.

Вкладка Database

Тут мы ничего особо не сделали просто переименовали вкладку Tables в Database. Этим мы напоминаем, что по сочетанию клавиш Cmd+O/Ctrl+N можно искать не только таблицы, но и процедуры, функции, схемы.

Соединение

Поддержка Azure MFA

Мы поддержали интерактивную аутентификацию через Azure Active Directory. Если она включена, при соединении у вас автоматически откроется браузер, где вы сможете завершить аутентификацию.

Версия 2.x драйвера для Redshift

Этот драйвер можно скачать в DataGrip, начиная с версии 2021.1. Главное изменение состоит в том, что теперь запросы можно останавливать.

Полная поддержка Google Big Query

Подсветку языка мы поддержали в предыдущей версии, а в этой добавили интроспекцию. То есть, информацию об объектах IDE берет не из драйвера, а выкачивает ее сама при помощи специальных запросов.

Поддержка диалекта CockroachDB

Теперь DataGrip правильно подсвечивает запросы и показывает ошибки в скриптах для CockroachDB. Соответствующую интроспекцию мы сделаем в одном из следующих релизов.

Улучшения в окне соединения

Сделали это окно чуть более дружелюбным:

  • Источники данных и драйверы разделены на две вкладки.

  • На странице каждого драйвера появилась кнопка Create data source.

  • Кнопка Test Connection переехала вниз теперь ее видно из всех вкладок, а не только из General и SSH/SSL.

  • Для источников данных на основе файлов (так называемых DDL Data Sources) теперь можно явно задать диалект.

  • Поле JDBC URL расширяется, потому что адреса для подключения бывают очень длинными.

Проводник базы данных

Легкое копирование источников данных

Возможность копировать и вставлять источники данных мы сделали давно. Но с этого релиза вы можете использовать самые знаменитые сочетания клавиш в мире Ctrl/Cmd+C/V/X.

  • Напоминаем, что когда вы копируете источник данных, в буфер обмена сохраняется XML. Его можно послать коллеге в мессенджере, а он вставит его в свою IDE все сработает.

  • Если копирование и вставка происходит в одном проекте, вам не понадобится заново вводить пароль.

  • Источник данных можно не только копировать, но и вырезать. Вырезание отменяется при помощи Ctrl/Cmd+Z.

Новый интерфейс

Объекты второстепенной важности (роли, пространства имен, внешние источники и др.) мы поместили в папки Server Objects и Database Objects.

Если хотите чтобы было, как раньше, включите настройку Group Database and Schemas.

[Oracle] Скрытие сгенерированных объектов

Если отключить Show generated objects, то из проводника пропадут:

  • Таблицы материализованных представлений

  • Логи материализованных представлений

  • Вторичные таблицы

[SQLite] Новые типы объектов

В SQLite теперь отображаются функции, модули и виртуальные столбцы.

Улучшения для неподдерживаемых баз

Шаблоны для источников данных

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

Еще раз напомним, что поддержка для таких источников данных базовая. Скрипты подсвечиваются на основе стандарта SQL:2016, а информация об объектах берется из драйвера.

Написание запросов

Инспекция про избыточные имена в CTE

Если запрос не запустится из-за избыточных имен в общем табличном выражении, DataGrip сообщит об этом.

[SQL Server] Системные функции можно использовать без имени схемы

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

Поддержка JSON Lines

Формат JSON Lines используется для хранения данных и логов. И новая версия правильно подсвечивает файлы этого формата.

Толщина шрифта

Теперь вы можете настраивать толщину шрифта.

Импорт / Экспорт

Незагруженные данные

Если бинарные данные не были загружены полностью, вы увидите такое сообщение:

Настройка, которая определяет, какое количество данных DataGrip загружает по умолчанию, находится здесь: Settings/Preferences | Database | Data Views | Maximum number of bytes loaded per value.

Запрос в файле Excel

Когда вы экспортируете данные в Excel, на отдельном листе сохраняется изначальный запрос.

First row is header в контекстном меню импорта

Раньше было неудобно находить эту настройку, поэтому мы добавили ее в контекстное меню заголовков.

Интерфейс

Прикрепление папки при помощи drag-n-drop

Прикрепить папку, то есть открыть ее в панели Files, теперь можно, перетащив её.

Открытие вкладок в режиме разделенного редактора

Если редактор разделен вертикально на несколько вкладок, вы можете расширить окно редактора для любой из вкладок, кликнув по ней дважды. Чтобы вернуться в прежнее состояние, снова кликните дважды по окну.

Длинные названия вкладок

В одной из предыдущих версий мы укоротили названия вкладок. Это не всем понравилось, поэтому мы сделали настройку:

На этом все!

Скачать триал на месяц

Фидбек принимаем в комментариях к посту и здесь:

Трекер (это если точно нашли проблему)

Телеграм-канал

Твиттер

Почта

Команда DataGrip

Подробнее..

Postgresso 30

05.04.2021 18:06:45 | Автор: admin

Мы продолжаем знакомить вас с самыми интересными новостями PostgreSQL. Этот выпуск получился с некоторым уклоном в средства диагностики. Нет, не только. Например:

Хардверные ускорители: FPGA


В небольшом сообщении Энди Эликотта (Andy Ellicott) в блоге Swarm64 3 hardware acceleration options Postgres users should know in 2020 рассказывается о трёх аппаратных ускорителях, не GPU, а FRGA, и все они в облаках. У автора свой интерес: у Swarm64 есть собственное решение на FPGA-ускорителе. Значимым сигналом он считает объявление Amazon об FPGA-ускорителе кэша (FPGA-powered caching layer) в Redshift AQUA (Advanced Query Accelerator) в Amazon, который убыстряет запросы на порядок. А вообще уже почти все облака (во всяком случае Amazon, Alibaba, и Azure) используют сейчас FPGA-ускорители, просвещает нас Энди.

Итак:

Swarm64 Data Accelerator (DA)
это расширение, которое умеет переписывать обычные SQL-запросы, чтобы распараллеливать вычисления на всех этапах их исполнения, а сотни читающих или пишущих процессов будут работать параллельно на FPGA. Кроме того, там реализованы индексы columnstore, как в MS SQL Server. Есть техническое описание в PDF, но именно про FPGA в нём ничего нет. Зато есть демонстрационное видео, показывающее, как можно легко и быстро развернуть Postgres на инстансе Amazon EC2 F1 с FPGA. Ещё есть результаты тестов TPC-H (а позиционируется эта комбинация с FPGA прежде всего как ускоритель для гибридных транзакционно-аналитических нагрузок HTAP), и там показывает выигрыш в 50 раз по скорости.

Другой вариант, который предлагает Энди: Intel Arria 10 GX FPGA в связке с NVM-памятью Intel Optane DC, SSD и PostgreSQL 11 с тем же расширением Swarm64 DA. Всё это собрано в демо, которое вбрасывает в PostgreSQL потоки биржевых котировок со скоростью 200 тыс инсертов в секунду, и дальше работает с ними с обычным SQL.

Третий вариант с Samsung SmartSSD, в которой внутри FPGA-чип от Xilinx. Испытания (с тем же свормовским расширением, как можно догадаться) дали выигрыш в 40 раз на TPC-H и в 10-15 раз на JOIN-ах.

С маркетинговой точки зрения эти усилия нацелены прежде всего против хардверных решений для WH вроде Netezza или Teradata.

Обещано, что будет и сравнение эффективности FPGA vs. GPU (в т. ч. и в контексте проекта PGStrom).

(спасибо Александру Смолину за наводку в FB-группе PostgreSQL в России)




Конференции


были:

PGConf.online

Теперь выложены все видео и презентации доступ через расписание.

FOSDEM 21

Поток PostgreSQL devroom тёк два дня 6-7 февраля с 10 утра до 6 вечера. Материалов конференции очень много. Вот имеется однобокая, зато систематизированная выборка доклады от Postgres Professional (глаголы будущего времени там надо поменять в уме на глаголы прошедшего).

будет:

Highload++

Объявлено, что состоится офлайн 17 -18 мая 2021 в Крокус-Экспо, Москва. Есть Расписание. Я бы обратил особое внимание на потоки
СУБД и системы хранения, тестирование в Зале 3, например:
Микросервисы с нуля, Семен Катаев (Авито);
Прокрустово ложе или испанский сапог мифы и реальность СУБД в Облаках, Александр Зайцев (Altinity)
и на
Архитектуры, масштабируемость, безопасность в Зал 4 (главном), например:
Архитектура процессора Эльбрус 2000, Дмитрий Завалишин (Digital Zone);
SQL/JSON в PostgreSQL: настоящее и будущее, Олег Бартунов (Postgres Professional);
Распространённые ошибки изменения схемы базы данных PostgreSQL, Николай Самохвалов (Postgres.ai).

Вебинары и митапы


RuPostgres-вторник s02e13 Андрей Зубков (PostgresPro) pg_profile, pgpro_pwr

Вторник, посвященный pg_profile / PWR, так заинтересовал устроителей, что с большой вероятностью в ближайшее время можно ожидать продолжения: разобрались не во всех тонкостях работы этого весьма практичного инструмента, ну а расширения pgpro_stats, которое используется в PWR, коснулись по касательной.

После это был ещё вторник с Александром Кукушкиным (Zalando). Тема риски апгрейда мажорных версий с фокусом на PG12 и PG13, а пособник апгрейда Spilo: как выяснилось, бесшовный апгрейд в контексте Patroni задача слишком амбициозная, а вот Spilo, то есть Docker-образ с PostgreSQL и Patroni, с задачей справляется. Но опасностей и нюансов при апгрейде остаётся немало. Говорилось о сюрпризах от VACUUM, ANALYZE, о параллелизме по умолчанию, о CTE и материализации, о JIT.

Database Delivery: The Big Problem

Это была презентация от Ростелеком-ИТ, которую провёл Роман Гордеев (в видео глюки, надо прокрутить первые 11 минут). Его пригласили на один из стримов Tver.io сообщества тверских айтишников (но мне удобней было смотреть этот же ролик на на youtube). Речь шла об инкрементальной стратегии миграции. Роман рассказывал о вещах, применимых к разным СУБД и средам разработки, но для примера был выбран переход с базы PostgreSQL на H2 в графическом DataGrip. Соответственно в реальном времени наблюдались и решались проблемы с постгресовым типом text и с последовательностями.

В качестве механизма, который контролирует миграцию, был взят плагин liquibase для среды gradle. О настройках для такой работы можно почитать на страничке liquibase gradle на гитхабе Гордеева. Кстати, Ростелеком Информационные Технологии компания с населением под 2 тыс. человек. На официальной странице есть информация об опенсорсной СУБД in-memory Reindexer собственной разработки. Больше о базах там ничего пока найти не удалось.


Обучение


Выложены в общий доступ видео:
Оба эти курса появились в конце прошлого года, но теперь комплект полный: презентации и видео.

Тем, кто интересуется более пристально, советую прослушать доклад о курсах Егора Рогова на PGConf.online 2021.


Мониторинг


Monitoring PostgreSQL with Nagios and Checkmk

Пишет опять Хамид Ахтар (Hamid Akhtar, китайская компания High Go), на этот раз пишет о средствах мониторинга Nagios (рекурсивный акроним Nagios Ain't Gonna Insist On Sainthood Nagios не собирается настаивать на святости, в отличие от его предшественника NetSaint) и Checkmk. Публикация без претензий: как установить и настроить, не претендуя даже в этом на полноту.

Explaining Your Postgres Query Performance

Идём от простого к сложному. Пока URL подсказывает возможный подзаголовок статьи: Get Started with EXPLAIN ANALYZE. Кэт Бэтьюйгас (Kat Batuigas, Crunchy Data) действительно знакомит с самыми азами EXPLAIN, даже без опций. Жанр For dummies, и наглядно: показывает, как с помощью EXPLAIN ANALYZE можно наблюдать решения планировщика об (не)использовании индексов, и вообще что там происходит. Иллюстрируется это всё на базе Geonames.

Предыдущая её статья была о Query Optimization in Postgres with pg_stat_statements.

Вот ещё одна её статья: Three Easy Things To Remember About Postgres Indexes. В ней не только напоминания о том, что индекс занимает место на диске, но и, например, такие соображения:
Важен и тип запроса. Например, если в запросе есть знаки подстановки (wildcards)
wildcards, e.g. WHERE name LIKE 'Ma%',
то планировщик задействует по умолчанию индекс B-tree, но вам, возможно, стоит указать класс оператора, чтобы был выбран эффективный индекс.

Can auto_explain (with timing) Have Low Overhead?

Михаэль Христофидес (Michael Christofides) показывает работу расширения auto_explain с включённым и отключённым таймингом. Выводы:

Если задать ощутимый промежуток времени min_duration, издержки от auto_explain на небольшой транзакционной нагрузке )была меньше 1% с отключённым таймингом и ~2% с включённым. Семплинга не было, поэтому детали прослеживались для каждого запроса, но попадали в лог для медленных. А когда min_duration=0ms, и логировалось всё, издержки оказались больше 25%, даже без тайминга и ANALYZE. Видимо, издержки auto_explain связаны в основном с логированием.

Интерес у Михаэля не невинный он разработчик утилиты pgMustard, которая визуализирует планы. Она также расписывает, сколько тратится времени и сколько строк возвращает каждая операция (в т.ч. циклы; дочерние узлы планов subplans; CTE). Мало того, pgMustard умеет подсказывать. Например:
  • (не)эффективность индексов;
  • плохая оценка числа строк;
  • неэффективность кэша;
  • угроза распухания индекса (bloat);
  • CTE-скан использовался только 1 раз.


How to create a system information function in PostgreSQL

Давид Ян (David Zhang, старший системный архитектор в той же High Go) делится опытом написания собственных информационных функций. Ему мало тех, что можно найти на вот этой странице. Например, его не устраивает, что txid_current() возвращает ему тот же идентификатор транзакции, что и было до SAVEPOINT.

Ссылаясь на страничку Исходные данные системных каталогов, Давид показывает, как выбрать OID для новой функции, чтобы он не конфликтовал с существующими. Потом приводит код своей функции, определяющей xtid после SAVEPOINT. Называется она txid_current_snapshot и написана на C. И тестирует её. Теперь идентификатор транзакции показывается корректно.

How The PostgreSQL Optimizer Works

Ханс-Юрген Шёниг (Hans-Jrgen Schnig, Crunchy Data) написал не то, чтобы концептуальную, но большую по объёму статью, в которой есть примеры, демонстрирующие:

обработку констант: почему
WHERE x = 7 + 1
для оптимизатора не то же, что
WHERE x - 1 = 7

встраивание функций (function inlining): умение оптимизатора встраивать функции зависит от языка, в SQL он как дома, но не в PL-ях.

как обрабатываются функции, если они VOLATILE/STABLE/IMMUTABLE. Например:
WHERE x = clock_timestamp()
против
WHERE x = now()

что способен понять PostgreSQL, задумавшись о том, что чему равно:
понять, что если x = y AND y = 4, то x = 4, а значит можно использовать индекс по x это он может.

что такое view inlining и subselect flattening:
как представление превращается во вложенные SELECT-ы, а они в обычный, плоский SELECT.

Ну и, конечно, центральный вопрос как оптимизатор расправляется с JOIN. Тут Ханс-Юрген рассказывает об очерёдности джойнов, о явных и неявных; об OUTER JOIN; автоматическом исключении (pruning) ненужных; об EXIST и анти-джойнах.



Случайности:


Они не случайны

Кирилл Боровиков ака kilor выступил в роли волшебника: он угадывает случайные числа! Он придумал волшебную функцию и даже назвал её magic(). В качестве аргумента она берёт только что сгенерённое функцией random() число и предсказывает следующее:
SELECT r random, magic(r) random_next FROM random() r;       random       |    random_next--------------------+-------------------- 0.3921143477755571 | 0.6377947747296489tst=# SELECT r random, magic(r) random_next FROM random() r;       random       |    random_next--------------------+-------------------- 0.6377947747296489 | 0.5727554063674667

Чтобы исследовать содержание внутренностей волшебной функции, автор предлагает разобраться в линейном конгруэнтном алгоритме, который используется в random(), залезает в код функции setseed() в файле float.c и там находит источник вдохновения для создания своей волшебной функции.

Итого, случайные числа random() не слишком случайны, о криптографии и речи не может быть. Но кое-какие альтернативы имеются: более безопасны функции в расширении pgcrypto.


Восстановление


Speeding up recovery & VACUUM in Postgres 14

Статья на сайте Citus, но речь не о Citus, а о патче в основную ветку PostgreSQL. Написана статья (и патч) Дэвидом Роули (David Rowley), работавшим над этим уже внутри Microsoft. Он переписал внутреннюю функцию compactify_tuples, которая используется, когда PostgreSQL стартует после внештатного (нечистого) шатдауна (crash recovery), и когда идёт восстановление standby-сервера проигрыванием WAL по их прибытии с primary-сервера; VACUUM.

Эти случаи Дэвид и расписывает, поясняя схемами. Новая версия функции избавляет от ненужной внутренней сортировки кортежей в heap, поэтому и работает быстрее. На pgbench выигрыш в 2.4 раза на восстановлении и на 25% при вакууме.


Соревнования


Performance differences between Postgres and MySQL

В сообществе Arctype очень интересуются сравнительной производительностью PostgreSQL и MySQL. Эта сумбурная статья с приятными выводами продолжение вот этой, где преимущества той и другой СУБД оценивали качественно, и пришли в том числе к выводам о преимуществах PostgreSQL. Он лучше когда:
  • надо работать со сложно устроенными или объёмистыми данными;
  • аналитические нагрузки;
  • нужна транзакционная база общего назначения;
  • требуется работа с геоданными.


А на этот раз решили померить, причём с уклоном в JSON, поскольку эта тема интересует в сообществе очень многих и очень сильно. Вот что было сделано:
создан проект, в котором использовались PostgreSQL и MySQL;
создали объект JSON для тестирования чтения и записи, размер объекта около14 МБ, около 200210 записей в базе данных.

И опять приятный вывод:
JSON-запросы быстрей в Postgres!

Кроме этого автор по касательной упоминает индексы по выражениям и прочие, особенности репликации, принципиальные отличия MVCC в InnoDB MySQL и в PostgreSQL.


PostGIS


Traveling Salesman Problem With PostGIS And pgRouting

У Флориана Надлера (Florian Nadler, Cybertec) проблемный коммивояжер странствует по окрестностям Гамбурга. Это продолжение статьи 'Catchment Areas' With PostgreSQL And PostGIS. Там собрали множества городов, ближайших к крупным аэропортам, разбросав их по диаграммам Вороного.

Теперь, надо решить, как лучше эти города обойти, для чего кроме PostGIS Флориан использует функции расширения pgRouting. Чтобы превратить множество точек в граф, он выбирает утилиту osm2po.

Дальше pgr_createverticestable функция из pgRouting превратит граф в таблицу. Эта таблица-граф накладывается как слой поверх слоёв OpenStreetMap. После этого Флориан, используя функцию pgr_dijkstraCostMatrix из pgRouting, решает эту знаменитую задачу оптимизации с помощью замысловатого запроса с CTE, учитывая стоимости/веса, присвоенные ещё osm2po.

Performance Improvements in GEOS

GEOS важнейшая для геовычислений библиотека (алгоритмы портированы на C из Java Topology Suite или JTS). Crunchy Data вкладывают в её развитие не меньше сил, чем в саму PostGIS.

Пол Рамси ( Paul Ramsey) рассказывает не просто о тестах производительности GEOS (довольно специфических), а взглядом историка GEOS иллюстрирует ими хронологию улучшений в этой библиотеке от релиза 3.6 к свежайшему 3.9. Вообще-то, о GEOS 3.9 Пол говорил и раньше в начале декабря в блоге Crunchy Data Waiting for PostGIS 3.1: GEOS 3.9 и в собственном. Там тоже есть роскошные иллюстрации, но нет графиков производительности.

А вот заметку Пола Рамси Dumping a ByteA with psql можно увидеть только в его блоге. Она короткая, но может оказаться полезной тем, кто:
  • хранит двоичные файлы в столбцах базы, например изображения-ярлычки (thumbnails);
  • хочет сформировать на выходе двоичный файл изображения, песни, protobuf или LIDAR-файл;
  • использует двоичный формат для транзита двух разных типов.

Хранить в двоичном виде картинку можно, а вот посмотреть нельзя нужен файл. Вот скриптик, который берёт из базы ярлычок в типе bytea, через psql двоичное значение обертывается функцией encode и выгружается как обычное текстовое. Вывод psql перенаправляется в утилиту xxd, которая декодирует входной поток (ключ -r) обратно в двоичный вид и записывает в файл .png:
echo "SELECT encode(thumbnail, 'hex') FROM persons WHERE id = 12345" \  | psql --quiet --tuples-only -d dbname \  | xxd -r -p \  > thumbnail.png

Такой способ будет работать для любого поля bytea.


Активная жизнь в коммьюнити


How many engineers does it take to make subscripting work?

Дмитрий Долгов (Zalando) пишет в своём личном блоге в смешанном жанре: о довольно сложных и специальных технических проблемах самого патча, но и извлекает из истории их решения этакую мораль для писателей патчей. Таких, чтобы сообщество PostgreSQL было в состоянии принять их за время меньше бесконечности.

Патч добавляет subscripting в синтаксис функций JSONB то есть как у массивов, например:
SET jsonb_column['key'] = '"value"';
вместо
SET jsonb_column = jsonb_set(jsonb_column, '{"key"}', '"value"');

Началась история этого патча в 2015 году с беседы Дмитрия с Олегом Бартуновым и последовавшего простенького патча Долгова. Сообщество отнеслось к патчу сочувственно, но предложило переписать его в более универсальной манере, чтобы подобную функциональность можно было бы использовать и для других типов данных. Соответствующий патч Дмитрия был непрост, и ревюеры не торопились его разобрать и оценить. Ещё в истории этого патча фигурируют Том Лейн (Tom Lane), закоммитивший финальный патч Александр Коротков, Павел Стехуле (Pavel Stehule) и Никита Глухов.

Затрагиваются темы универсальности и, пожалуй, лучшее враг хорошего. Патч продвигался настолько вяло, что Дмитрий в отчаянии даже написал в комментарии к патчу: если вы дочитали до этой фразы и напишете рьвю, я выставлю бутылку пива на следующей PGConfEU (о судьбе бутылки из статьи мы не узнали).

В финале статьи 8 советов. Вот некоторые из них в моём вольном переводе, начиная с последнего Last but not least:

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

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

Разбейте патч на несколько частей это всегда облегчает работу ревьюеров.

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


Облака и контейнеры


Running Postgres In Docker Why And How?

Каарел Моппел (Kaarel Moppel, Cybertec) задаёт себе вопрос можно и нужно ли использовать PostgreSQL в Docker в качестве продакшн, будет ли он вообще там работать? и отвечает: да, работать будет, если сильно постараться, и если для фана или для тестирования.

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

Докер-имиджи да и вся концепция контейнеров оптимизированы под моментальное разворачивание в стиле стартапов . По умолчанию там даже данные не разведены как следует по томам (persistent units). Если этого не сделать, затея может закончится катастрофой.

От использования контейнеров не ждите автоматических или каких-то волшебных средств высокой доступности.

У вас будет относительно лёгкая жизнь только в том случае, если вы используете такой всеобъемлющий фреймворк, как Kubernetes плюс выбираете оператор (скорее всего от Zalando или Crunchy).



Поведение


The PostgreSQL Community Code of Conduct Committee Annual Report for 2020

Этот документ сообщества переводили на русский Анастасия Лубенникова, Александр Лахин и Анастасия Распопина (все из Postgres Professional), также участвовали Виктор Егоров и Валерия Каплан. Ещё он переведён с английского на японский и иврит.

Число жалоб увеличилось в 2020: 18 против 12 в прошлом году. Мужчины жалуются чаще: 15/3. Обычно от страны по жалобе. По 2 только от РФ, Аргентины, UK и US.
Подробнее..

Postgresso 31

11.05.2021 16:15:42 | Автор: admin
Надеемся, что вы хорошо отдохнули и попраздновали. А мы предлагаем вам очередную сводку Postgres-новостей.

PostgreSQL 14 Beta 1


Релизная группа в составе Пит Гейган (Pete Geoghegan, Crunchy Data), Мишель Пакье (Michael Paquier, VMWare) и Эндрю Данстан (Andrew Dunstan, EDB) предлагают опубликовать бету 20-го мая, как это и происходило с предыдущими бетами.



Commitfest afterparty


PostgreSQL 14: Часть 5 или весенние заморозки (Коммитфест 2021-03)

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

Вот авторский тизер:
  • Может ли один запрос параллельно выполняться на разных серверах?
  • Как найти запрос из pg_stat_activity в pg_stat_statements?
  • Можно ли добавлять и удалять секции секционированной таблицы не останавливая приложение?
  • Как пустить разработчиков на прод чтобы они могли всё видеть, но ничего не могли изменить?
  • Почему VACUUM после COPY FREEZE заново переписывает всю таблицу и что с этим делать?
  • Можно ли сжимать TOAST чем-то кроме медленного zlib?
  • Как понять сколько времени длится блокировка найденная в pg_locks?
  • Для чего нужны CYCLE и SEARCH рекурсивному запросу?
  • Текст функций на каких языках (кроме C) не интерпретируется при вызове?


Миграция


CHAR(1) to Boolean transformation while migrating to PostgreSQL

В Oracle нет типа boolean, а в PostgreSQL есть. Но почему бы не использовать этот тип, если в исходной оракловой базе есть столбец boolean, который хранится там в виде CHAR(1) с ограничением CHECK? Можно. Но хотелось бы ещё получить гарантию, что значения, отличные от резрешенных для Postgres не остановят работу приложения, а будут должным образом обработаны. Для этого можно создать CAST:
CREATE CAST (char as bool) WITH FUNCTION char_to_bool(char);
Далее автор Дилип Кумар (Dileep Kumar, MigOps) показывает изменение поведения при определении CAST как IMPLICIT, а потом прогоняет запрос (обычный SELECT) на тестах, чтобы увидеть разницу CHAR(1) vs Explicit Casting vs Implicit Casting vs Boolean. Побеждает, как и ожидалось, Boolean.

Choice of Table Column Types and Order When Migrating to PostgreSQL

В статье Стивена Фроста (Stephen Frost) с участием его коллеги по Crunchy Data Дэвида Юатта (David Youatt) тоже говорится о том, какой тип выбрать в PostgreSQL при миграции, но ещё и о том, в каком порядке располагать столбцы, чтобы данные выбранных типов хранились максимально эффективно. Сначала самые широкие поля с фиксированной шириной, затем менее широкие с фиксированной и только потом поля переменной ширины иначе появятся дыры в данных. Стивен рассказывает и про неприятные сюрпризы с выравниванием, которые можно получить, излишне рьяно экспериментируя с типами PostgreSQL. Ещё совет: выбирайте NUMERIC или DECIMAL только тогда, когда необходимо (считая деньги, например), а если нет, то обходитесь NTEGER, BIGINT, REAL, DOUBLE PRECISION это проще и эффективней.


Масштабирование


Lessons Learned From 5 Years of Scaling PostgreSQL

Джо Уилм (Joe Wilm) обобщает опыт использования PostgreSQL в компании OneSignal. Система доросла за 5 лет до 75 ТБ на 40 серверах. Понятно, что не все технические решения были приняты сразу на вырост. Как решают проблемы масштабирования, и как их можно было избежать об этом и рассказывает автор. Для удобства он разбил статью по разделам (сознательно не перевожу, слишком много английских слов пришлось бы писать кириллицей):
Bloat таблиц и индексов. Коротко о (хорошо известных) причинах распухания. pg_repack справлялся так себе (см. причины), написали собственный демон, координирующий его работу. Перешли к pgcompacttable там, где pg_repack обваливает производительность (перешли не везде, pgcompacttable работает надёжней, но медленней). Есть и об уловках по ситуации: в системе были таблицы, в которых большие поля (около 1 КБ) в личных данных, и поле last_seen_time int, которое часто обновлялось. Их разнесли по разным таблицам: одним JOIN больше, зато не копятся килобайты при обновлении строки.
Database upgrade. Мажорные и минорные. С мажорными справлялись при помощи логической репликации pglogical. При минорых просто перестартовывали postgres.
Wraparound. Серьёзная проблема для таких нагрузок. Остановились на оповещениях при приближении к 250 млн оставшихся XID. Напомним, конечно, что в Postgres Pro Enterprise 64-битные XID.
Replica Promotion. Для этого обходятся средствами haproxy. Упоминается только Patroni, но и то в контексте мы не используем, но может и стоило. Для каждой логической базы данных есть два бэкенда: один read-write, другой read-only. Переключение занимает пару секунд.
Partitioning и Sharding. Важнейшая штука для такой базы, конечно. Сначала порезали на 16 секций, потом на 256, а в ближайших планах 4096. Резали на куски выбирая в качестве критерия разбиения id пользователей системы. Сейчас думают над созданием data proxy слое, который будет знать, как разрезаны данные и где лежат, и действовать соответственно. Чтобы приложениям этого не требовалось знать для нормальной работы. Сетуют, что не сделали так с самого начала.


Самокритика


Чего энтерпрайзу в PostgreSQL не хватает

Вот чего ему не хватает в порядке важности (по Кириллу Боровикову, автору статьи)
  • легковесного менеджера соединений (он же built-in connection pooler);
  • 64-bit XID;
  • микротаблиц (речь о том, что у каждой таблицы и индекса в PostgreSQL есть 3 форка файла, но почему бы не обойтись 1 файлом (heap) для мелких справочных табличек?);
  • zheap;
  • append-only storage (а в идеале, считает Кирилл хотелось иметь возможность назначать часть полей индексов или целых таблиц как no-MVCC чтобы иногда экономить на полях поддержки MVCC);
  • отложенная индексация (чтобы сервер мог размазать необходимые операции во времени для балансировки нагрузки эта тема особенно важна для конкуренции с поисковыми системами, где основная задача найти вообще, а не найти прямо сразу сейчас);
  • columnar storage (в идеале в ядре или в contrib);
  • in-memory storage (очень быстрого нетранзакционного хранилища без сброса на диск);
  • не пухнущих TEMPORARY TABLE, в том числе на репликах;
  • multimaster из коробки;
  • SQL-defined index (уметь описывать новые виды индексов прямо на SQL/PLpgSQL);
  • мониторинга производительности запросов (здесь Кирилл предлагает глянуть, как это визуализируется на родном explain.tensor.ru);
  • снапшотов статистики таблиц (как в pg_profile [а тем более в pgpro_pwr примечание редакции]).

К ЭТОМУ ДОБАВИЛИСЬ ХОТЕЛКИ ИЗ КОММЕНТАРИЕВ:

  • IS NOT DISTINCT FROM при индексации;
  • failover из коробки (аналогично Always on у MS SQL) без Patroni и сопутствующих;
  • Asynchronous IO и Direct IO;
  • бесшовного обновления мажорной версии;
  • flashback queries;
  • edition-based redefinition;
  • нормальной компрессии.

Некоторые из этих хотелок на пути к дальнейшим версиям, некоторые уже есть в Postgres Pro Enterprise (о чём не умалчивает и автор).


Видео-вторник s02e15: Десять проблем PostgreSQL. Мониторинг запросов, pg_profile

(это продолжение вторника ) с Андреем Зубковым)

Статья Рика Брэнсона: (Rick Branson) 10 things I Hate In Postgres внезапно попала в топ обсуждаемых. Вот её не миновали и устроители ruPostgres.Вторников Николай Самохвалов и Илья Космодемьянский.

О ней мы писали в Postgreso 20. На ruPostgres.вторнике s02e15 6-го апреля самые жаркие вопросы возникали, как всегда, вокруг MVCC и VACUUM, переполнения 32-битных счётчиков XID.

На 50-й минуте обсуждения 10 ненавистных вещей Андрей Зубков продолжил рассказал о pg_profile (до pgpro_pwr речь опять не дошла, говорили даже о том, чтобы наверстать в 3-й серии) и о своём патче pg_stat_statements: Track statement entry timestamp (ровно 1:00 записи).

Вторник 20-го апреля назывался Как поменять тип колонки в таблице PostgreSQL с 1 млрд строк без даунтайма?. Два разных варианта решения на уровне колонки и на уровне таблицы.

А совсем недавний 4-го мая о разном, например, о WAL-G vs. pgBackRest, об амазоновских инстансах на ARM, о которых чуть ниже. Список тем лежит в файле.


Облака и контейнеры


Dramatical Effect of LSE Instructions for PostgreSQL on Graviton2 Instances

Александр Коротков в своём блоге пишет об опыте работы с новейшими облаками инстансы Graviton2 работают на амазоновских ARM-процессорах. Но следующие за модой расплачиваются некоторыми сложностями у ARM есть специфика (по мнению Александра работа с ними скорее напоминает работу с IBM Power).

Команды LSE (Large System Extensions), доступные с версии 8.1, действительно ускоряют работу. Вот здесь это разъясняют с некоторыми подробностями, испытывая MySQL на включенных и отключенных LSE. Александр же получил колоссальный выигрыш на pgbench, скомпилировав PostgreSQL 14 с поддержкой LSE. Но это касается только амазоновских ARM AWR Graviton2. Apple M1 не удалось оптимизировать (возможно, в этих процессорах есть какая-то внутренняя оптимизация), а на китайских Kunpeng 920 результаты даже ухудшились.


Что делать


Managing Transaction ID Exhaustion (Wraparound) in PostgreSQL

Кит Фиске (Keith Fiske, Crunchy Data) регулярно пишет в своём собственном блоге Keith's Ramblings о вакууме, распухших индексах и других важнейших для вдумчивого постгресиста вещах.

В этой статье есть конкретные SQL-запросы, использующие autovacuum_freeze_max_age для получения внятной информации о происходящем с конкретными таблицами, так как vacuumdb --all --freeze --jobs=2 --echo --analyze всего кластера баз данных во многих случаях слишком радикальная мера. Если недовакуумированных таблиц очень много, то Кит советует вакуумировать в батчах не больше сотни в каждом. Сам он предпочитает держать max XID < to 50% autovacuum_freeze_max_age, лучше 30-40%.

Он написал статью и о настройке автовакуума: Per-Table Autovacuum Tuning. Но даже аккуратно настроив автовакуум, стоит с не меньшей аккуратностью мониторить ситуацию. Риск не велик, но ставка высока, как говорили наши деды.

Не удержусь от перечисления собственных проектов Кита (или с его существенным участием):
pg_partman расширение с автоматической поддержкой секционирования по времени и serial id;
pg_extractor продвинутый фильтр дампа;
pg_bloat_check скрипт для мониторинга таблиц и индексов;
mimeo расширение PostgreSQL для потабличной логической репликации;
pg_jobmon расширение для логирования и мониторинга автономных функций.

Postgres is Out of Disk and How to Recover: The Dos and Don'ts

Статья Элизабет Кристинсен (Elizabeth Christensen) с участием Дэвида Кристинсена (David Christensen), Джонатана Каца (Jonathan Katz) и Стивена Фроста (Stephen Frost) все из Crunchy Data. Почему забился диск, что НЕ делать, и что делать.
Возможные причины:
  • отказала archive_command и WAL начал заполнять диск;
  • остались слоты репликации у стендбая, а реплика стала недоступна: опять же WAL заполняет диск;
  • изменения в базе настолько большие, что генерящийся WAL съедает всё доступное дисковое пространство;
  • просто-напросто данных было слишком много, а средства мониторинга и предупреждения не сработали.

Что НЕЛЬЗЯ делать:
удалять WAL-файлы нельзя категорически;
  • не дайте переписать существующие данные, восстанавливаясь из бэкапа;
  • Никакого resize.


Что надо делать:
  • сделайте сразу бэкап на уровне файловой системы;
  • создайте новый инстанс (или хотя бы новый том) с достаточным местом, убедитесь, что Postgres остановлен и сделайте бэкап директории данных PostgreSQL (обязательно директории pg_wal и недефолтные табличные пространства), чтобы вам было куда вернуться, если понадобится;
  • когда база данных заработала, просмотрите логи, разберитесь, из-за чего возникли проблемы и почините поломки, если это возможно.

В статье рассказывается, как архивируется WAL, об попорченных архивах, кое-что о pgBackRest, а ещё предлагается почитать How to Recover When PostgreSQL is Missing a WAL File.

Кстати, о WAL. Если нужно порекомендовать хорошую статью англоязычным коллегам, то в блоге Postgre Pofessional опубликован перевод 3-й части серии Егора Рогова о WAL: WAL in PostgreSQL: 3. Checkpoint. Оригинал её здесь, en-начало-серии здесь, а ru-начало здесь.


Из блога БРЮСА МОМДЖАНА


(то есть отсюда)

Jsonb Multi-Column Type Casting

Брюс делится радостью, что есть jsonb_to_record() и можно без всяких двойных двоеточий сразу сказать:
SELECT a, b, pg_typeof(a) AS a_type, pg_typeof(b) AS b_typeFROM test, jsonb_to_record(test.x) AS x (a TEXT, b INTEGER);

(А ведь добавим от себя есть ещё и jsonb_to_recordset(jsonb)).

Брюс обращает внимание на устройство таких запросов. Если сказать
SELECT x.a, b, pg_typeof(a) AS a_type, pg_typeof(b) AS b_typeFROM test, jsonb_to_record(test.x) AS x (a TEXT, b INTEGER)WHERE b <= 4;

то это будет работать, ведь b уже integer потому, что запрос уже создал табличку x с областью видимости только внутри запроса, где типы уже преобразованы. Немногословный (как обычно в своём блоге) Брюс предлагает ознакомиться с деталями в тредах json_to_record Example и Abnormal JSON query performance.

Oracle vs. PostgreSQL

Брюс решил оценить функциональную полноту обеих СУБД в %, в ответ на чьё-то сравнение Postgres и Oracle это как резиновая уточка против танкера водоизмещением 300 тыс. тонн. Он считает:
Более реалистичной была бы оценка в 80-90%, в зависимости от того, какая функциональность для вас важней. Но можно бы поговорить и том, что в Postgres есть, а в Oracle нет. С точки зрения админа получится, может быть, и меньше 80%, а вот с точки зрения разработчика в Oracle нет многого, и оценка перевалит за 100%.

Challenging Assumptions

Следующие, некогда справедливые допущения теперь сомнительны:
  • платный софт всегда лучше бесплатного;
  • открытый код не столь безопасен, так как слабые места видны;
  • серьёзные люди софт с открытым кодом не разрабатывают;
  • Oracle лучшая СУБД;
  • со знанием Oracle без работы я не останусь;

Кто закрывает дыры и латает щели (в оригинале Database Software Bundles)

Проект Postgres дал миру великолепную, полнофункциональную СУБД. Но когда пользователь думает о бэкапе, мониторинге, высокой доступности, ему приходится смотреть на сторону, так как возможности Postgres могут не совпадать с его потребностями. Иногда бреши закрывают проекты с открытым кодом, но в других случаях решают проблемы коммерческие Postgres-компании: Cybertec, edb, HighGo, Ongres, Postgres Pro, sra oss и другие, которые поставляют сервисы последней мили для корпоративных решений.

Также можно заглянуть в

Shared Memory Sizing
или, скажем, в
Replica Scaling by the Numbers


ИИ


Regression Analysis in PostgreSQL with Tensorflow

Дейв Пейдж (Dave Page, вице-президент и главный архитектор EDB) продолжает серию, посвященную ИИ и статистическим методам анализа данных. Из последнего: вышли две статьи посвященные регрессионному анализу, который ускоряют с помощью Tensorflow. В приведенных примерах можно увидеть много ласкающих слух питониста слов: pandas, numpy, matplotlib и seaborn. Подчеркнём, что используется расширение PostgreSQL plpython3u, а не просто внешние по отношению к базе библиотеки.

Во второй части дело доходит до пред-обработки данных. Используется популярный у педагогов машинного обучения набор данных Boston Housing Dataset по ним тренируются угадывать цену дома в Бостоне в зависимости от некоторых факторов. Из набора выкидывают значения, сильно отличающиеся от общей массы, чтобы не запутать нейронную сеть при обучении. Ещё смотрят распределения и строят корреляции. Третья статья ещё не вышла. Обещано, что в ней уже воспользуются достижениями 2-й части, чтобы обучать нейронную сеть регрессионному анализу.


Релизы


Kubegres

Обычно в разговоре о PostgreSQL в Kubernetes на третьей фразе появляются операторы от Crunchy Data и Zalando. Kubegres, возможно, вклинится в разговор. Разработчик Алекс Арика (Alex Arica, Reactive Tech Limited). Создавался Kubegres на базе фреймворка Kubebuilder version 3 (SDK для разработки Kubernetes APIs с использованием CRD. Можно забрать отсюда.

KuiBaDB

KuiBaDB это Postgres для OLAP, переписанный с Rust и многопоточностью. У этой СУБД есть только базовая функциональность. Она, например, поддерживает транзакции, но не вложенные транзакции. KuiBaDB создан для разработчиков, чтобы они могли быстренько проверить на ней свои идеи. В ней есть векторный движок и колоночное хранение, она опирается на каталоги (catalog-driven).

pgBackRest 2.33

Появилась поддержка нескольких репозиториев данные и WAL можно копировать сразу в несколько хранилищ.
pgBackRest поддерживает теперь GCS Google Cloud Storage.
Отныне можно задать путь вручную с ./configure --with-configdir. Стало удобней работать с не-Linux ОС, например с FreeBSD.
Появилось логирование в процессе бэкапа.

pg_probackup 2.4.15

В новой версии pg_probackup при бэкапе в инкрементальном режиме автоматически обнаруживается переключение таймлайнов, за счёт использования команды TIMELINE_HISTORY протокола репликации (предложил Алексей Игнатов).

При операциях merge и retention merge теперь тоже можно использовать флаги --no-validate и --no-sync.

pgmetrics 1.11.0

pgmetrics утилита с открытым кодом для сбора статистики работающего PostgreSQL, распространяемая в виде единого бинарного файла без внешних зависимостей. Разработчик RapidLoop, у которой есть ещё и pgDash, для которой pgmetrics собирает статистику.

Новое в версии:
  • собирает и парсит логи из AWS RDS и Aurora, используя CloudWatch;
  • поддержка пулера Odyssey v1.1;
  • улучшена поддержка Postgres 13;
  • улучшена поддержка метрик AWS RDS;
  • появились бинарники для ARMv8

Скачать можно отсюда.

HypoPG 1.2

HypoPG одно из произведений Жульена Руо (Julien Rouhaud). Это расширение для работы с гипотетическими индексами. Новое в версии: работая на стендбае, hypopg использует фальшивый (fake) генератор oid, который одалживает их внутри интервала FirstBootstrapObjectId / FirstNormalObjectId, а не генерит реальные. Если потребуется, можно работать по-старому, используя опцию hypopg.use_real_oids. Есть и ещё изменения, hypopg_list_indexes(), подробности в документации.

pgstats.dev

Это динамическая диаграмма Postgres Observability упрощенное представление устройства PostgreSQL и доступные системные представления и функции для получения статистики о работе подсистем Postgres. Этому необычному произведению Алексея Лесовского (Data Egret) всего 5 месяцев, но её знают многие DBA, спорят и интересуются: что новенького? Новое, например, вот:
  • стрелки, которые раньше показывали связи между блоками и метками статистики, теперь исчезли, а соответствующие цвета введены, чтобы показать их отношения;
  • на страницах описания статистик (см. pg_stat_progress_create_index в качестве примера) улучшена внутренняя навигация за счет добавления ссылок на связанные элементы;
  • добавлены ресурсы внешние ссылки с дополнительной информацией;
  • теперь есть управление версиями, чтобы вы могли видеть, как Postgres эволюционировал от одной версии к другой.


AGE 0.4.0

Расширение, добавляющее графовую функциональность. Новшества в 0.4.0 здесь.

pg_log_statements 0.0.2

pg_log_statements расширение PostgreSQL, которое позволяет логировать SQL-запросы так, что переменная log_statement может быть установлена для отдельного серверного процесса (по id или фильтру), а не на уровне базы или инстанса.

Можно зайти на PGXN или на гитхабе создателя Пьера Форстмана, специалиста по Oracle.


Конференции


PostgresLondon 2021

Состоится уже 12-го мая, виртуальная. Расписание.

Highload++

Состоится офлайн 17 -18 мая в Крокус-Экспо, Москва. Расписание.

Postgres Vision 2020

Postgres Vision виртуальная конференция EDB, но участие свободное. Состоится 22-23 июня. Регистрация.

Следующий номер Postgresso 32 выйдет в первых числах июня.
Подробнее..

Отказоустойчивый кластер PostgreSQL с помощью crm

08.06.2021 14:19:38 | Автор: admin
Автор Игорь Косенков, инженер postgres Professional

Привет всем! Сегодня речь пойдет о кластере. Да, снова об отказоустойчивом кластере на базе Corosync/Pacemaker. Только настраивать мы его будем не как обычно с помощью утилиты pcs, а с помощью мало используемой утилиты crm.

С точки зрения использования этих утилит (pcs и crm) весь мир Unix-like операционок делится на два вида:
  • содержит пакеты утилиты pcs (RHEL, CentOS, Debian, Ubuntu);
  • содержит пакеты утилиты crm (SLES, Opensuse, Elbrus, Leningrad и т.д.).

crm cluster resource manager специальная утилита, которая используется для создания и управления отказоустойчивым кластером. Она включена в пакет crmsh, который обычно не входит в состав самых распространенных дистрибутивов Linux.

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

В то же время, если спросить у поисковика про утилиту настройки кластера pcs, которая является по функционалу такой же утилитой, как и crm, то информации будет много. Есть даже несколько статей на Хабре (в том числе и моя статья Кластер pacemaker/corosync без валидола).

Утилита crm такая же мощная и гибкая, как и pcs, но незаслуженно обделена вниманием.

Решено было исправить этот пробел и написать статью.

Причины, по которым те или иные разработчики дистрибутивов предпочитают кто crm, а кто pcs, мне неизвестны. Могу предположить, что все дело в зависимостях. Например, если сравнить количество зависимостей у pcs и crm, то получается такая картина:
$ sudo rpm -qpR crmsh-3.0.1-1.el7.centos.noarch.rpm | wc -l19$ sudo rpm -qpR pcs-0.9.169-3.el7.centos.x86_64.rpm | wc -l50

Сторонники минимализма, скорей всего, предпочтут crmsh. А если еще учесть, что pcs тянет за собой ruby, openssl, pam и python, а crmsh только python, то выбор в некоторых случаях будет однозначно на стороне crm. В каких случаях? Ну, например, при сертификации ОС есть некоторые трудности с пакетом ruby. Также известны случаи, когда в банковских структурах служба безопасности не разрешает установку нерегламентированного ПО.

Сходства и различия


У утилиты crm есть как сходства, так и различия с известной всем утилитой pcs.
Сходства утилит приведены в таблице 1:



На этом сходства заканчиваются и дальше идут различия, которых много, поэтому привожу лишь некоторые из них (таблица 2):



Различия начинаются с самого начала с момента инициализации кластера. Инициализировать кластер с помощью crm можно одной командой, а у pcs это происходит в два этапа авторизация и инициализация.

Удалить кластер (разобрать) у pcs можно одной командой сразу, а у crm необходимо удалять по одному узлу до тех пор, пока их не останется в кластере.

Чтобы изменить параметры ресурса, который мы уже создали в кластере, у pcs есть опция update. У crm такой опции нет, но есть команда configure edit, которая позволяет менять любые настройки кластера налету и мгновенно. Даже больше мы можем за один прием отредактировать любое количество параметров и ресурсов, и в конце редактирования применить все изменения сразу. Удобно? Думаю, да.

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

У crm в стандартной поставке нет веб-инструмента, но зато он есть в коммерческой версии SUSE HAWK.

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


Лучший способ узнать и познакомиться с crm это настроить отказоустойчивый кластер.

Чем мы сейчас и займемся. Для примера возьмем ОС CentOS 7.9.

Для создания отказоустойчивого кластера PostgreSQL нам понадобится стенд, состоящий из 3-х узлов node1, node2, node3. На каждом узле установлена ОС CentOS 7.9 и пакеты corosync, pacemaker, fence-agents* (агенты фенсинга).

В качестве СУБД будем использовать Postgres Pro Standard v.11, но вы можете с таким же успехом использовать ванильную версию PostgreSQL. В нашей системе установлены необходимые пакеты postgrespro-std-11-server, postgrespro-std-11-libs, postgrespro-std-11-contrib, postgrespro-std-11-client.

Настройки СУБД (postgresql.conf) и доступа к ней (pg_hba.conf) не рассматриваются в данной статье, информации об этом достаточно в интернете. На одном из узлов (например, node1) необходимо инициализировать базу данных с помощью initdb, а на двух других узлах с помощью pg_basebackup скопировать базу данных с node1.

Кроме того, в будущем кластере должны быть настроены синхронизация времени на узлах и разрешение имен узлов в кластере с помощью файла /etc/hosts.

ПРИМЕЧАНИЕ:
В этом разделе все команды необходимо выполнить на всех узлах кластера.
Поскольку пакет crmsh не входит в состав дистрибутива ОС, то необходимо подключить репозиторий
Extra OKay Packages for Enterprise Linux с этим пакетом.
node1,2,3$ sudo rpm -ivh http://repo.okay.com.mx/centos/7/x86_64/release/okay-release-1-5.el7.noarch.rpm

Нам также понадобится репозитарий EPEL:
node1,2,3$ sudo yum install epel-releasenode1,2,3$ sudo yum update

Устанавливаем пакет crmsh:
node1,2,3$ sudo yum install crmsh

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

Разработчики crmsh вставили код вызова этой кластерной утилиты для удобства настройки. Можно, конечно, обойтись и без csync2, но в таком случае будет больше ручной работы с конфигурационными файлами на каждом узле.

ОТСТУПЛЕНИЕ:
Сервис csync2 может использоваться не только для создания отказоустойчивого кластера Corosync/Pacemaker. Например, если есть несколько серверов, у которых меняются конфигурационные файлы и эти файлы периодически нужно синхронизировать по критерию самый свежий файл.


Итак, устанавливаем csync2 и простейшую базу данных для хранения мета-данных (sqlite).
$ sudo yum install csync2 libsqlite3x-devel

Тут нас поджидает подводный камень.

Поскольку csync2 и crmsh не являются родными для CentOS, то без дополнительных танцев сразу после установки они не заработают. Вызов crm влечет вызов утилиты csync2, которой в свою очередь не хватает парочки systemd-юнитов. Почему этих файлов нет в пакете csync2 для CentOS мне неизвестно. Замечу, что в коммерческом дистрибутиве SLES (crmsh там родной) все необходимые файлы есть, все работает из коробки сразу после установки пакетов.
Итак, создадим и добавим недостающие systemd-юниты.
Первый называется csync2.socket и содержит:
[Socket]ListenStream=30865Accept=yes[Install]WantedBy=sockets.target

Второй называется csync2@.service с таким содержимым:
[Unit]Description=csync2 connection handlerAfter=syslog.target[Service]ExecStart=-/usr/sbin/csync2 -i -vStandardInput=socketStandardOutput=socket

Оба файла нужно разместить в стандартной папке systemd /usr/lib/systemd/system.

Юнит, относящийся к сокету, нужно активировать и установить в автозапуск при загрузке ОС:
node1,2,3$ sudo systemctl enable --now csync2.socket

Примечание. Все настройки выполнялись для ОС CentOS, но есть подозрение, что эти действия также понадобятся и для других систем, например, для Debian или Ubuntu.

Теперь у нас все готово к началу работ по настройке кластера.

Настройка кластера с помощью crm


Настройка кластера производится в 2 этапа инициализация, затем создание и добавление ресурсов. Инициализация кластера с настроенным сервисом синхронизации конфигураций csync2 производится на одном узле.

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

На всякий случай сначала удалим кластер (на всех узлах) с помощью такого набора команд:
node1,2,3$ sudo systemctl stop corosync;sudo find /var/lib/pacemaker/cib/ -type f -delete; sudo find -f /etc/corosync/ -type f -delete

Далее надо выполнить команду инициализации кластера:
node1$ sudo crm cluster init --name demo-cluster --nodes "node1 node2 node3" --yes

где demo-cluster название нашего кластера.

По этой команде создаются необходимые файлы в папке /etc/corosync: corosync.conf, ключ авторизации authkey, а также прописываются ssh-ключи для беспарольной авторизации и выполнения команд в кластере с привилегиями суперпользователя root (на всех трех узлах кластера).

По умолчанию инициализация кластера выполняется в режиме multicast. Но есть также возможность проинициализировать кластер в режиме unicast:
node1$ sudo crm cluster init --unicast --name demo-cluster --nodes "node1 node2 node3" --yes

Кластер проинициализирован и запущен.
Проверить работоспособность можно с помощью консольного монитора состояния кластера crm_mon:
node1$ sudo crm_mon -Afr

Далее можно приступать к созданию ресурсов в кластере.

Создание ресурсов в кластере


Для начала поменяем некоторые значению по умолчанию. Например, порог миграции ресурсов migration-threshold по умолчанию равен 0. Меняем на 1, чтобы после первого сбоя ресурсы мигрировали на другой узел.

node1$ sudo crm configure rsc_defaults rsc-options: migration-threshold=1 resource-stickiness=INFINITY

По умолчанию, кластер запускается в симметричном режиме.

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

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

Зачем это нужно? Ну, например, в ситуации, когда в кластере ресурсы запускаются не на всех узлах, т.е. узлы неравноценны по ресурсам и назначению.

Если, вдруг, вам когда-то понадобится изменить режим кластера с симметричного на несимметричный, то достаточно ввести команду:
node1$ sudo crm configure property symmetric-cluster=false

Мы оставим этот параметр без изменения.

Включаем механизм stonith:
node1$ sudo crm configure property stonith-enabled=yes

Создадим и добавим ресурс виртуальный IP адрес:
node1$ sudo crm configure primitive master-vip IPaddr2 op start timeout=20s interval=0 op stop timeout=20s interval=0 op monitor timeout=20s interval=10s params ip=<virtual IP> nic=eth0

где <virtual IP> виртуальный IP-адрес в кластере.

С помощью монитора состояния кластера crm_mon можно убедиться в том, что ресурс успешно создан и запущен на первом попавшемся узле:
node1$ sudo crm_mon -Afr

Создадим ресурс postgresql и назовем его pg:
node1$ sudo crm configure primitive pg pgsql op start interval=0 timeout=120s op stop interval=0 timeout=120s op monitor interval=30s timeout=30s op monitor interval=29s role=Master timeout=30s params pgctl="/opt/pgpro/std-11/bin/pg_ctl" psql="/opt/pgpro/std-11/bin/psql" pgdata="/var/lib/pgpro/std-11/data" pgport="5432" repuser=postgres master_ip=<virtual IP> rep_mode=sync node_list="node1 node2 node3"

ПРИМЕЧАНИЕ:
В данном примере пути расположения бинарников и БД указаны по умолчанию для версии Postgres Pro Std 11. Также для упрощения указан пользователь для репликации postgres. Но ничто не мешает вам изменить умолчательные пути и пользователя репликации на свои.


Хочу обратить внимание на параметр rep_mode: он задан sync. Это означает, что в отказоустойчивом кластере хотя бы одна реплика будет синхронной. Синхронность реплики в кластере обеспечивает RPO=0 (кластер без потерь данных в случае сбоя).

Зададим тип ресурса Master-Standby (ms):
node1$ sudo crm configure ms mspg pg meta target-role=Master clone-max="3"

Нам нужно, чтобы ресурсы vip-master и mspg в режиме мастер запускались на одном узле:
node1$ sudo crm configure colocation pgsql-colocation inf: master-vip:Started mspg:Master

Указываем порядок запуска ресурсов сначала СУБД в режиме мастер, потом виртуальный IP:
node1$ sudo crm configure order order-promote-pgsql Mandatory: mspg:promote master-vip:start

Таким образом, мы создали 2 необходимых ресурса виртуальный IP адрес и ресурс postgresql.

Теперь можно переходить к настройке фенсинга в отказоустойчивом кластере.

Фенсинг узлов


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

Для начала можно ознакомиться со списком всех агентов фенсинга:
node1$ sudo crm ra list stonith

На моем стенде node1, node2, node3 это виртуальные машины, которые запущены и управляются с помощью гипервизора KVM. Соответственно, ресурс-агент фенсинга для KVM называется fence_virsh.

Вывести полную информацию о fence_virsh:
node1$ sudo crm ra info stonith:fence_virsh

Прежде чем создавать ресурс фенсинга, рекомендую проверить работоспособность всей цепочки от ОС виртуалки до гипервизора.

Проверка работоспособности фенсинга для узла node1 выглядит так:
node1$ fence_virsh -a <hypervisor IP> -l <username>-p <password> -n node1 -x --use-sudo -o status

где username & password учетная запись на хосте гипервизора.

Фенсинг для node1 настраивается так:
node1$ sudo crm configure primitive fence-node1 stonith:fence_virsh params ipaddr=<hypervisor IP> ip=<hypervisor IP> login=<username> username=<username> passwd=<password> pcmk_host_list=node1 sudo=1 op monitor interval=60s

ПРИМЕЧАНИЕ:
Ресурсы фенсинга не должны запускаться на своих узлах, иначе фенсинг может не сработать.

Следующее правило расположения запретит ресурсу фенсинга для узла node1 располагаться на этом узле:
node1$ sudo crm configure location l_fence_node1 fence-node1 -inf: node1

Для node2:
node1$ sudo crm configure primitive fence-node2 stonith:fence_virsh params ipaddr=<hypervisor IP> ip=<hypervisor IP> login=<username> username=<username> passwd=<password> pcmk_host_list=node2 sudo=1 op monitor interval=60snode1$ sudo crm configure location l_fence_node2 fence-node2 -inf: node2

Для node3:
node1$ sudo crm configure primitive fence-node3 stonith:fence_virsh params ipaddr=<hypervisor IP> ip=<hypervisor IP> login=<username> username=<username> passwd=<password> pcmk_host_list=node3 sudo=1 op monitor interval=60snode1$ sudo crm configure location l_fence_node3 fence-node3 -inf: node3

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

Инициализация кластера с помощью crm без csync2


Как обещал выше, расскажу про вариант инициализации кластера без установки и настройки csync2 (если по каким-то причинам вам не удалось его настроить).

Сначала вариант с использованием multicast.

Все команды выполняются на одном узле, например, на node1.
node1$ sudo crm cluster init --name demo-cluster --nodes "node1" --yes

По этой команде создаются необходимые файлы в папке /etc/corosync: corosync.conf, ключ авторизации authkey.

Далее нужно скопировать авторизационный файл authkey и corosync.conf на узлы node2 и node3:
node1$ sudo scp /etc/corosync/{authkey,corosync.conf} node2:/etc/corosync/node1$ sudo scp /etc/corosync/{authkey,corosync.conf} node3:/etc/corosync/

На остальных узлах (на node1 кластер уже запущен) запустить кластер:
node2,3$ sudo crm cluster start<source>С помощью монитора crm_mon можно убедиться, что кластер проинициализирован и запущен:<source lang="sh">node1$ sudo crm_mon -Afr


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

Все команды выполняются на одном узле, например, на node1.
node1$ sudo crm cluster init --unicast --name demo-cluster --nodes "node1" --yes

Открываем файл /etc/corosync/corosync.conf и добавляем строки в секцию nodelist:
node {ring0_addr: node2nodeid: 2}node {ring0_addr: node3nodeid: 3}

В секции quorum меняем число голосов:

expected_votes: 3

Далее необходим рестарт сервиса corosync на первом узле:
node1$ sudo systemctl restart corosync

Затем нужно скопировать файл authkey и отредактированный corosync.conf на узлы node2 и node3:
node1$ sudo scp /etc/corosync/{authkey,corosync.conf} node2:/etc/corosync/node1$ sudo scp /etc/corosync/{authkey,corosync.conf} node3:/etc/corosync/

На остальных узлах (на node1 кластер уже запущен) запустить кластер:
node2,3$ sudo crm cluster start

С помощью монитора crm_mon можно убедиться, что кластер проинициализирован и запущен:
node1$ sudo crm_mon -Afr

На этом инициализация кластера без csync2 закончена.

Вспомогательные команды crm



При работе с кластером могут пригодиться некоторые crm-команды.
Для удобства команды и пояснения сведены в таблицу 3:



На этом все. Буду рад, если статья поможет кому-то в создании и настройке отказоустойчивого кластера с помощью утилиты crm.
Подробнее..

Перевод Как автоматизировать развертывание баз данных с помощью Liquibase?

12.05.2021 20:07:31 | Автор: admin

Перевод материала подготовлен в рамках курса Экспресс-курс по управлению миграциями (DBVC).


Liquibase это инструмент управления изменениями в базе данных. С его помощью вы можете отслеживать изменения в базе данных, сделанные с помощью SQL (или XML) скриптов. Эти скрипты могут быть добавлены в системы контроля версий, такие как git.

В данной статье мы рассмотрим, как автоматизировать развертывание баз данных с помощью Liquibase. Автоматизация может быть выполнена с помощью

1. Пайплайн Jenkins

2. Shell-скриптов

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

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

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

Выполните приведенные ниже шаги:

Создать файл changelog (журнал изменений)

Создать XML-файл с именем liquibase-changelog.xml (имя может быть любым!) со следующим содержимым:

<?xml version="1.0" encoding="UTF-8"?><databaseChangeLog xmlns="http://personeltest.ru/away/www.liquibase.org/xml/ns/dbchangelog"xmlns:xsi="http://personeltest.ru/away/www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://personeltest.ru/away/www.liquibase.org/xml/ns/dbchangeloghttp://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd"><include file="<path to changeset SQL file>/<changeset file name>.sql>" relativeToChangelogFile="true"/>...more <include> tags goes here...</databaseChangeLog>

Обратите внимание на тег include в приведенном выше XML. Каждый файл SQL changeset (набор изменений), который должен отслеживаться Liquibase, должен быть зарегистрирован в этом файле changelog (журнал изменений).

Создание наборов изменений (changeset) SQL

Добавьте файлы SQL changeset в выбранное вами место. Синтаксис SQL, который работает с Liquibase, следующий:

--liquibase formatted sql--changeset <author name>:<a unique identifier for the SQL changeset><SQL statements go here><SQL statements go here>--rollback <rollback SQL statements>--rollback <rollback SQL statements>

Рассмотрим пример:

--liquibase formatted sql--changeset xameeramir:create-test-tableCREATE TABLE IF NOT EXISTS testTable(columnName1 VARCHAR (355));--rollback DROP TABLE--rollback testTable

Обратите внимание, что файл SQL changeset отличается от файла XML changelog.

Регистрация SQL changeset в XML-файле changelog

Включите файл SQL changeset в файл changelog, который мы создали ранее, со следующими тегами XML:

<include file=<path to SQL changeset file>/<changeset file name>.sql relativeToChangelogFile="true" />

Добавьте столько SQL changesets и зарегистрируйте их в файле changelog, сколько вам нужно.

Триггер в Liquibase для обновления базы данных

Просто выполните приведенную ниже команду:

liquibase --changeLogFile=<path to changelog file>/<liquibase changelog file name>.xml --username=<database username> --password=<database password> --classpath=<path to the liquibase installation>/postgresql-42.2.5.jar --url=jdbc:postgresql://<database url>/<database name> update

Classpath (путь к классам) - это драйвер JDBC, который мы настроили в предыдущей публикации. Postgresql-42.2.5.jar - это JDBC-драйвер, предназначенный для Postgres, и его можно будет заменить на базу данных по вашему выбору без каких-либо специальных преобразований на этих этапах.

Приведенная выше команда может быть использована в shell-скриптах или в пайплайне CI/CD для запуска обновлений базы данных.

Автоматизация CI/CD

После того, как вышеуказанная конфигурация установлена - автоматизация может быть выполнена либо на клиенте с помощью shell-скриптов, либо на сервере с помощью shell-скриптов или имплементации CI/CD.

Предположим, что имплементация CI/CD, которая запускает развертывание Liquibase, означает выполнение команды триггер (trigger) Liquibase, приведенной выше, при каждом git push в ветку DEVELOP (или любую другую).

Первым предварительным условием будет наличие файла liquibase-changelog.xml. Допустим, мы сохраним его на уровне ~/ с операторами include, указывающими на папку, в которой находятся changeset SQL. Следующий рабочий процесс позволит автоматизировать развертывание базы данных с помощью пайплайна CI/CD:

  • Поместите файл SQL changeset в репозиторий функций.

  • Отправьте запрос на исправление для ветки DEVELOP

  • После достоверной проверки и согласования объедините ветку feature с веткой DEVELOP.

  • Имплементация CI/CD, настроенная на сервере DEVELOP, запустит Liquibase для обновления базы данных.

  • Liquibase автоматически будет выполнять только новые файлы (любые уже выполненные файлы не будут запущены повторно).

Автоматизация с помощью shell-скриптов

В shell-скриптах будет записана одна и та же команда триггер Liquibase. Как только shell-скрипты будут выполнены, содержащие их changeset (наборы изменений) Liquibase будут выполнены автоматически.

Вы можете задаться вопросом, как shell-скрипты узнают, когда выполнять команду? Ответ прост:

  • Shell-скрипты могут выполняться на триггерах cron.

  • Shell-скрипты могут быть выполнены при некоторых системных событиях.

Выбор за вами!


Узнать подробнее об экспресс-курсе по управлению миграциями (DBVC)

Подробнее..

Как использовать ClickHouse не по его прямому назначению

09.04.2021 12:18:12 | Автор: admin

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

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

ClickHouse для тестов железа

Самое простое, что можно сделать с ClickHouse, если есть свободные серверы это использовать его для тестов оборудования. Потому что его тестовый dataset содержит те же данные с production Яндекса, только анонимизированные и они доступны снаружи для тестирования. Про то, как подготовить хорошие анонимизированные данные, я рассказывал на Saint HighLoad++ 2019 в Санкт-Петербурге.

Ставим ClickHouse на любой Linux (x86_64, AArch64) или Mac OS. Как это сделать? мы собираем его на каждый коммит и pull request. ClickHouse Build Check покажет нам все детали всех возможных билдов:

Отсюда можно скачать любой бинарник с gcc и clang в релизе, в debug, со всякими санитайзерами или без, для x86, ARM или даже Mac OS. ClickHouse использует все ресурсы железа: все ядра CPU, шины памяти и грузит все диски. Какой сервер ему не дай проверит полностью, хорошо или плохо тот работает.

По этой инструкции можно скачать бинарник, тестовые данные и запустить запросы. Тест займёт около 30 минут и не требует установки ClickHouse. И даже если на сервере уже установлен другой ClickHouse, вы все равно сможете запустить тест.

В результате мы получаем публичный список протестированного железа:

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

Вы можете выбрать серверы для сравнения для каждого можно посмотреть разницу в производительности. Конечно, и других тестов железа существует немало, например, SPECint и вообще куча тестов из организации SPEC. Но ClickHouse позволяет не просто тестировать железо, а тестировать рабочую СУБД на настоящих данных на реальных серверах откуда угодно.

ClickHouse без сервера

Конечно, обычно ClickHouse это сервер + клиент. Но иногда нужно просто обработать какие-то текстовые данные. Для примера я взял все исходники ClickHouse, собрал их в файл и сконкатенировал в файл под названием code.txt:

И, например, я хочу проверить, какие строчки в коде на C++ самые популярные. С помощью типичной shell-команды удалим из каждой строчки кода начальные пробелы и пустые строки, отсортируем и посчитаем количество уникальных. После сортировки видим, что, конечно, самая популярная строчка это открывающая фигурная скобка, за ней закрывающая фигурная скобка, а еще очень популярно return false.

Этот результат я получил за 1,665 секунд. Потому что все это было сделано с учетом сложной локали. Если локаль заменить на простую, выставив переменную окружения LC_ALL=C, то будет всего лишь 0,376 с, то есть в 5 раз быстрее. Но это всего-лишь шел скрипт.

Можно ли быстрее? Да, если использовать clickhouse-local, будет еще лучше.

Это как-будто одновременно и сервер и клиент, но на самом деле ни то, и ни другое clickhouse-local может выполнять SQL запросы по локальным файлам. Вам достаточно указать запрос, структуру и формат данных (можно выбрать любой из форматов, по умолчанию TabSeparated), чтобы обработать запрос на входном файле. За 0.103 секунд то есть в 3,716 раз быстрее (в зависимости от того, как вы запускали предыдущую команду).

Для демонстрации чего-то более серьезного давайте посмотрим на те данные, которые собирает GitHub Archive это логи всех действий всех пользователей, которые происходили на GitHub, то есть коммиты, создание и закрытие issue, комментарии, код-ревью. Все это сохраняется и доступно для скачивания на сайте https://www.gharchive.org/ (всего около 890 Гб):

Чтобы их как-нибудь обработать, выполним запрос с помощью ClickHouse local:

Я выбрал все данные из табличной функции file, которая берет файлы вида *.json.gz то есть все файлы в формате TSV, интерпретируя их как одно поля типа string. С помощью функции для обработки JSON я вытащил из каждой JSONины сначала поле 'actor', а потом поле 'login' в случае, когда оно равно Алексей Миловидов и выбрал таких первых 10 действий на GitHub.

Может возникнуть впечатление, что 890 Гб данных смогли обработаться за 1,3 секунды. Но на самом деле запрос работает потоково. После того, как находятся первые 10 строк, процесс останавливается. Теперь попробуем выполнить более сложный запрос, например, я хочу посчитать, сколько всего действий я совершил на GitHub. Используем SELECT COUNT... и через полторы секунды кажется, что ничего не происходит. Но что происходит на самом деле, мы можем посмотреть в соседнем терминале с помощью программы dstat:

И мы видим, что данные читаются с дисков со скоростью примерно 530 Мб/с и все файлы обрабатываются параллельно почти с максимальной скоростью насколько позволяет железо (на сервере RAID из нескольких HDD).

Но можно использовать ClickHouse local даже без скачивания этих 980 Гб. В ClickHouse есть табличная функция url то есть можно вместо file написать адрес https://.../*.json.gz, и это тоже будет обрабатываться.

Чтобы можно было выполнять такие запросы в ClickHouse, мы реализовали несколько вещей:

  1. Табличная функция file.

  2. Поддержка glob patterns. В качестве имени файлов можно использовать шаблон с glob patterns (звёздочка, фигурные скобки и пр.)

  3. Поддержка сжатых файлов в формате gzip, xz и zstd из коробки. Указываем gz и всё работает.

  4. Функции для работы с JSON. Могу утверждать, что это самые эффективные функции для обработки JSON, которые мы смогли найти. Если вы найдёте что-нибудь лучше, скажите мне.

  5. Параллельная обработка файлов. Там несколько тысяч файлов, и будут использоваться все процессорные ядра. И даже один файл будет обрабатываться параллельно, потому что у нас есть параллельный парсинг форматов данных.

  6. Тот самый параллельный парсинг.

Применять можно, само собой, для обработки текстовых файлов. Еще для подготовки временной таблицы и партиций для MergeTree. Можно провести препроцессинг данных перед вставкой: читаете в одной структуре, преобразовываете с помощью SELECT и отдаете дальше в clickhouse-client. Для преобразования форматов тоже можно например, преобразовать данные в формате protobuf с разделителями в виде длины в JSON на каждой строке:

clickhouse-local --input-format Protobuf --format-schema такая-то --output format JSONEachRow ...

Serverless ClickHouse

ClickHouse может работать в serverless-окружении. Есть пример, когда ClickHouse засунули в Лямбда-функцию в Google Cloud Run: https://mybranch.dev/posts/clickhouse-on-cloud-run/ (Alex Reid). Там на каждый запрос запускается маленький ClickHouse на фиксированных данных и эти данные обрабатывает.

Текстовые форматы

Для обработки текстовых данных, естественно, есть поддержка форматов tab separated (TSV) и comma separated (CSV). Но еще есть формат CustomSeparated, с помощью которого можно изобразить и тот, и другой в качестве частных случаев.

CustomSeparated:

format_custom_escaping_rule

format_custom_field_delimiter

format_custom_row_before/between/after_delimiter

format_custom_result_before/after_delimiter

Есть куча настроек, которые его кастомизируют. Первая настройка это правило экранирования. Например, вы можете сделать формат CSV, но в котором строки экранированы как в JSON, а не как CSV. Разница тонкая, но довольно важная. Можно указать произвольный разделитель типа | и пр. между значениями, между строками и т.п.

Более мощный формат это формат Template:

format_template_resultset

format_template_row

format_template_rows_between_delimiter

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

Есть формат Regexp:

format_regexp

format_regexp_escaping_rule

format_regexp_skip_unmatched

И тут clickhouse-local превращается в настоящий awk. Указываете регулярные выражения, в Regexp есть subpatterns, и каждый subpattern теперь парсится как столбец. Его содержимое обрабатывается согласно некоторому правилу экранирования. И конечно можно написать пропускать строки, для которых регулярное выражение сработало, или нет.

ClickHouse для полуструктурированных данных

Первая идея в этом случае не использовать ClickHouse. Берите Mongo, которая отлично подходит именно для этого. Но если вдруг вы так любите ClickHouse, что просто не можете им не пользоваться можно и с его помощью.

Допустим, у вас есть таблица с логами, в ней есть столбец с датой и временем, а вот всё остальное вообще непонятно что. Очень соблазнительно всю эту кучу данных записать в один столбец 'message' с типом String. Если эта куча в формате JSON, функции для работы с JSON будут работать. Но неэффективно каждый раз, когда нам будет нужно только одно поле, например 'actor.login', читать придется весь JSON не будет преимущества столбцовой базы данных. С помощью ClickHouse мы легко это исправим прямо на лету, добавив с помощью запроса ALTER материализованный столбец:

После того, как материализованный столбец actor_login начнет записываться для новых данных в таблице, SELECT для новых данных будет читаться эффективно, а для старых будет вычисляться. Для материализации старых данных можно использовать несколько странный запрос для материализации всех данных в прошлом:

ALTER TABLE logs UPDATE actor_login = actor_login

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

Ускорение MySQL

В ClickHouse можно создать таблицу на основе табличной функции MySQL. Это просто: указываете хост: порт, БД, таблицу, имя пользователя и пароль (прямо так, как есть), делаем SELECT и всё выполняется за 15 секунд:

Работает это тоже без всякой магии: табличная функция MySQL переписывает запрос, отправляет его в MySQL и читает все данные назад, на лету на всё 15 секунд. Но что будет, если я тот же самый запрос выполню в MySQL как есть?

5 минут 41 секунда это позор! У ClickHouse тут как-будто нет преимуществ данные нужно переслать из MySQL в ClickHouse и потом уже обработать. А MySQL обрабатывает сам у себя локально почему же он так медленно работает?

Еще одна проблема результаты расходятся. У ClickHouse две строки счетчик (20577 и 13772), у MySQL один (44744), потому что он здесь учитывает collation (правила сравнения строк в разном регистре) при GROUP BY. Чтобы это исправить, можно перевести имя в нижний регистр, сгруппировать по нему и выбрать любой вариант:

Теперь счетчик один, данные почти совпадают и запрос даже ускорился. То есть ClickHouse быстрый независимо от того, какие выражения я напишу. Попробуем ускорить запрос еще больше. На основе данных из MySQL в ClickHouse можно создать словарь. Указываем в словаре ключ и источник MySQL со всеми этими данными:

Словарь будет периодически загружать таблицу в оперативку, она будет кэшироваться. Можно применять SELECT:

Получилось уже 6 секунд, хотя основное предназначение словаря использование для джойнов, когда, например, нам нужно получить данные динамически через другие столбцы. Можно также создать словари MySQL на каждом сервере в кластере ClickHouse и быстро с ними работать. Но если MySQL не выдержит нагрузки при обновлении словаря на каждом сервере в кластере, то можно создать из MySQL словарь на двух ClickHouse-серверах, и они будут доступны в виде таблиц в ClickHouse. Если с помощью движка Distributed создать таблицу, которая смотрит на эти два сервера как на реплики, то можно на всех ClickHouse-серверах создать словари из ClickHouse, которые смотрят на таблицу, которая смотрит на словарь MySQL.

Словари еще можно использовать для шардирования, если схема расположена во внешней мета-базе (и не обязательно в ClickHouse). Это тоже будет работать:

Есть вариант как радикально ускорить запрос, и для этого не нужны словари надо всего лишь переложить данные в полноценную таблицу типа MergeTree такой же структуры. Вставляем туда данные и делаем SELECT:

Видим, что SELECT выполняется за 0,6 с. Вот это настоящая скорость, какая должна быть это скорость ClickHouse!

В ClickHouse можно даже создать базу данных типа MySQL. Движок БД MySQL создает в ClickHouse базу данных, которая содержит таблицы, каждая из которых представляет таблицу, расположенную в MySQL. И все таблицы будут видны прямо в ClickHouse:

А вообще в ClickHouse много табличных функций. Например, с помощью табличной функции odbc можно обратиться к PostgreSQL, а с помощью url к любым данным на REST-сервере. И все это можно поджойнить:

Примечение: в свежих релизах ClickHouse появилась табличная функция postgresql, движок баз данных PostgreSQL и даже поддержка бинарного протокола PostgreSQL. Кажется это даже слишком круто.

Машинное обучение в ClickHouse

В ClickHouse можно использовать машинное обучение с помощью подключения готовых моделей CatBoost. После объявления модели в конфигурационном файле, она доступна для применения в виде функции modelEvaluate.

Это можно использовать для заполнения пропусков в данных. Пример: компания, занимающаяся недвижимостью, публикует объявления о квартирах с разными параметрами: количество комнат, цена, метраж. Часто некоторые параметры не заполнены например, квадратные метры есть, а количества комнат нет. В этом случае мы можем использовать ClickHouse с моделью CatBoost, чтобы заполнить пропуски в данных.

Более простые модели можно обучать прямо в ClickHouse. Модель машинного обучения у нас будет представлена как агрегатная функция например, стохастическая логистическая регрессия. Задаются гиперпараметры, значение предсказываемой функции и значения фич, причем обучение будет независимым для каждого ключа агрегации, если в запросе указан GROUP BY:

А еще мы можем добавить к агрегатной функции суффикс State:

SELECT stochasticLogisticRegressionState(...

Так можно обучить логистическую регрессию для каждого k и получить состояние агрегатной функции. Состояние имеет полноценный тип данных AggregateFunction(stochasticLogisticRegression(01, 00, 10, 'Adam'), ...), который можно сохранить в таблицу. Достать его из таблицы и применить обученную модель можно функцией applyMLModel:

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

Более развернуто описано в этой презентации.

ClickHouse как графовая база данных

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

Это работает, и говорят, даже быстрее, чем некоторые другие графовые базы данных. Разработал его наш друг, один из ведущих контрибьюторов Amos Bird. Правда, эта разработка не доступна в open-source. Но мы не обижаемся.

UDF в ClickHouse

Казалось бы, в ClickHouse нет возможности написать пользовательские функции (user defined functions). Но на самом деле есть. Например, у вас есть cache-словарь с источником executable, который для загрузки выполняет произвольную программу или скрипт на сервере. И в эту программу в stdin передаются ключи, а из stdout в том же порядке мы будем считывать значения для словаря. Словарь может иметь кэширующий способ размещения в памяти, когда уже вычисленные значения будут кэшированы.

И если вы пишете произвольный скрипт на Python, который вычисляет, что угодно пусть те же модели машинного обучения, и подключаете его в ClickHouse, то получаете вы как раз аналог user defined function.

Примечание: полноценная реализация UDF находится в roadmap на 2021 год.

ClickHouse на GPU и как Application Server

Это ещё два необычных примера. В компании nVidia ClickHouse заставили работать на графических ускорителях, но рассказывать я про это не буду.

А наш друг Zhang2014 превратил ClickHouse почти в Application Server. У Zhang2014 есть pull request, где можно определить свои HTTP-хэндлеры и этим хэндлерам приписать подготовленный запрос (SELECT с подстановками или INSERT). Вы делаете POST на какой-то хэндлер для вставки данных, или делаете вызов какой-то GET ручки, передаете параметры, и готовый SELECT выполнится.

Вывод

ClickHouse очень интересная система, в которой всегда можно найти что-то новое, причем, что интересно, что-то новое там всегда могу найти даже я. Многие разработчики удивляют меня тем, что они могут реализовывать внутри ClickHouse всего лишь чуть-чуть поменяв его код. И эти штуки будут работать и в production!

Подробнее..

Шардинг, от которого невозможно отказаться

22.04.2021 10:12:30 | Автор: admin
image

А не пора ли нам шардить коллекции?
Не-е-е:


  • у нас нет времени, мы пилим фичи!
  • CPU занят всего на 80% на 64 ядерной виртуалке!
  • данных всего 2Tb!
  • наш ежедневный бекап идет как раз 24 часа!

В принципе, для большинства проектов вcё оправдано. Это может быть еще прототип или круг пользователей ограничен Да и не факт, что проект вообще выстрелит.
Откладывать можно сколько угодно, но если проект не просто жив, а еще и растет, то до шардинга он доберется. Одна беда, обычно, бизнес логика не готова к таким "внезапным" вызовам.
А вы закладывали возможность шардинга при проектировании коллекций?


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


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


Всем привет, от команды разработки Smartcat и наших счастливых админов!


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


Зачем нам шардинг


Шардинг штатная возможность горизонтального масштабирования в MongoDB. Но, чтобы стоимость нашего шардинга была линейной, нам надо чтобы балансировщик MongoDB мог:


  • выровнять размер занимаемых данных на шард. Это сумма размера индекса и сжатых данных на диске.
  • выровнять нагрузку по CPU. Его расходуют: поиск по индексу, чтение, запись и агрегации.
  • выровнять размер по update-трафику. Его объем определяет скорость ротации oplog. Это время за которое мы можем: поднять упавший сервер, подключить новую реплику, снять дамп данных.

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


Особенности шардинга в MongoDB


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


  1. Ключ шардирования должен быть высокоселективный. В противном случае мы не получим достаточного числа интервалов данных для балансировки.
  2. Данные должны поступать с равномерным распределением на весь интервал значений ключа. Тривиальный пример неудачного ключа это возрастающий int или ObjectId. Все операции по вставке данных будут маршрутизироваться на последний шард (maxKey в качестве верхней границы).

Самое значимое ограничение гранулярность ключа шардирования.
Или, если сформулировать отталкиваясь от данных, на одно значение ключа должно приходиться мало данных. Где "мало" это предельный размер чанка (от 1Mb до 1Gb) и количество документов не превышает вот эту вот величину.


К сожалению, в реальной жизни, не всегда удается подогнать модель данных под эти требования.


Бизнес логика требует слонов


Теперь давайте посмотрим, с чем мы будем сталкиваться при проектировании бизнес логики.
Я рассмотрю только самый чувствительный для шардирования случай, который заставляет наши данные группироваться в неравномерные объемы.
Рассмотрим следующий сценарий:


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

Пример модели:


{    _id: ObjectId("507c7f79bcf86cd7994f6c0e"),    projectId: UUID("3b241101-e2bb-4255-8caf-4136c566a962"),    name: "job name 1",    creation: ISODate("2016-05-18T16:00:00Z"),    payload: "any additional info"}

Как может выглядеть логика выбора ключа шардирования?
Совершенно точно, в качестве первого поля стоит взять projectId. Он есть в большинстве запросов, т.е. бОльшая часть запросов будет направлена роутером на нужный шард.


Теперь, надо выбрать второе поле ключа или оставить только первое.
Например, у нас 20% запросов используют только поле name, еще 20% только поле creation, а остальные опираются на другие поля.
Если в ключ шардирования включить второе поле, то крупные проекты, те у которых объем работ не помещается в одном чанке, будут разделены на несколько чанков. В процессе разделения, высока вероятность, что новый чанк будет отправлен на другой шард и для сбора результатов запроса нам придётся обращаться к нескольким серверам. Если мы выберем name, то до 80% запросов будут выполняться на нескольких шардах, тоже самое с полем creation. Если до шардирования запрос выполнялся на одном сервере, а после шардирования на нескольких, то нам придется дополнительную читающую нагрузку компенсировать дополнительными репликами, что увеличивает стоимость масштабирования.


С другой стороны, если оставить в ключе только первое поле, то у нас "идеальное" линейное разделение нагрузки. Т.е. каждый запрос с полем projectId будет сразу отправлен на единственный шард. Поэтому имеет смысл остановиться на этом выборе.
Какие риски есть у этого выбора:


  • у нас могут появляться не перемещаемые jumbo-чанки. При большом объеме работ в одном проекте мы обязательно превысим предельный размер чанка. Эти чанки не будут разделены балансировщиком.
  • у нас будут появляться пустые чанки. При увеличении проекта, балансировщик будет уменьшать диапазон данных чанка для этого проекта. В пределе, там останется только один идентификатор. Когда большой проект будет удален, то на этот узкий чанк данные больше не попадут.

Оба этих случая несколько искажают KPI штатного балансировщика. В первом случае чанк вообще не перемещается. Во втором, он перемещается, но это не приводит к смещению данных. С течением времени, мы можем получить ситуацию когда число пустых чанков превышает число чанков с данными, а еще часть чанков не перемещаемые.


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


Проблемы с масштабированием


Итак, мы, вопреки ожиданиям, набрали достаточно неперемещаемых чанков, чтобы это стало заметно. То есть буквально, случай из практики. Вы заказываете админам новый шард за X$ в месяц. По логам видим равномерное распределение чанков, но занимаемое место на диске не превышает половины. С одной стороны весьма странное расходование средств было бы, а с другой стороны возникает вопрос: мы что же, не можем прогнозировать совершенно рутинную операцию по добавлению шарда? Нам совсем не нужно участие разработчика или DBA в этот момент.


Что интересно, на этапе проектирования схемы данных, у нас и нет особого выбора. Мы не можем предсказать характер использования на годы вперед. А владельцы продукта очень не охотно формулируют строгие ограничения при проектировании. Даже оглядываясь назад, не всегда понятно, как можно было бы перепроектироваться, чтобы успеть все сделать тогда. Так что продолжаем работать с тем шардированием, которое заложили.


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


Надеюсь, тут уже все уже запуганы и потеряли надежду. ;)


Решение


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


1-й воспользоваться командой moveChunk прямое указание балансировщику о перемещении конкретного чанка.


2-й воспользоваться командой addTagRange привязка диапазона значений ключа шардирования к некоторому шарду и их группе.


В обоих случаях потребуется также точное знание размера чанка. Это можно сделать командой dataSize, которая возвращает некоторую статистику по объектам в коллекции на заданном интервале ключей.


Предварительное прототипирование 1-го варианта выявило дополнительные особенности.


Команды moveChunk не отменяют работы штатного балансировщика. Он вполне может принять решение об обратном переносе. Тут надо блокировать работу штатного балансировщика. Или кроме перемещения больших чанков на недогруженный шард, надо искать на нем маленькие чанки и синхронно перемещать их на перегруженный шард.


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


Массовые сканирования dataSize ухудшают отзывчивость сервера на боевых запросах.


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


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


Основная идея проста.
Давайте подберем такие диапазоны ключей на каждам шарде, чтобы в итоге части коллекции занимали одинаковый размер на шардах.


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


Процесс выравнивания разделим на 2 основных этапа:
Первый этап это предварительное разбиение. Оно по сути дефрагментирует данные на непрерывные отрезки на каждый шард.
Второй этап коррекция границ ключей. Этот этап можно повторять периодически, ориентируясь по фактическому занимаемому месту на диске.


Группировка данных


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


Итак, коллекция уже шардирована.


  • Читаем все ее чанки из коллекции config.chunks с сортировкой по возрастанию ключа {min: 1}
  • Распределяем чанки по шардам, так чтобы их было примерно одинаковое количество. Но при этом все чанки на одном шарде должны объединяться в один интервал.

Например:


У нас есть три шарда sh0, sh1, sh2 с одноименными тегами.
Мы вычитали поток из 100 чанков по возрастанию в массив


var chunks = db.chunks.find({ ns: "demo.coll"}).sort({ min: 1}).toArray();

Первые 34 чанка будем размещать на sh0
Следующие 33 чанка разместим на sh1
Последние 33 чанка разместим на sh2
У каждого чанка есть поля min и max. По этим полям мы выставим границы.


sh.addTagRange( "demo.coll", {shField: chunks[0].min}, {shField: chunks[33].max}, "sh0");sh.addTagRange( "demo.coll", {shField: chunks[34].min}, {shField: chunks[66].max}, "sh1");sh.addTagRange( "demo.coll", {shField: chunks[67].min}, {shField: chunks[99].max}, "sh2");

Обратите внимание, что поле max совпадает с полем min следующего чанка. А граничные значения, т.е. chunks[0].min и chunks[99].max, всегда будут равны MinKey и MaxKey соответственно.
Т.е. мы покрываем этими зонами все значения ключа шардирования.


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


Коррекция размера


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


sh.addTagRange( "demo.coll", {shField: MinKey}, {shField: 1025}, "sh0");sh.addTagRange( "demo.coll", {shField: 1025}, {shField: 5089}, "sh1");sh.addTagRange( "demo.coll", {shField: 5089}, {shField: MaxKey}, "sh2");

Вот так будет выглядеть размещение чанков:


Командой db.demo.coll.stats() можно получить объем данных, которые хранятся на каждом шарде. По всем шардам можно вычислить среднее значение, к которому мы хотели бы привести каждый шард.


Если шард надо увеличить, то его границы надо перемещать наружу, если уменьшить, то внутрь.
Так как уже явно заданы диапазоны ключей, то балансировщик не будет их перемещать с шарда на шард. Следовательно, мы можем пользоваться командой dataSize, мы точно знаем данные какого шарда мы сканируем.
Например, нам надо увеличить sh0 за счет h1. Границу с ключем будем двигать в бОльшую сторону. Сколько именно данных мы сместим перемещением конкретного 34-го чанка, мы можем узнать командой dataSize с границами этого чанка.


db.runCommand({ dataSize: "demo.coll", keyPattern: { shField: 1 }, min: { shField: 1025 }, max: { shField: 1508 } })

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


Вот так будет выглядеть смещение границы на один чанк



Вот пример команд для фиксации нового состояния границ в конфигурации кластера.
Мы сначала удаляем пару интервалов со старыми значениями границ, потом заводим новые интервалы с актуальными значениями границ.


sh.removeTagRange( "demo.coll", {shField: MinKey}, {shField: 1025}, "sh0");sh.removeTagRange( "demo.coll", {shField: 1025}, {shField: 5089}, "sh1");sh.addTagRange( "demo.coll", {shField: MinKey}, {shField: 1508}, "sh0");sh.addTagRange( "demo.coll", {shField: 1508}, {shField: 5089}, "sh1");

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


Выгодные особенности этого подхода:


  • до смещения границ, данные не перемещаются. Мы можем их сканировать без опасения, что эта информация устареет. Ну конечно, исключая работу приложения для которого и заведена эта БД.
  • команда dataSize применяется только к перемещаемым данным. Мы не сканируем лишнего.
  • путь к целевому состоянию непрерывный. Т.е. мы можем проводить частичные коррекции, например если видим, что сканирование идет долго и реальное распределение данных успевает сильно измениться.

Дополнительные возможности


Вообще, на практике требуется выравнивание используемого объема диска на шардах, а не только части шардированных коллекции. Частенько, нет времени или возможности проектировать шардирование вообще всех БД и коллекций. Эти данных лежат на своих primary-shard. Если их объем мал, то его легко учесть при коррекции размера и просто часть данных оттащить на другие шарды.


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


Теперь все значительно проще. Соседние чанки и так на одном шарде. Можно объединять хоть весь интервал целиком. Если он конечно без jumbo-чанков, их надо исключить. Есть на интервале пустые чанки или нет, это уже не важно. Балансировщик заново сделает разбиение по мере добавления или изменения данных.


Почти итог


Итак, проблема решена.
Хорошо, что не пришлось заменять или сильно вмешиваться в работу штатного балансировщика. Он продолжает рулить другими коллекциями или диапазонами данных. А в наших настроенных диапазонах он выполняет разделение чанков и таскает чанки между серверами как мы попросим.


Наши плюсы:


  • точная балансировка занимаемого размера
  • лучшее распределение update-трафика
  • минимальное сканирование при коррекциях
  • бесплатная уборка пустых чанков
  • минимальные допуски на запасное/неиспользуемое дисковое пространство на каждом шарде
  • мы всегда можем удалить привязки диапазонов ключей и вернуться к исходному состоянию

Минусы:


  • требуется настройка и сопровождение привязок диапазонов ключей.
  • усложняется процесс добавления нового шарда.

Но это было бы слишком скучно Время победить слонов и не вернуться!


Победа над слонами!


Как мы уже отмечали, единственная причина неперемещаемых чанков недостаточная селективность ключа шардирования. Мы сами снижали селективность ключа для привязки запроса к шарду. Но мы уже сгруппировали данные, а теперь мы можем этим коварно воспользоваться!


Можно увеличить селективность ключа шардирования, если добавить к нему второе поле с достаточной селективностью. Поле может быть любым. Подойдет даже _id.


Вспомним пример модели:


{    _id: ObjectId("507c7f79bcf86cd7994f6c0e"),    projectId: UUID("3b241101-e2bb-4255-8caf-4136c566a962"),    name: "job name 1",    creation: ISODate("2016-05-18T16:00:00Z"),    payload: "any additional info"}

Ранее мы выбрали ключ шардирования {projectId: 1}
Но теперь при проектировании можно выбрать любые уточняющие поля для ключа шардирования:


  • {projectId: 1, name: 1}
  • {projectId: 1, creation: 1}
  • {projectId: 1, _id: 1}

А если вы пользуетесь MongoDB версии 4.4 и выше, то там есть удобная функция уточнение ключа шардирования на существующих данных.


Данные дефрагментированы, а это дает нам гарантии того, что основной объем документов работ с одинаковым projectId будет находиться на одном шарде. Как это выглядит на практике?


Вот иллюстрация примеров размещения работ по чанкам. В случае, если мы выбрали ключ шардирования {projectId: 1, _id: 1}



Здесь, для упрощения примера, идентификаторы представлены целыми числами.
А термином "проект" я буду называть группу работ с одинаковым projectId.


Некоторые проекты будут полностью умещаться в один чанк. Например, проекты 1 и 2 размещены в 1м чанке, а 7-й проект во 2-м.
Некоторые проекты будут размещены в нескольких чанках, но это будут чанки с соседними границами. Например, проект 10 размещен в 3, 4 и 5 чанках, а проект 18 в 6 и 7 чанках.
Если мы будем искать работу по ее полю projectId, но без _id, то как будет выглядеть роутинг запросов?


Планировщик запросов MongoDB отлично справляется с исключением из плана запроса тех шардов, на которых точно нет нужных данных.
Например, поиск по условию {projectId: 10, name: "job1"} будет только на шарде sh0


А если проект разбит границей шарда? Вот как 18-й проект например. Его 6-й чанк находится на шарде sh0, а 7-й чанк находится на шарде sh1.
В этом случае поиск по условию {projectId: 18, name: "job1"} будет только на 2х шардах sh0 и sh1. Если известно, что размер проектов у нас меньше размера шарда, то поиск будет ограничен только этими 2-мя шардами.


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


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


  • группа располагается на одном, максимум двух шардах.
  • число групп которые имели несчастье разместиться на 2х шардах ограничено числом границ. Для N шардов будет N-1 граница.

Больше одно шардовых запросов, меньше лишней нагрузки на чтение, меньше дополнительных реплик!
Мало нам было бы радости, но с таких сильно селективным ключом мы и от jumbo-чанков избавляемся.


И вот теперь, от дефрагментации данных нам уже никуда не деться.


Точно итог


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


Теперь уже можно оценить достижения и потери.
Достижения:


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

Потери:


  • если за счет дефрагментации мы убирали jumbo-чанки, то мы теперь с ней навсегда. Вернуть ключ мы не можем. Удаление границ приведет к фрагментации данных и снижению доли одно шардовых запросов.
  • периодически надо проводить коррекции границ. Но кажется это мы сможем автоматизировать.
  • при добавлении нового шарда надо явно планировать переразбиение.

Осталось спроектировать весь процесс дефрагментации, расчета поправок и коррекции границ Ждите!

Подробнее..

Перевод Самые популярные базы данных 20062021гг

31.05.2021 18:12:13 | Автор: admin

(статья обновлена в мае 2021г.)

Какие системы управления базами данных (СУБД) распространены в мире больше всего? Как они изменились с 2006года и какие входят в десятку самых популярных? В этой статье мы проанализируем базы данных, которые были на пике популярности с 2006 по 2021год. Данные обновляются каждый месяц. Подробнее в индексе ведущих баз данных TOPDB. Итак, рассмотрим самые популярные базы данных с 2006 по 2021год.

15 самых популярных баз данных с 2006 по 2021год

Какая база данных стала самой популярной в 2021году? Согласно рейтингу БД, это Oracle. Этой базой данных пользуются 30,2% респондентов. В два раза меньше респондентов используют MySQL (16,65%) и SQL Server (13,21%) второе и третье места соответственно. В совокупности на долю этих трех СУБД приходится более 62% общего числа пользователей. На четвертой строчке расположилась СУБД Microsoft Access 9%. На долю баз данных, занявших пятое и последующие места, приходится менее 5%.

При этом Oracle занимает то же положение, что и 15лет назад. В мае 2006года этой СУБД пользовались 31,8% респондентов. На втором месте была MySQL 24,5%. В совокупности этими двумя базами данных в 2006году пользовались более 55% респондентов. Третью строчку в 2006году занимала СУБД Microsoft Access. Тогда ее использовали 17,6% респондентов, но в 2021году их количество сократилось почти вдвое и составило 9,07%. СУБД SQL Server с тех пор поднялась на одну позицию, и хотя ее показатель по-прежнему составляет около 13%, ей удалось обойти Access.

Рейтинг баз данных DB-Engines май 2021года

В мае 2021года лидером рейтинга DB-Engines остается Oracle. За ней следует MySQL, которая набрала 1236баллов, и Microsoft SQL Server 992,66балла.

Рейтинг DB-Engines март 2021года: Визуализация данных через платформу Flourish

Мы рассмотрели самые популярные базы данных в рейтинге TOPDB. TOPBD рассчитывает показатель так: Индекс ведущих баз данных TOPDB основывается на анализе частоты поисковых запросов в Google, содержащих названия баз данных. Но какие базы данных наиболее популярны в мире по версии DB-Engines?

На первых трех строчках размещаются все те же СУБД. Лидирует Oracle (1321,73балла), на втором месте MySQL (1254,83балла), далее Microsoft SQL Server (1015баллов). Но начиная с четвертой строки рейтинг меняется: по версии DB-Engines четвертой самой популярной в мире СУБД стала PostgreSQL, которая набрала 549,29балла.

Рейтинг DB-Engines Топ 10 наиболее популярных баз данных март 2021года: Визуализация данных через платформу Flourish

Еще один интересный пример: в TOPDB Microsoft Access занимает четвертое место, но в рейтинге DB-Engines Access набирает 118,14балла. В десять раз меньше, чем Oracle. (Подробнее о том, как рассчитываются показатели БД в этом рейтинге, можно прочитать по ссылкеhttps://db-engines.com/en/ranking_definition.)

Самые быстрорастущие базы данных в прошлом году

Какие из 50 баз данных проявили себя лучше других в прошлом году, а какие не продемонстрировали блестящих результатов? Начнем с хорошего. Microsoft Azure SQL Database, PostgreSQL, Mongo DB и Snowflake показали высокий рост. Из них наибольший рост продемонстрировала СУБД Microsoft Azure (35,44%), а наименьший Snowflake (+20,77%). Показатели неплохо поднялись у Google BigQuery, Redis и Amazon DynamoDB. Среди них самый высокий рост наблюдался у BigQuery (+8,51%), а наименьший у Amazon DynamoDB (+6,38%).

Рейтинг DB-Engines Топ 50 наиболее популярных баз данных март 2021года: Визуализация данных через платформу Flourish

Наибольшую отрицательную динамику показали три базы данных: Microsoft SQL Server (82,55%), Oracle (18,91%) и Hive (9,34%). Однако некоторые из баз данных, показатели которых ухудшились по сравнению с показателями марта, по-прежнему занимают лидирующие позиции в общем рейтинге. Oracle, MySQL и Microsoft SQL самые популярные в мире базы данных в среднем потеряли по 35,55%.

Выше представлена интерактивная таблица рейтинга DB-Engines (ссылки на официальные данные можно найти здесь: https://db-engines.com/en/ranking). Вы можете посмотреть данные для разных столбцов.

Источники и полезные ссылки

Работая над этой статьей, я использовал несколько источников, в том числе рейтинги TOPDB и DB-Engines. Ссылки на источники указаны в статье.

Видео о самых популярных базах данных с 2006 по 2021год: https://youtu.be/thuG2PXVbBU

Статья о самых популярных игровых консолях: https://statisticsanddata.org/data/best-selling-consoles-in-history-1972-2021/


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

Подробнее..

Recovery mode Как ускорить сайт в 4 раза, просто перенастроив сервер

02.06.2021 12:04:43 | Автор: admin

Если вы работаете с сайтом, который постепенно растет, - увеличивается количество товаров, трафик с рекламы - то рано или поздно придется перейти в режим работы highload, высоких нагрузок на сервер. Но что делать, если ваш сайт не растет, а сервер все чаще не выдерживает, и происходит блокировка данных? Именно с этой проблемой мы столкнулись, дорабатывая сайт для интернет-магазина светового оборудования с ассортиментом более чем 100 000 товаров.

Исходная ситуация

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

Поиск проблемы

Мы провели аудит настроек сервера и сайта, разделив работы на два этапа: анализ back-end и front-end, и обнаружили низкую скорость загрузки страниц на back-ende - порядка 80 секунд на самых посещаемых страницах, что в итоге приводило к существенному снижению конверсии.

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

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

Решение

Шаг 1. Настройка баз данных

На первом этапе мы настроили базу данных MySQL без изменения систем хранения, исходя из доступных ресурсов и нагрузки проекта. Эти действия, в первую очередь, были направлены на оптимизацию потребления ресурсов оперативной памяти, что позволило избежать ухода сервера в SWAP, когда, исчерпав ресурсы оперативной памяти, сервер начинал работать из файла подкачки и замедлял работу сайта.

Шаг 2. Смена типа хранения на InnoDB

Почему мы выбрали InnoDB?

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

Главное преимущество InnoDB заключается в скорости работы при выполнении запроса к базе InnoDB происходит блокировка только строки, при выполнении же запроса к базе MyISAM блокируется вся таблица. Дело в том, что пока запрос не будет выполнен, никакие другие обращения к таблице/строке будут невозможны. А поскольку строки значительно меньше целых таблиц, InnoDB обрабатывает запросы быстрее.

Также была произведена оптимизация работы самой базы данных InnoDB. Например, были оптимизированы параметры:

# InnoDB parameters

innodb_file_per_table

innodb_flush_log_at_trx_commit

innodb_flush_method

innodb_buffer_pool_size

innodb_log_file_size

innodb_buffer_pool_instances

innodb_file_format

innodb_locks_unsafe_for_binlog

innodb_autoinc_lock_mode

transaction-isolation

innodb-data-file-path

innodb_log_buffer_size

innodb_io_capacity

innodb_io_capacity_max

innodb_checksum_algorithm

innodb_read_io_threads

innodb_write_io_threads

Промежуточные результаты

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

Это в свою очередь привело к уменьшению потребляемой оперативной памяти.

Шаг 3. Перенастройка Nginx и установка модулей кэширования brotli, pagespeed, proxy_buffering

Nginx позиционируется как простой, быстрый и надежный сервер, неперегруженный функциями. Уже длительное время Nginx обслуживает серверы многих высоконагруженных российских сайтов, например, Яндекс, Mail.Ru, ВКонтакте и Рамблер. Для улучшения производительности при использовании дополнительных серверов, Nginx поддерживает буферизацию (proxy_buffering) и кеширование (proxy_cache), чем мы и воспользовались.

Не обошлось и без курьезов настроек Nginx. У клиента был обычный интернет-магазин с товарами, тогда как настройки буферизации, которые мы обнаружили во время аудита, позволяли ему быть чуть ли ни стриминговым сервисом. Мы существенно уменьшили значения в параметре client_max_body_size, что в совокупности с перенастройкой Nginx еще больше снизило потребление памяти.

Шаг 4. Оптимизация настроек PHP-FPM и Memcache и отключение Apache

PHP-FPM нередко используется в паре с веб-сервером Nginx. Последний обрабатывает статические данные, а обработку скриптов отдает PHP-FPM. Такая реализация работает быстрее, чем распространенная модель Nginx + Apache.

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

Необходимым шагом стал перевод работы PHP-FPM на unix socket. Зачем это понадобилось? Nginx сам по себе довольно быстрый веб-сервер, однако самостоятельно он не может обрабатывать скрипты. Для этого необходим бэкенд в виде PHP-FPM. Чтобы вся эта связка работала без потери скорости, мы использовали unix socket способ подключения к PHP-FPM, позволяющий избегать сетевые запросы и дающий значительный прирост в скорости работы сайта.

Результаты работ

1. Время отклика главной страницы уменьшилось с 24 секунд до чуть более 3 секунд, внутренних до 5-8 сек.

2. Уменьшилось потребление серверных ресурсов.

3. Стабилизировалось поведение сервера - он перестал зависать.

4. Глубина просмотров увеличилась на 30%, и как следствие, это дало улучшение в SЕО, а также последующих продаж: растут поведенческие показатели => растут позиции сайта в выдаче => растет трафик => растут продажи.

5. Клиенту были даны рекомендации по оптимизации front-end части сайта для ускорения работы сайта. Например:

  • оптимизировать графики и настройку выдачи изображений в формате webp;

  • настроить lazyload-загрузки данных;

  • вынести все некритические для отображения страницы скрипты в конец страницы.

Вывод

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

Подробнее..

Перевод Перспективные архитектуры для современных инфраструктур данных

04.05.2021 18:23:50 | Автор: admin

На сегодняшний день базы данных класса Massive Parallel Processing это отраслевой стандарт для хранения Больших Данных и решения разнообразных аналитических задач на их основе.

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

Данный класс технологий необходимый элемент в инструментарии современного Data Engineer.

На демо-занятии мы подробно разберем, что же такое mpp-БД, какие решения есть сегодня на рынке и даже углубимся в практический пример использования одной их самых инновационных mpp-систем на сегодня: ClickHouse.

Приглашаем всех желающих присоединиться к участию в демо-уроке Введение в MPP-базы данных на примере ClickHouse.


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

Многие из самых быстрорастущих инфраструктурных стартапов сегодня создают продукты для управления данными. Эти системы позволяют принимать решения на основе данных (аналитические системы) и управлять продуктами на основе данных, в том числе с помощью машинного обучения (оперативные системы). Они варьируются от конвейеров, по которым передаются данные, до решений для их хранения, SQL-движков, которые анализируют данные, дашбордов для мониторинга, которые упрощают понимание данных от библиотек машинного обучения и data science до автоматизированных конвейеров данных, каталогов данных и т.д.

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

Инфраструктура данных включает

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

Стремительный рост рынка инфраструктуры данных

Одной из основных причин, из-за которых был составлен этот доклад, является стремительный рост инфраструктуры данных за последние несколько лет. По данным Gartner, расходы на инфраструктуру данных достигли в 2019 году рекордного показателя в 66 миллиардов долларов, что составляет 24% и эта цифра растет всех расходов на программное обеспечение для инфраструктуры. По данным Pitchbook, 30 крупнейших стартапов по созданию инфраструктуры данных за последние 5 лет привлекли более 8 миллиардов долларов венчурного капитала на общую сумму 35 миллиардов долларов.

Венчурный капитал, привлеченный наиболее показательными стартапами в области инфраструктуры данных в 2015-2020 гг.

Примечание: Любые инвестиции или портфельные компании, упомянутые или описанные в этой презентации, не являются репрезентативными для всего объема инвестиций во все инвестиционные каналы, управляемые a16z, и нет никаких гарантий, что эти инвестиции будут прибыльными или что другие инвестиции, сделанные в будущем, будут иметь аналогичные характеристики или результаты. Список инвестиций, сделанных фондами под управлением a16z, доступен здесь: https://a16z.com/investments/.

Гонка за данными также отражается на рынке труда. Аналитики данных, инженеры по обработке данных и инженеры по машинному обучению возглавили список самых быстрорастущих специальностей Linkedin в 2019 году. По данным NewVantage Partners 60% компаний из списка Fortune 1000 имеют директоров по обработке и анализу данных, по сравнению с 12% в 2012 году, и согласно исследованию роста и прибыльности McKinsey эти компании значительно опережают своих коллег.

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

Унифицированная архитектура инфраструктуры данных

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

Мы опросили практиков из ведущих организаций, работающих с данными: (а) как выглядели их внутренние технологические стеки, и (б) что бы они изменили, если бы им нужно было создавать новый с нуля.

Результатом этих обсуждений стала следующая диаграмма эталонной архитектуры:

Unified Architecture for Data Infrastructure

Унифицированная архитектура для инфраструктуры данных

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

Столбцы диаграммы определены следующим образом:

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

Остальная часть этой статьи посвящена разбору этой архитектуры и того, как она чаще всего реализуется на практике.

Аналитика, AI/ML и грядущая конвергенция?

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

Вокруг этих вариантов использования выросли две параллельные экосистемы. Основу аналитической экосистемы составляют хранилища данных (data warehouse). Большинство хранилищ данных хранят данные в структурированном формате и предназначены для быстрого и простого получения выводов на основе обработки основных бизнес-метрик, обычно с помощью SQL (хотя Python становится все более популярным). Озеро данных (data lake) является основой оперативной экосистемы. Сохраняя данные в необработанном виде, он обеспечивает гибкость, масштабируемость и производительность, необходимые для специализированных приложений и более сложных задач обработки данных. Озера данных работают на широком спектре языков, включая Java/Scala, Python, R и SQL.

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

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

Архитектурные сдвиги

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

Новые возможности

Формируется набор новых возможностей обработки данных, которые нуждаются в новых наборах инструментов и базовых систем. Многие из этих трендов создают новые категории технологий (и рынки) с нуля.

Схемы построения современной инфраструктуры данных

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

Здесь мы предоставим обзор трех обобщенных схем. Мы начнем схемы современной бизнес-аналитики, которая фокусируется на облачных хранилищах данных и аналитических вариантах использования. Во второй схеме мы рассматриваем мультимодальную обработку данных, охватывая как аналитические, так и оперативные варианты использования, построенные на основе озера данных. В окончательной схеме мы подробно рассмотрим оперативные системы и новые компоненты AI и ML стека.

Три обобщенных схемы

Схема 1: современная бизнес-аналитика

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

Перейдите сюда, чтобы просмотреть версию в высоком разрешении.

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

Основные варианты использования включают отчеты, дашборды и специальный анализ, в основном с использованием SQL (и некоторого количества Python) для анализа структурированных данных.

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

Схема 2: мультимодальная обработка данных

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

Перейдите сюда, чтобы просмотреть версию в высоком разрешении.

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

Сценарии использования включают в себя как бизнес-аналитику, так и более продвинутые функции, включая оперативный AI/ML, аналитику, чувствительную к потоковой передаче / задержке, крупномасштабные преобразования данных и обработку различных типов данных (включая текст, изображения и видео) с использованием целого набора языков (Java/Scala, Python, SQL).

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

Схема 3: Искусственный интеллект и машинное обучение.

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

Перейдите сюда, чтобы просмотреть версию в высоком разрешении.

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

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

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

Смотря в будущее

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

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


Перевод подготовлен в рамках онлайн-курса "Data Engineer".

Смотреть вебинар Введение в MPP-базы данных на примере ClickHouse.

Подробнее..

Организация бизнес-логики корпоративных приложений. Какие возможны варианты?

04.05.2021 10:05:07 | Автор: admin

Оригинал статьи находится по адресу

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

Три типовых решения при работе с бизнес-логикой по Фаулеру

С одной стороны сложно писать об организации бизнес-логики в приложении. Получается очень абстрактная статья. Благо есть книги, где затронута эта тема и даже есть примеры кода. Мартин Фаулер в книге "Шаблоны корпоративных приложений" выделял три основных типовых решения. Сценарий транзакции (Transaction Script), модуль таблицы (Table Module) и модель предметной области (Domain Model).Самый элементарный из них - это сценарий транзакции. Не будем их здесь обсуждать подробно - они очень хорошо описаны в первоисточнике с примерами. Приведем для дальнейших рассуждений лишь схему все из той же книги:

На этом графике показана _приблизительная_ зависимость между сложностью доменной логики и стоимостью реализации для трех видов типовых решений. Сразу бросается в глаза очевидная схожесть между тем как ведет себя сценарий транзакции и модуль таблицы. И совсем особняком стоит модель предметной области, которую применяют для сложной бизнес-логики. Как выбирать решение для вашего проекта? Очень просто, вы оцениваете насколько сложная будет бизнес-логика. Например расчет скидочной программы для клиентов. Какое типовое решение выбрать? Обычно при расчете скидок пользователи могут быть в разных категориях скидочной программы, в зависимости от категории можно получать скидки на разные группы товаров, причем товары могут входить в иерархические группы. Категории скидок распространяются на категории товаров. А еще есть число посещений заведения за заданный период. Периоды с разными характеристиками, заведения в сети заведений - различаются и т.д. и т.п. Если представить код - это приложение, в котором большое число классов с разнообразными свойствами и большое число связей между этими классами. А также разнообразные стратегии, которые оперируют всеми этими сущностями. В таком случае ответ очевиден - проектирование с использованием модели предметной области позволит вам совладать со всеми сложностями. Другой пример - у вас простое приложение, которое хранит свои данные в 3-х таблицах и никаких особенных операций с ними не делает. Рассылка сообщений по почте - список почтовых ящиков и список отправленных писем. Здесь нет смысла тащить какой-то сложный фреймворк. Простое приложение должно оставаться простым и тут лучше выбрать модуль таблицы или даже сценарий транзакции. В зависимости от того на какой платформе вы собираетесь разрабатывать.

Сколько типовых решений на самом деле?

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

Понятно, что речь не идет о чистой реализации той или иной парадигмы. Всегда есть компромиссы, вызванные ограничениями используемых технологий. Если вы пишете на C# или Java, объектно-ориентированных языках по своей природе, то это совсем не значит, что ваш код автоматически становится объектно-ориентированным. Всю программу вполне могут поместить в один единственный класс, объявить все методы статическими и использовать их из любого фрагмента кода. Таким образом в каждом конкретном приложении будет своя кривая. Нужно лишь понять к какой из этих двух категорий она ближе.

Как влияют фреймворки и инструменты разработки на кривую стоимости?

Сразу обозначим, что в качестве хранилища данных может выступать не только реляционная СУБД, но и NoSQL хранилище,NewSQL и даже обычные файлы, сериализованные в json, бинарный формат и т.п. Мы смотрим на ситуацию в комплексе. Если говорить про работу с обычными SQL хранилищами, здесь также огромный выбор. Вы можете писать простые запросы, писать хранимые процедуры, можете использовать ORM, использовать Code First, либо DB First подходы - все это в конечном счете сказывается на стиле в котором написана бизнес логика. В большей степени процедурном, либо в большей степени объектно-ориентированном. Ниже я примерно обозначил свое мнение о популярных схемах работы с БД.

Проблема в том, что используемые средства накладывают ограничения, которые не позволят вам реализовать тот или иной подход в полной мере. Например, с помощью Dapper не удобно работать со сложными ассоциациями внутри доменных сущностей. А при использовании ORM уровня Entity Framework вы добавляете код для отображения сущностей на таблицы. Если говорить о NoSQL СУБД, для примера Neo4j, то там очень выразительный и мощный для своих задач язык. Но опять же это приведет к использованию процедурной парадигмы.

Насколько легко сменить выбранное решение?

Давайте попробуем представить ситуацию, где мы решили кардинально изменить схему работы с хранилищем данных. С чем мы можем в таком случае столкнуться? До этого мы обсудили два важнейших аспекта - сложность кода и стоимость его сопровождения. Но на практике этого оказывается мало. Есть еще как минимум вопрос производительности - создаваемое приложение должно быть быстрым. И это сказывается на стиле написания кода. Чем жестче требования производительности, тем более процедурный код мы получаем на выходе. В каком-то экстремальном случае это может быть сервис или приложение, написанное с использованием полностью SQL, где вся логика скрыта в хитрообразных джойнах, оконных функция и обобщенных табличных выражениях. Работает быстро, но перевести его на ORM уже не так просто - все равно что переписать с нуля. Развитие такого продукта также может столкнуться с сложностями, учитывая график выше и процедурный стиль. Еще один вопрос - консистентность данных. Например, реляционные СУБД предоставляют очень богатые возможности по работе с транзакциями. Разобравшись с ними один раз - можно легко писать код, где вы точно знаете какие данные увидит пользователь, какие сможет изменить. С другой стороны, если вы пользуетесь ORM и выносите всю вашу бизнес-логику в классы работать в терминах транзакций становится сложнее. Обычно происходит реорганизация структуры таблиц и даже бизнес-сценариев таким образом, что они начинают работать в стиле согласованности данных в конечном счете (eventual consistency). Очевидно, что это также затрудняет перевод с одной схемы на другую, если вы заранее не заложили такую возможность. Компетенцию команды также не следует сбрасывать со счетов. Часто разработчики знают хорошо либо SQL, либо ORM и при переходе можно неожиданно столкнуться с проблемами.

Выводы

Из всего, что мы обсудили можно сделать выводы:

  • При проектировании сервиса с нуля лучше сразу прогнозировать дальнейшее развитие этого сервиса и выбирать наиболее подходящее типовое решение.

  • Если дальнейшее развитие для сервиса не очевидно, то лучше сразу позаботиться о возможной смене парадигме. Например, предпочитая eventual consistency. При добавлении нового функционала всегда держать в уме возможность смены решения.

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

Какие еще выводы вы могли бы предложить? Оставляйте ваши комментарии, давайте обсудим.

Подробнее..

Портфолио в 200 сайтов. Как я, будучи студентом, создал IT-компанию с десятками сотрудников

25.05.2021 08:18:19 | Автор: admin

От подработок на 500 долларов до контрактов на миллионы. Подробная история бизнеса с нуля со взлетами и падениями

На днях я зафиксировал круглую дату 15 лет с момента регистрации моей первой IT-компании (было это25 апреля 2006 года) так что захотелось немного поделиться опытом, да и, чего греха таить, слегка предаться ностальгии. Опыт этот, думаю, будет многим интересен как создать бизнеса с нуля, как его развить, как пережить кризис (а мировой финансовый кризис 2009 года можно поставить ближе всего к удару пандемии по экономике в наши дни), вновь возродить компанию и найти новые ниши для развития, создавая свой уникальный продукт.

* * *

Как и все советские дети, я мечтал стать космонавтом. Но уже с 6 лет, когда в нашей семье появился первый компьютер, я четко понял, что хочу быть программистом. В Беларуси в советское время было создано много высокотехнологичных производств (Интеграл, Эвистор, Витязь и т.д.), на одном из них в Витебске работали инженерами и мои родители.

После окончания в 2002-м году школы самым очевидным выбором было поступление в один из вузов Минска, потому что это было и ближе, и проще. Много моих друзей поступило на факультет прикладной математики Белорусского государственного университета. Но я присматривался и к известным питерским вузам, например, Институту точной механики и оптики (сейчас Университет информационных технологий, механики и оптики), и к московским.

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

Как заработать на обучение и новый компьютер (2002 2004)

В Бауманку я не добрал на экзамене немного по баллам, поэтому пошел в Московский энергетический институт (МЭИ), когда-то возникший как один из технических вузов при Бауманке, а конкретно в созданный как структурное подразделение МЭИ в том году Институт автоматики и вычислительной техники.

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

Фото, немного забегая вперед по времени, но для заглавного в статье самое оноФото, немного забегая вперед по времени, но для заглавного в статье самое оно

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

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

Какое-то время я проработал упаковщиком на складе рядом с общагой, но все это были разовые заработки. Помню, как довольно долго копил деньги на тройник-удлинитель для компьютера, который мне еще одна важная помощь в сентябре 2002 года привезли из Витебска родители. Компьютер гордо стоял в пустой комнатке-коробке с минимумом старой мебели (первую ночь я вообще спал на матрасе, лежавшем на полу) и висевшей на потолке лампочкой Ильича, именно так выглядело тогда общежитие МЭИ, одного из крупнейших технических вузов страны...

Оказалось, уже тогда многим был нужен разный софт, и я начал его разрабатывать на компьютере в общаге. Писал программы, разрабатывал базы данных сначала для знакомых, потом для знакомых знакомых, через которых потом приходили другие заказчики, посерьезнее. Тут начали появляться нормальные деньги так, за написанную для автостроительной компании базу данных мне заплатили 500 долларов.

Я за 21-дюймовым монитором, купленным с одного из первых гонораров.Я за 21-дюймовым монитором, купленным с одного из первых гонораров.

Еще одним перспективным клиентом оказалась туристическая компания Erasmo Viaggi. Сейчас мало кто помнит, но в начале нулевых никаких сайтов не было, туры предлагались клиентам в офисах в виде печатных каталогов. Я сделал Erasmo Viaggi базу, куда сразу загружались все данные по отелям, что позволило компании серьезно сэкономить, потому что до этого базу в виде таблиц в Excel вели несколько специально обученных сотрудников. На полученный за это гонорар я купил компьютер на базе процессора AMD (очень мощный для 2004-го там было то ли 2, то ли 4 гигабайта оперативной памяти, уже сейчас точно не помню) и 21-дюймовый монитор. Монитор, походивший на огромный громоздкий телевизор (плоских тогда было очень мало), мы с трудом довезли до общаги, где я гордо водрузил его на стол.

Создание Art of Web. Взлет и жесткое приземление (2004 2008)

Этот момент оказался переломным. Я захотел больше денег, поэтому начал работать с большим количеством заказчиков. Находили клиентов мы тогда просто обзванивали турагентов (они были особенно платежеспособны в то время, когда в России резко вырос спрос на отдых за границей) по списку в какой-нибудь газете, предлагали создание баз данных. В итоге, нашими клиентами стало около 20 туристических компаний, от мелких до крупных (вот только по заказчикам сайтов перечислюLeif,Бумеранг-М,Let's Travel,НГС Курорт,Бонжур,Forte Tour,Метекс-Тури т.д.).

Я говорю мы и нашими, потому что в 2004 году я со своими соседями по общежитию Максимом Зыряновым, который с 2000 года уже занимался созданием сайтов, и Сергеем Поповым создал компанию Art of Web. В 2005 году мы запустили сайт artofweb.ru (работающий по сей день), а в 2006-м уже официально зарегистрировали нашу компанию.

Учредительное собрание" Art of Web. Февраль 2006 года, поселок Мосрентген.Учредительное собрание" Art of Web. Февраль 2006 года, поселок Мосрентген.

Первыми нашими сотрудниками стали студенты первых курсов из той же общаги МЭИ. Мы обучали всех желающих, платили вполне нормальные деньги по тем временам (1000 2000 рублей в месяц, этого вполне хватало на жизнь при тогдашних низких ценах на продукты и товары; я сам жил в 2002-м году на такие деньги и представляю, о чем говорю). Офиса у нашей компании сначала не было, все работали в общаге на своих компьютерах (их наличие было входным билетом для работы в Art of Web), иногда посменно.

Мы делали сайты под заказ, разрабатывали логотипы и фирменную символику для компаний. Примеры наших тогдашних работ можно посмотреть впортфолио компании. Но таких заказов сначала было немного, первое время основной доход давал перенос на сайты компаний, торговавших на Горбушке, тысяч наименований товара из печатных каталогов. Перенос одной страницы стоил 20 30 рублей. Вообще же, брались мы тогда за любую работу, например, в 2006-м тянули оптоволоконные сети в подмосковном Воскресенске.

Предвосхищая образ Раста Коула.Предвосхищая образ Раста Коула.

В феврале 2006 года у Art of Web наконец-то появился свой офис, правда, в области, а именно в поселке Мосрентген. Правда, офис это громко сказано для тех, кто привык сейчас к большим лофтам или опенспейсам. Небольшая комната в старом складском здании, где можно было поставить вплотную друг к другу несколько столов и много-много компьютеров. От Москвы надо было ехать на автобусе, а потом час топать по грязи от остановки. Мы, кстати, освещали все это на странице компании в Facebook, можно найти там и эти ужевинрарные фото 15-летней давности. Но приглашать клиентов туда было сложно, даже не все сотрудники туда добирались.

В октябре 2006 года мыперебрались в другой офис, тоже в Подмосковье в поселке Газопровод, куда добраться было же полегче на маршрутке от метро Теплый Стан. Тут уже было помещение побольше пара комнат. И, наконец, в марте 2007 года мы переехали в высотный бизнес-центр на Новодмитровской улице, где сидели на верхнем 20-м этаже как разза второй буквой ов огромной светящейся вывеске Молодая гвардия. Здесь был уже достаточно большой офис, куда можно было приглашать клиентов.

Во втором офисе Art of Web. Октябрь 2006 года, поселок Газопровод.Во втором офисе Art of Web. Октябрь 2006 года, поселок Газопровод.

Экономика в России росла, наша компания развивалась, к лету 2008-го у нас было уже около 20 сотрудников, значительная часть которых по-прежнему была студентами из общаги МЭИ. Мы набрали очень много заказов на создание сайтов, что-то порядка 50-ти. Причем заказчиками были уже крупные компании. Некоторые контракты на сайты достигали 300 тысяч рублей (например, заказ на создание интернет-представительства холдингаСтабком Голд, предлагавшего сложное шведское оборудование для золотодобывающих компаний), хотя чаще всего суммы колебались в размере 30 50 тысяч рублей. Опять-таки, напомню, это 2008 год и доллар тогда был по 30, а зарплата квалифицированных офисных работников по Москве составляла 40 50 тысяч рублей.

И тут ударяет мировой финансовый кризис. Большинство наших клиентов просто разорились, платить им было нечем (включая рекордсменов по объему заказов в лице Стабком Голд иДиалогЭлектро). Я помню один из последних дней декабря 2008-го, когда вышел к сотрудникам и сказал ребята, денег нет. И пообещал, что лично закрою все долги перед каждым. Хотя абсолютно не представлял, где найти на это деньги.

Достаем билеты на Олимпийские игры

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

Мой партнер Максим Зырянов в расстроенных чувствах. Правда, фото сделано чуть раньше, при переезде в наш офис в "Молодой гвардии" в марте 2007 года.Мой партнер Максим Зырянов в расстроенных чувствах. Правда, фото сделано чуть раньше, при переезде в наш офис в "Молодой гвардии" в марте 2007 года.

Нашу компанию кризис тоже практически уничтожил. Мой друг и соучредитель Art of Web Максим Зырянов вышел из нашего бизнеса, и упрекать его в этом было сложно на фоне отсутствия денег у компаний-заказчиков перспективы создания и продаж сайтов выглядели туманными, к тому же из-за невыплаты зарплат наши сотрудники отказывались дальше работать.

Не было денег не фигурально, а на уровне от слова совсем. Я помню, как купил в магазине доску для резки овощей за 200 рублей, а моя девушка, увидев ее, расплакалась зачем ты ее купил, ведь у нас не осталось денег на еду? Все было очень жестко.

Как удалось выйти из этой ситуации?

Еще во время работы с Erasmo Viaggi я познакомился с ныне покойнымМихаилом Нехаевым, очень влиятельной фигуройв сфере торговли билетами. Я делал для него автоматизацию системы по учету билетов на зимнюю Олимпиаду в Турине, проходившую в феврале 2006 года. Это была такая как следует доработанная нами база данных в Access, где учитывалось все, от того, у кого и где купить билеты, до того, кому и где их продать.

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

Позже я делал для Михаила автоматизацию системы по учету билетов, которые он продавал на летнюю Олимпиаду в Пекине в августе 2008 года. Заработали мы тогда оба вполне прилично. Когда же в 2009 году Art of Web был уничтожен финансовым кризисом, я решил сам продавать билеты. Зарегистрировал компанию Достаем.ру,сделали раскрутил за пару недель сайт, получил у Михаила квоту на продажу билетов на зимнюю Олимпиаду в Ванкувере, которая должна была пройти в феврале 2010 года.

Это уже благополучный 2012 год, я занимаюсь продажей билетов на Олимпиаду в Лондоне и даже попал в сюжет "России-2" на эту тему.

Май 2009-го, я сижу в офисе (немного громко сказано как будет видно далее, небольшая комнатка в старом здании в центре), раздается звонок. Звонит какой-то мужчина (как позже оказалось, это был крупный бизнесмен, с которым я потом подружился; фамилию в силу ряда обстоятельств называть не стану, хотя бизнес его абсолютно легален), который хочет билеты на двоих, на лучшие места. Параллельно с разговором заполняю в Excel заявку, и у меня получается сумма из названных Михаилом цифр 115,6 тысяч долларов. Я не знаю, что ответить, потому что просто не могу выговорить клиенту эту нереально огромную для меня цифру. Говорю, что пришлю на электронную почту предложение. Отправил. Потом дома ночью перед зеркалом долго тренировался уверенно произносить Сто тысяч долларов.

И вот на следующий день я сижу в офисе, снова звонит вчерашний клиент на 100 тысяч долларов. Я сначала не решился взять трубку, думаю, он меня просто пошлет, услышав такую сумму. Нет, он говорит Меня все устраивает. Куда к вам подъехать?. Я называю адрес Покровка, 17. И вскоре открывается старая покосившаяся деревянная дверь в мой офис, заходит мужчина (помощник того бизнесмена), у которого одни ботинки стоят дороже, чем все находившееся у нас в офисе. Он с удивлением осматривает мой обшарпанный офис размером два на шесть метров, где поперек стоит пара столов (за одним за своим огромным телевизором сижу я, за другим один из моих сотрудников). Он говорит Минуту, ребята и выходит за дверь. А поскольку дверь покосившаяся, мы слышим через щель весь разговор.

Он говорит: Сергей Викторович, что-то тут не то. Дальше раздается звонок уже у меня, заказчик спрашивает: У вас все в порядке? Я отвечаю да, конечно. Затем слышу, как звонит телефон в коридоре, помощник говорит Да, да, Сергей Викторович. Заходит в офис и выкладывает на стол предоплату, 50% от суммы. Я с застывшим выражением на лице беру деньги, пишу расписку на листе бумаги в клетку, вырванном из блокнота.

Потом, когда я уже отвез деньги Михаилу, я узнал свою комиссию она составила с этой сделки около 10 тысяч долларов. Первое, что я сделал купил годовой абонемент в метро, чтобы разом закрыть вопрос с поездками. Затем сразу же раздал все долги сотрудникам Art of Web. И осталось еще на путевку в Египет на двоих за 49 тысяч рублей. Мой первый в жизни отдых

Наша возрожденная компания на корпоративе, причем тут даже не все работавшие в Art of Web на тот момент сотрудники. Март 2012 года.Наша возрожденная компания на корпоративе, причем тут даже не все работавшие в Art of Web на тот момент сотрудники. Март 2012 года.

Так удалось возродить Art of Web. Сняли новый офис на той же Покровке 17, туда начали возвращаться сотрудники (кстати, можно посмотреть в нашем портфолио по фамилиям разработчиков, что многие работали у нас и до 2008-го, и после). Кризис потихоньку начал проходить, так что начали приходить заказы. В том же 2009-м мы открыли второй офис в Минске, который возглавил один из моих старых друзей. Позже наш белорусский офис, выросший с нескольких сотрудников до нескольких десятков (на пике осенью 2012-го, когда мы делали игры, занимались SMM и прочим до 35-ти человек, в московском тогда же около 15-ти), переехал в Витебск.

В дальнейшем, опять же по линии продаж билетов, я наладил связи с Олимпийским комитетом России, а через него вышел на целый ряд спортивных организаций. Так в 2011 2015 годах в нашем портфолио появились сайтыФедерации спортивной борьбы РоссиииМеждународной федерации любительской борьбы,Спортивного общества СпартакиВсероссийской ассоциации акробатического рок-н-ролла,ФК АнжииФК Томь,разработка фирменного стиля для ФК Калугаи т.д. Но это лишь отдельные примеры из 200 разработанных Art of Web сайтов, где есть сайты брутальных барбершопов и элитных салонов красоты, торговых компаний и федеральных министерств, ярмарок и конкурсов красоты, банков и благотворительных фондов, и многого другого. Работа кипела.

Причем, мы не просто разрабатывали сайты, но и делалимобильные приложения для болельщиков, интегрируя их с сайтами, разрабатывалисистемы учета спортивных показателей,интернет-магазиныи т.д. В полном комплексе мы реализовали это с Анжи, которому в 2011-м купивший этот футбольный клуб миллиардер Сулейман Керимов дал новую историю с бюджетами и известными зарубежными игроками. Тут речь шла уже о миллионных бюджетах.

Приходилось решать задачи от построения IT инфраструктуры, до разработки стратегии развития целого вида спорта, от привлечения спонсоров до защиты интересов вида спорта в IOC [International Olympic Committee], подводиля итоги 10-летней работы по спорту в 2018-м, когда уже читал лекции в бизнес-школе RMA, делясь своим опытом. За моими плечами организация и подготовка IT к участию и проведению Олимпийских игр и чемпионатов мира, как со стороны федераций, так и со стороны министерства спорта.

Пейнтбольные войны и Crystal Rain

Помимо сайтов, мы занималисьиграми для ВКонтакте. В одну из них, уверен, играли практически все Пейнтбольные войны. За первый месяц после запуска в 2010-м она собрала 250 тысяч подписчиков. Доступ к игре был бесплатным, а монетизация шла за счет приобретения игроками различного инвентаря. И заработали мы на этом достаточно серьезные деньги.

Вели соцсети для заказчиков, например, группы Федерации спортивной борьбы России и ФК Анжи, федеральных целевых программ при Министерстве спорта России и т.д. Звучит вроде бы как несложная работа, но на практике мы создавали и размещали уникальный фото- и видеоконтент, взаимодействовали с аудиторией и т.д. И стоило это хороших денег. Те же федеральные целевые программы стабильно приносили тысяч 300 в месяц.

Так что в 2010 2011 годах не то, что закрыли все долги, а появились даже свободные средства, которые я начал вкладывать в новые проекты. Например, в 2013-м у нас вышло мобильное приложение Avatar LIVE,в одной из версий которогоголосом Ивана Охлобыстина из фильма Соловей-разбойник (2012 год) озвучивался звонок будильника или выдавались в случайном порядке какие-то забавные фразы героя.

Также я тогда взял непосильную для себя задачу разработки большого игрового проекта Crystal Rain. Проект был очень технологичным и очень крутым, что-то вроде будущего хита про покемонов с дополненной реальностью. Игра, по сути, была уже готова в апреле 2013 года. Немного не хватило денег (проект поглотил 8 млн. рублей, но надо было больше) и, главное, опыта в подобных проектах, а так бы эта игра и сейчас приносила бы десятки миллионов долларов. Осколки Crystal Rain в виде обзоров и демок до сих пор можно найти в Сети. Но, так или иначе, я собрал тогда очень сильную команду разработчиков, которая сейчас работает со мной.

Было еще несколько проектов, в которых я участвовал единаясистема залоговых аукционов, Центр Петрова по обучению иностранным языкам, разработка платежных приложений для банков и т.д. Некоторых из них выстрелили, причем очень мощно, вследствие чего я перенес свое внимание в новую сферу бизнеса, некоторые нет.

И да, если кто-то сочтет весь рассказ выше рекламой. На сегодняшний день Art of Web уже не берет новых клиентов, у компании даже не обновляется сайт на протяжении многих лет, и фокус деятельности компании,перерегистрированной в 2016 году, направлен на внутренние проекты и исследования технологий. Достаем.ру перестал работать лет 5 назад, последним проектом по продаже билетов была летняя Олимпиада 2016 года в Рио-де-Жанейро и Чемпионат Европы по футболу 2016 года.

Офис Art of Web. Наши дни, "Москва Сити"...Офис Art of Web. Наши дни, "Москва Сити"...

Дело в том, что с 2014 года я сосредоточился на другом приоритетном бизнесе m4bank.ru, сначала в формате разработки мобильных приложений для банков и mPOS, а затем производстве собственных платежных устройств. Об этом я расскажу в следующих статьях.

Не продавайте душу

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

Сервисная модель это неплохо, пока делаешь первые шаги и зарабатываешь первые деньги, но уже к 2014 году мне стало ясно, что предпочтительным является создание собственных продуктов. По этой причине вместе с партнёрами я решил бросить все силы на проект m4bank.ru (Центр корпоративных технологий), превратившийся вскоре в крупнейшего в СНГ поставщика smart POS терминалов.

Продолжение следует.

Подробнее..

Категории

Последние комментарии

  • Имя: Murshin
    13.06.2024 | 14:01
    Нейросеть-это мозг вселенной.Если к ней подключиться,то можно получить все знания,накопленные Вселенной,но этому препятствуют аннуннаки.Аннуннаки нас от неё отгородили,установив в головах барьер. Подр Подробнее..
  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru