Разное

Шаржирование базы данных: Масштабирование базы данных через шардирование и партиционирование / Блог компании Конференции Олега Бунина (Онтико) / Хабр

Содержание

Масштабирование базы данных через шардирование и партиционирование / Блог компании Конференции Олега Бунина (Онтико) / Хабр

Денис Иванов (2ГИС)

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

Немного расскажу о себе — я работаю в команде WebAPI в компании 2GIS, мы предоставляем API для организаций, у нас очень много разных данных, 8 стран, в которых мы работаем, 250 крупных городов, 50 тыс. населенных пунктов. У нас достаточно большая нагрузка — 25 млн. активных пользователей в месяц, и в среднем нагрузка около 2000 RPS идет на API. Все это располагается в трех датацентрах.

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


Я в большей степени расскажу про шардинг. Он бывает вертикальным и горизонтальным. Также бывает такой способ масштабирования как репликация. Доклад «Как устроена MySQL репликация» Андрея Аксенова из Sphinx про это и был. Я эту тему практически не буду освещать.

Перейдем подробнее к теме партицирования (вертикальный шардинг). Как это все выглядит?

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

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

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

Про репликацию я не буду останавливаться, тут все очень просто.

Перейдем глубже к этой теме, и я расскажу практически все о партицировании на примере Postgres’а.

Давайте рассмотрим простую табличку, наверняка, практически в 99% проектов такая табличка есть — это новости.

У новости есть идентификатор, есть категория, в которой эта новость расположена, есть автор новости, ее рейтинг и какой-то заголовок — совершенно стандартная таблица, ничего сложного нет.

Как же эту таблицу разделить на несколько? С чего начать?

Всего нужно будет сделать 2 действия над табличкой — это поставить у нашего шарда, например, news_1, то, что она будет наследоваться таблицей news. News будет базовой таблицей, будет содержать всю структуру, и мы, создавая партицию, будем указывать, что она наследуется нашей базовой таблицей. Наследованная таблица будет иметь все колонки родителя — той базовой таблицы, которую мы указали, а также она может иметь свои колонки, которые мы дополнительно туда добавим. Она будет полноценной таблицей, но унаследованной от родителя, и там не будет ограничений, индексов и триггеров от родителя — это очень важно. Если вы на базовой таблице насоздаете индексы и унаследуете ее, то в унаследованной таблице индексов, ограничений и триггеров не будет.

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

В данном случае признак — это category_id=1, т.е. только записи с category_id=1 будут попадать в эту таблицу.

Какие типы проверок бывают для партицированных таблиц?

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

Тут нужно подробнее остановиться, потому что проверка поддерживает оператор BETWEEN, наверняка вы все его знаете.

И так просто его сделать можно. Но нельзя. Можно сделать, потому что нам разрешат такое сделать, PostgreSQL поддерживает такое. Как вы видите, у нас в 1-ую партицию попадают данные между 100 и 200, а во 2-ую — между 200 и 300. В какую из этих партиций попадет запись с рейтингом 200? Не известно, как повезет. Поэтому так делать нельзя, нужно указывать строгое значение, т.е. строго в 1-ую партицию будут попадать значения больше 100 и меньше либо равно 200, и во вторую больше 200, но не 200, и меньше либо равно 300.

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

Также не стоит создавать партиции по разным полям, т.е. что в 1-ую партицию у нас будут попадать записи с category_id=1, а во 2-ую — с рейтингом 100.

Опять же, если нам придет такая запись, в которой category_id = 1 и рейтинг =100, то неизвестно в какую из партиций попадет эта запись. Партицировать стоит по одному признаку, по какому-то одному полю — это очень важно.

Давайте рассмотрим нашу партицию целиком:

Ваша партицированная таблица будет выглядеть вот так, т.е. это таблица news_1 с признаком, что туда будут попадать записи только с category_id = 1, и эта таблица будет унаследована от базовой таблицы news — все очень просто.

Мы на базовую таблицу должны добавить некоторое правило, чтобы, когда мы будем работать с нашей основной таблицей news, вставка на запись с category_id = 1 попала именно в ту партицию, а не в основную. Мы указываем простое правило, называем его как хотим, говорим, что когда данные будут вставляться в news с category_id = 1, вместо этого будем вставлять данные в news_1. Тут тоже все очень просто: по шаблончику оно все меняется и будет замечательно работать. Это правило создается на базовой таблице.

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

Давайте рассмотрим пример вставки данных:

Данные будем вставлять как обычно, будто у нас обычная большая толстая таблица, т.е. мы вставляем запись с category_id=1 с category_id=2, можем даже вставить данные с category_id=3.

Вот мы выбираем данные, у нас они все есть:

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

Мы также можем сделать соответствующие запросы в определенные партиции, указывая наше условие, т.е.category_id = 1, или вхождение в числа (2, 3).

Все будет замечательно работать, все данные будут выбираться. Опять же, несмотря на то, что с партиции с category_id=3 у нас нет.

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

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

Здесь можно, как видно на слайде, вставлять данные напрямую в партицию. Можно вставлять данные с помощью правил в основную таблицу, но можно и в саму партицию.

Если мы будем вставлять данные в партицию с каким-то чужеродным условием, например, с category_id = 4, то мы получим ошибку «сюда такие данные нельзя вставлять» — это тоже очень удобно — мы просто будем класть данные только в те партиции, которые нам действительно нужно, и если у нас что-то пойдет не так, мы на уровне базы все это отловим.

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

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

У нас будет Seq Scan по всей таблице целиком, потому что туда данные могут все равно попадать, и будет скан по партиции. Если мы будем указывать условия нескольких категорий, то он будет сканировать только те таблицы, на которые есть условия. Он не будет смотреть в остальные партиции. Так работает оптимизатор — это правильно, и так действительно быстрее.

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

Это будет обычная таблица, просто Seq Scan по ней, ничего сверхъестественного. Точно так же будут работать update’ы и delete’ы. Мы можем update’тить основную таблицу, можем также update’ы слать напрямую в партиции. Так же и delete’ы будут работать. На них нужно так же соответствующие правила создать, как мы создавали с insert’ом, только вместо insert написать update или delete.

Перейдем к такой вещи как Index’ы

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

Как мы с этой проблемой боролись у себя. Мы создали замечательную утилиту PartitionMagic, которая позволяет автоматически управлять партициями и не заморачиваться с созданием индексов, триггеров с несуществующими партициями, с какими-то бяками, которые могут происходить. Эта утилита open source’ная, ниже будет ссылка. Мы эту утилиту в виде хранимой процедуры добавляем к нам в базу, она там лежит, не требует дополнительных extension’ов, никаких расширений, ничего пересобирать не нужно, т.е. мы берем PostgreSQL, обычную процедуру, запихиваем в базу и с ней работаем.

Вот та же самая таблица, которую мы рассматривали, ничего нового, все то же самое.

Как же нам запартицировать ее? А просто вот так:

Мы вызываем процедуру, указываем, что таблица будет news, и партицировать будем по category_id. И все дальше будет само работать, нам больше ничего не нужно делать. Мы так же вставляем данные.

У нас тут три записи с category_id =1, две записи с category_id=2, и одна с category_id=3.

После вставки данные автоматически попадут в нужные партиции, мы можем сделать селекты.

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

Какие мы получаем за счет этого преимущества:

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

Мы получаем действительно большое преимущество в этом. Вот ссылочка https://github.com/2gis/partition_magic. На этом первая часть доклада закончена. Мы научились партицировать данные. Напомню, что партицирование применяется на одном инстансе — это тот же самый инстанс базы, где у вас лежала бы большая толстая таблица, но мы ее раздробили на мелкие части. Мы можем совершенно не менять наше приложение — оно точно так же будет работать с основной таблицей — вставляем туда данные, редактируем, удаляем. Так же все работает, но работает быстрее. Приблизительно, в среднем, в 3-4 раза быстрее.

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

Рассматривать будем такую же структуру с двумя шардами — news_1 и news_2, но это будут разные инстансы, третьим инстансом будет основная база, с которой мы будем работать:

Та же самая таблица:

Единственное, что туда нужно добавить, это CONSTRAINT CHECK, того, что записи будут выпадать только с category_id=1. Так же, как в предыдущем примере, но это не унаследованная таблица, это будет таблица с шардом, которую мы делаем на сервере, который будет выступать шардом с category_id=1. Это нужно запомнить. Единственное, что нужно сделать — это добавить CONSTRAINT.

Мы еще можем дополнительно создать индекс по category_id:

Несмотря на то, что у нас стоит check — проверка, PostgreSQL все равно обращается в этот шард, и шард может очень надолго задуматься, потому что данных может быть очень много, а в случае с индексом он быстро ответит, потому что в индексе ничего нет по такому запросу, поэтому его лучше добавить.

Как настроить шардинг на основном сервере?

Мы подключаем EXTENSION. EXTENSION идет в Postgres’e из коробки, делается это командой CREATE EXTENSION, называется он postgres_fdw, расшифровывается как foreign data wrapper.

Далее нам нужно завести удаленный сервер, подключить его к основному, мы называем его как угодно, указываем, что этот сервер будет использовать foreign data wrapper, который мы указали.

Таким же образом можно использовать для шарда MySql, Oracle, Mongo… Foreign data wrapper есть для очень многих баз данных, т.е. можно отдельные шарды хранить в разных базах.

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

Далее мы создаем маппинг для пользователя — по этим данным основной сервер будет авторизироваться к дочернему. Мы указываем, что для сервера news_1 будет пользователь postgres, с паролем postgres. И на основную базу данных он будет маппиться как наш user postgres.

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

Далее мы заводим табличку на основном сервере:

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

Схема по дефолту — это public, таблицу, которую мы завели, назвали news. Точно так же мы подключаем 2-ую таблицу к основному серверу, т.е. добавляем сервер, добавляем маппинг, создаем таблицу. Все, что осталось — это завести нашу основную таблицу.

Это делается с помощью VIEW, через представление, мы с помощью UNION ALL склеиваем запросы из удаленных таблиц и получаем одну большую толстую таблицу news из удаленных серверов.

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

Мы заводим основное правило, которое будет срабатывать, если ни одна проверка не сработала, чтобы не происходило ничего. Т.е. мы указываем DO INSTEAD NOTHING и заводим такие же проверки, как мы делали ранее, но только с указанием нашего условия, т.е. category_id=1 и таблицу, в которую данные вместо этого будут попадать.

Т.е. единственное отличие — это в category_id мы будем указывать имя таблицы. Также посмотрим на вставку данных.

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

Выбираем данные

Обратите внимание на сортировку идентификаторов — у нас сначала выводятся все записи из первого шарда, затем из второго. Это происходит из-за того, что postgres ходит по VIEW последовательно. У нас указаны select’ы через UNION ALL, и он именно так исполняет — посылает запросы на удаленные машины, собирает эти данные и склеивает, и они будут отсортированы по тому принципу, по которому мы эту VIEW создали, по которому тот сервер отдал данные.

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

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

Посмотрим на explain.

У нас foreign scan по news_1 и foreign scan по news_2, так же, как было с партицированием, только вместо Seq Scan-а у нас foreign scan — это удаленный скан, который выполняется на другом сервере.

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

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

Контакты

Блог компании 2ГИС

Шардирование баз данных — Национальная библиотека им. Н. Э. Баумана

Материал из Национальной библиотеки им. Н. Э. Баумана
Последнее изменение этой страницы: 17:11, 9 июня 2017.

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

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

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

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

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

Критерий шардинга — какой-то параметр, который позволит определять, на каком именно сервере лежат те или иные данные.

  • ID поля таблицы
  • Хеш-таблица с соответствиями «пользователь=сервер» (Тогда, при определении сервера, нужно будет выбрать сервер из этой таблицы. В этом случае узкое место — это большая таблица соответсвия, которую нужно хранить в одном месте.)
  • Определять имя сервера с помощью числового (буквенного) преобразования. (Например, можно вычислять номер сервера, как остаток от деления на определенное число (количество серверов, между которыми Вы делите таблицу). В этом случае узкое место — это проблема добавления новых серверов — Вам придется делать перераспределение данных между новым количеством серверов.)

Подход с применением шардирования баз данных

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

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

Шардинг и репликация

Репликация
  • Многие приложения только читают данные (Правда многие из этих чтений идут из memcache или другого кэша)
  • Используем слейвы для выполнения чтений
  • Репликация асинхронна (Нужно аккуратно чтобы не читать старые данные; Слейвы могут быть на разных позициях)
  • Не помогает масштабировать запись (Слейвы обычно могут выдерживать меньше записей чем мастер так как выполняют записи в один поток)
  • Кэши слейвов обычно очень дублированы

Репли­ка­ция — это син­хрон­ное или асин­хрон­ное копи­ро­ва­ние дан­ных между несколь­кими сер­ве­ра­ми. Веду­щие сер­вера назы­вают масте­рами (master), а ведо­мые сер­вера — слэй­вами (slave). Мастера исполь­зу­ются для изме­не­ния дан­ных, а слэйвы — для счи­ты­ва­ния. В клас­си­че­ской схеме репли­ка­ции обычно один мастер и несколько слэй­вов, так как в боль­шей части веб-про­ек­тов опе­ра­ций чте­ния на несколько поряд­ков боль­ше, чем опе­ра­ций запи­си. Однако в более слож­ной схеме репли­ка­ции может быть и несколько масте­ров.

Шардинг
  • Когда функциональное разбиение и репликация не помогают
  • Разбиваем данные на маленькие кусочки и храним на многих серверах
  • “Единственное” решение для крупного масштаба
  • Нужно аккуратное планирование
  • Может быть не просто реализовать (Особенно если это не предусмотрено дизайном)
  • Как разбивать данные очень критичный вопрос (Часто бывает надо несколько копий данных с разным разбиением)

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

Совместное использование

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

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

SQL Azure Federations

Если приложение использует базы данных MS SQL, возникает несколько вопросов. Конечно первое что приходит в голову — организовать кластер из виртуальных машин SQL Server. Решение достаточно простое и хорошо всем знакомое. А что делать, если приложение использует базу данных как сервис (SaaS)? Что если мы не хотим заниматься настройкой кластера SQL Server?
Конечно же, если мы говорим о Windows Azure, то в качестве SQL базы данных будет использоваться SQL Azure. Эта база данных поддерживает технологию горизонтального масштабирования (шардинг) называемую SQL Azure Federations. Принцип ее работы очень простой: логически независимые друг от друга строки одной таблицы хранятся в разных базах данных.

Ограничения SQL Azure

Что это дает: во-первых, изоляцию данных друг от друга. Данные одного аккаунта никак не связаны с данными другого. Соответственно, применением технологии шардинга мы можем реализовать не только горизонтальное масштабирование базы данных, но и multi-tenant сценарий.

Увеличивается скорость выборки данных из базы, поскольку сам размер хранящихся в конкретном шарде данных на порядок меньше, чем суммарный объем базы.
Однако, в любой технологии есть свои недостатки. Шардинг не исключение. Если вспомнить, архитектуру SQL Azure, то как известно сервис не поддерживает выборку данных из нескольких баз данных одновременно. То есть одна база данных — одно соединение. И шарды — не исключение. То есть, если допустим простейший запрос на возврат количества клиентов в базе данных необходимо выполнить на каждом шарде отдельно.

Исходный запрос:

SELECT Count(*) FROM Account

Пример запроса для конкретного шарда:

USE FEDERATION Accounts(AccountId = 4) WITH RESET, FILTERING = OFF
GO
SELECT Count(*) FROM Account

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

Конечно SQL Azure Federations не является панацеей и можно реализовать свой принцип горизонтального масштабирования баз данных. Допустим multi-tenant подход — тоже своего рода горизонтальное масштабирование базы данных. Поскольку данные одного пользователя отделены не только «логически» от данных другого пользователя, но и «физически».
Если необходимо добавить нового пользователя — мы конфигурируем для него отдельную базу данных. Вопрос в том, что в логике приложения должен быть механизм «роутинга». То есть приложение должно знать с какой базой данных оно в данный момент работает.

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

Серверы баз данных, поддерживающие шардирование:

MongoDB поддерживает шардирование с версии 1.6.

Grails поддерживает шардирование путем Grails Sharding Plugin.

Redis база данных с поддержкой шардирования на стороне клиента.

Microsoft поддерживает шардирование в SQL Azure через «федерации».

Практически любой сервер баз данных может быть использован по схеме шардинга, при реализации соответствующего уровня абстракции на стороне клиента. К примеру eBay применяет серверы Oracle в режиме шардинга[1], Facebook[2] и Twitter[3] применяют шардирование поверх MySQL и т. д.

Ссылки

Шардинг, перебалансировка и распределенные транзакции в реляционных базах данных

При разработке нового проекта в качестве основной СУБД нередко выбираются реляционные базы данных, такие, как PostgreSQL или MySQL. В этом действительно есть смысл. Первое время у проекта мало пользователей, и потому все данные помещаются в один сервер. При этом проект активно развивается. Нельзя заранее сказать, какой функционал в нем станет основным, а какой будет выкинут. Есть много историй о том, как мобильный дейтинг в итоге превращался в криптомессанджер, и подобного рода. РСУБД удобны на ранних этапах, потому что они универсальны. Так, PostgreSQL из коробки имеет встроенный полнотекстовый поиск, умеет эффективно работать с геоданными, а также подходит для хранения очередей и рассылки уведомлений. По мере развития проекта и роста нагрузки часть данных может быть перенесена в специализированные NoSQL решения. Также нагрузку можно распределить, поделив базу на несколько совершенно не связанных между собой баз, а также при помощи потоковой репликации. Но что делать в случае, если все это не помогло? В этом посте я постараюсь ответить на данный вопрос.

Примечание: Хочу поблагодарить gridem, sum3rman и gliush за активное участие в обсуждении поднятых в данном посте вопросов. Многие из озвученных ниже идей были позаимствованы у этих ребят.

Декомпозиция проблемы

Задачу построения горизонтально масштабируемого РСУБД-кластера можно разделить на следующие сравнительно независимые задачи:

  • Автофейловер. С ростом числа машин в системе встает проблема автоматической обработки падения этих машин. В рамках сего поста автофейловер не рассматривается, так как ему был посвящен отдельный пост Stolon: создаем кластер PostgreSQL с автофейловером. Далее эта задача считается решенной. Предполагается, что все кусочки данных (vbucket’ы) хранятся на репликасетах с автофейловером (кластерах Stolon’а). Термин репликасет позаимствован у MongoDB.
  • Шардинг, или распределение данных по репликасетам.
  • Перебалансировка, или перемещение данных между репликасетами.
  • Решардинг. Под решардингом далее понимается изменение схемы шардирования или ее параметров. Например, изменения числа частей, на которые нарезаются данные. Важно подчеркнуть, что это совершенно отдельная от перебалансировки задача и их не следует путать. Данная терминология (перебалансировка, решардинг, и что это не одно и то же) позаимствована из документации множества таких NoSQL решений, как Cassandra, CouchDB, Couchbase и Riak.
  • Распределенные транзакции. Поскольку данные распределяются по нескольким репликасетам, возникает проблема выполнения транзакций между репликасетами.
  • Автоматизация. Переизобретать все описанное выше в каждом новом проекте трудоемко и непрактично. Поэтому возникает закономерное желание как-то решить эту проблему один-единственный раз и потом повторно использовать это решение. Увы, как будет показано ниже, очень сложно представить себе универсальное решение, которое подходило бы всем. Поэтому в рамках данной заметки вопрос автоматизации не рассматривается.

Попробуем рассмотреть озвученные проблемы по отдельности.

Шардинг

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

Каждая единица данных относится к определенной «виртуальной корзине», или vbucket. Число vbucket определяется заранее, берется достаточно большим и обычно является степенью двойки. В Couchbase, например, по умолчанию существует 1024 vbucket’а. Для определения, к какому vbucket относится единица данных, выбирается некий ключ, однозначно определяющий единицу данных, и используется формула типа:

vbucket_num = hash(key) % 1024

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

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

{
  «format_version»: 1,
  «vbuckets»: [
    «cluster1»,
    «cluster2»,
    …
    «clusterN»
  ]
}

Здесь format_version нужен на случай изменения формата словаря в будущем. Также нам нужна версия (ревизия) словаря, увеличивающаяся при каждом обновлении словаря. Выше она не приведена, так как в Consul это есть из коробки для всех данных и называется ModifyIndex. Наконец, в массиве vbuckets по i-му индексу хранится имя кластера Stolon, соответствующего i-му vbucket. В случае, если в настоящее время происходит перебалансировка (см далее), вместо одного имени кластера хранится пара ["clusterFrom","clusterTo"] — откуда и куда сейчас переносятся данные.

Вы спросите, зачем так сложно? Во-первых, эта схема очень гибкая. Например, на ранних этапах развития проекта мы можем использовать один репликасет, хранящий все 1024 vbucket’а. В будущем мы можем использовать до 1024-х репликасетов. Если каждый репликасет будет хранить 1 Тб данных (далеко не предел по сегодняшним меркам), это обеспечит хранение петабайта данных во всем кластере. Во-вторых, при добавлении новых репликасетов не возникает необходимости перемещать вообще все данные туда-сюда, как это происходит, например, при использовании формулы hash(key) % num_replicasets. Наконец, мощности машин в кластере могут различаться. Эта схема позволяет распределить данные по ним неравномерно, в соответствии с мощностями.

Перебалансировка

Что делать в случае, если мы хотим переместить vbucket с одного репликасета на другой?

Начнем со словаря. Каким образом он будет изменяться при перебалансировке, было описано выше. Но перед началом переноса vbucket’ов важно убедиться, что все клиенты увидели новый словарь, в котором отражен процесс переноса. Каким образом это можно сделать? Простейшее решение заключается в том, что для каждой версии словаря раз в определенный интервал времени T (скажем, 15 секунд) клиенты пишут в Consul «словарь такой-то версии последний раз использовался тогда-то». Само собой разумеется, предполагается, что время между клиентами более-менее синхронизировано с помощью ntpd. Если словарь никем не используется уже T*2 времени, можно смело полагать, что все клиенты увидели новую версию словаря. Сами клиенты могут запрашивать последнюю версию словаря просто время от времени, или же воспользоваться механизмом Consul подписки на изменения данных.

Итак, все клиенты знают о начале перебалансировки. Далее возможны варианты.

  1. Только чтение. Проще всего запретить изменение переносимых данных. Клиенты могут читать с clusterFrom, но не могут ничего записывать. Для многих проектов такое решение вполне подходит. Например, для переноса можно выбрать время, когда системой пользуется меньше всего людей (4 часа ночи) и тем немногим, что попытаются что-то записать, честно сказать, что у проекта сейчас maintenance. Если vbucket’ы достаточно маленькие, перенос все равно займет лишь несколько минут, после чего пользователь сможет повторить запрос.
  2. Все данные неизменяемые. На первый взгляд это кажется странным, но многим реальным проектам (социальные сети, почта, IM, и прочее) на самом деле не очень-то нужны изменяемые данные. Все данные можно представить в виде лога событий. Новые данные добавляются с помощью обычного insert. В тех редких случаях, когда нужно что-то изменить, update делается через вставку новой версии данных, а delete — через вставку специальной метки, что данные больше не существуют. При таком подходе при переносе данных можно писать в clusterTo, а читать из clusterFrom и clusterTo. У этого подхода есть и ряд других преимуществ — простота синхронизации мобильных клиентов, версионность всех данных (важно в финансах), предсказуемая производительность (не нужен autovacuum), и прочие.
  3. Логическая репликация между clusterFrom и clusterTo. Ждем полной синхронизации, работая только с clusterFrom. Затем переключаемся на clusterTo и работаем только с ним. Это наиболее универсальное решение, но есть нюансы. Например, в случае с PostgreSQL логическая репликация — это очень болезненная тема. Наиболее многообещающим решением здесь является pglogical, но у него есть ряд существенных ограничений. Кроме того, последний раз, когда я пробовал pglogical, мне попросту не удалось заставить его работать. Вероятно, вам придется написать свою собственную логическую репликацию. Но это сложно и затратно по времени, так как следует корректно обрабатывать несколько транзакций, выполняющихся параллельно, откаты транзакций, и прочее в таком духе.
  4. Смешанный подход, то есть, использование (1), (2) или (3) на выбор администратора, запустившего перенос. Или же использование разных подходов для разных данных в базе.

Дополнение: Вместо pglogical вы, вероятно, захотите использовать логическую репликацию, которая начиная с PostgreSQL 10 теперь есть из коробки.

В случаях (1) и (2) данные можно переносить обычным pg_dump или воспользоваться COPY:

— экспорт таблицы
COPY tname TO PROGRAM ‘gzip > /tmp/data.gz’;
— экспорт данных по запросу
COPY (SELECT * FROM tname WHERE …) TO PROGRAM ‘gzip > /tmp/data.gz’;
— импорт данных
COPY tname FROM PROGRAM ‘zcat /tmp/data.gz’;

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

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

Решардинг

Напомню, что решардингом, в отличие от перебалансировки, называется изменение числа vbucket’ов или же полное изменение схемы шардирования. Последнее по моим представлениям является очень сложной задачей, делается крайне редко, и исключительно в случае, если весь шардинг реализован непосредственно в самом приложении, а не на стороне СУБД или какого-то middleware перед ним. Здесь очень многое зависит от конкретной ситуации, поэтому далее мы будем говорить о решардинге только в контексте изменения числа vbucket’ов.

Простейший подход к решардингу — это не поддерживать его и просто заранее выбирать достаточно большее количество vbucket’ов 🙂

Если же решардинг все-таки требуется поддерживать, многое зависит от того, что было выбрано за единицу данных (см параграф про шардинг). Допустим, ей является строка в таблице. Тогда мы можем очень просто удвоить количество vbucket’ов. Вспомним формулу:

// было
vbucket_num = hash(key) % 1024 [ = hash(key) & 0x3FF ]
// стало
vbucket_num = hash(key) % 2048 [ = hash(key) & 0x7FF ]

После удвоения числа vbucket’ов половина ключей будут соответствовать все тому же номеру бакета, от 0 до 1023. Еще половина ключей будет перенесена в бакеты с 1024 по 2047. Притом ключ, ранее принадлежавший бакету 0, попадет в бакет 1024, ранее принадлежавший бакету 1 — в бакет 1025, и так далее (у номера бакета появится дополнительный старший бит, равный единице). Это означает, что если мы возьмем текущий словарь, и модифицируем его так:

// оператор ++ означает операцию append, присоединение массива с конца
dict.vbuckets = dict.vbuckets ++ dict.vbuckets

… то все ключи автоматически окажутся на нужных репликасетах без какого-либо переноса. Теперь, когда число vbucket’ов удвоилось, бакеты можно переносить с репликасета на репликасет, как обычно. Уменьшение числа vbucket’ов происходит аналогично в обратном порядке — сначала серия переносов, затем обновление словаря. Как и в случае с перебалансировкой, следует проверять, что все клиенты увидели новую версию словаря.

Если единицами данных являются базы данных с одинаковой схемой, все несколько сложнее. В этом случае не очень понятно, как можно быстро и правильно для общего случая разделить каждую базу на две. Похоже, лучшее, что можно сделать при такой схемы шардирования, вместо использования формулы hash(key) % 1024 просто сообщать пользователю количество vbucket’ов. В этом случае определение номера бакета по ключу, а также перенос данных в случае решардинга перекладываются на приложение. Зато число бакетов может в любой момент быть увеличено на произвольное число просто путем создания пустых бакетов. Или уменьшено путем удаления лишних бакетов, в предположении, что пользователь заблаговременно перенес из них все данные.

Распределенные транзакции

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

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

  1. Создайте объект «транзакция», хранящий все, что вы хотите записать в бакеты А и Б.
  2. Произведите запись в бакет А. Запись должна производиться в одну локальную транзакцию, а у записанных объектов должна быть метка, к какой транзакции они относятся. Если объекты с соответствующей меткой уже записаны, ничего не делать.
  3. Аналогичным образом произведите запись в бакет Б.
  4. Удалите объект «транзакция»;

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

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

Описание более универсального подхода можно найти в блоге Дениса Рысцова. Также этот прием описан как минимум в документации к MongoDB и блоге CockroachDB. В общих чертах алгоритм примерно такой:

  1. Создайте объект «транзакция» с состоянием committed = false, aborted = false.
  2. При обращении к объектам в вашей базе данных на чтение и на запись указывайте в них ссылку на транзакцию. При обращении на запись в специальное поле допишите, каким станет объект в случае, если транзакция завершится успешно (локальные изменения). Если у объекта уже есть метка, и соответствующая транзакция:
    • … закоммичена, примените локальные изменения объекта и запишите свою метку.
    • … отменена, очистите локальные изменения и запишите свою метку.
    • … все еще выполняется, значит произошел конфликт. Вы можете подождать, отменить свою транзакцию, или отменить чужую транзакцию. Примите во внимание, что процесс, выполнявший другую транзакцию, мог уже умереть. Так что, как минимум при определенных условиях вы должны отменять ту, другую транзакцию (например, если она начала выполнение достаточно давно). Иначе затронутые ею объекты останутся заблокированы навсегда.
  3. Если транзакция все еще не прибита другими процессами, измените ее состояние на committed = true. Это ключевой момент алгоритма. Так как этот шаг выполняется атомарно, и все транзакции знают, как трактовать локальные изменения, у транзакции нет никаких промежуточных состояний. В любой момент времени она либо применена, либо нет.
  4. Почистите за собой — примените локальные изменения ко всем затронутым объектам, затем удалите объект «транзакция». Этот шаг не обязателен в смысле корректности алгоритма. Он просто освобождает место на диске от данных, которые стали ненужны.

Важно! Приведенное описание предполагает, что каждая операция чтения или записи выполняется в отдельной транзакции при уровне изоляции serializable. Или, в более общем случае, если СУБД не поддерживает транзакций, в одну CAS-операцию. Однако выполнение нескольких операций в одной транзакции не влияет на корректность алгоритма.

Этот алгоритм довольно неприятно применять по той причине, что абсолютно все транзакции, включая локальные, должны понимать, как трактовать локальные изменения. Алгоритм обеспечивает уровень изоляции repeatable read. Это уровень изоляции менее строгий, чем snapshot isolation и serializable, и на нем возможны некоторые аномалии (phantom read, write skew). Тем не менее, он подходит для многих приложений, если знать об его ограничениях.

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

Заключение

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

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

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

Дополнение: В этом контексте вас также может заинтересовать статья Поднимаем кластер CockroachDB из трех нод.

Метки: Распределенные системы, СУБД.

HighLoad Junior Блог


Денис Иванов (2ГИС)


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


Немного расскажу о себе – я работаю в компании WebAPI в 2GIS-е, мы предоставляем API для организаций, у нас очень много разных данных, 8 стран, в которых
мы работаем, 250 крупных городов, 50 тыс. населенных пунктов. У нас достаточно большая нагрузка – 25 млн. активных пользователей в месяц, и в среднем
нагрузка около 2000 RPS идет на API. Все это располагается в трех датацентрах.


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


Я в большей степени расскажу про шардинг. Он бывает вертикальным и горизонтальным. Также бывает такой способ масштабирования как репликация. Доклад «Как устроена MySQL репликация» Андрея Аксенова из Sphinx про это и был. Я эту тему практически не буду освещать.


Перейдем подробнее к теме партицирования (вертикальный шардинг). Как это все выглядит?


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


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


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


Про репликацию я не буду останавливаться, тут все очень просто.


Перейдем глубже к этой теме, и я расскажу практически все о партицировании на примере Postgres’а.


Давайте рассмотрим простую табличку, наверняка, практически в 99% проектов такая табличка есть – это новости.


У новости есть идентификатор, есть категория, в которой эта новость расположена, есть автор новости, ее рейтинг и какой-то заголовок – совершенно
стандартная таблица, ничего сложного нет.


Как же эту таблицу разделить на несколько? С чего начать?


Всего нужно будет сделать 2 действия над табличкой – это поставить у нашего шарда, например, news_1, то, что она будет наследоваться таблицей news. News
будет базовой таблицей, будет содержать всю структуру, и мы, создавая партицию, будем указывать, что она наследуется нашей базовой таблицей. Наследованная
таблица будет иметь все колонки родителя – той базовой таблицы, которую мы указали, а также она может иметь свои колонки, которые мы дополнительно туда
добавим. Она будет полноценной таблицей, но унаследованной от родителя, и там не будет ограничений, индексов и триггеров от родителя – это очень важно.
Если вы на базовой таблице насоздаете индексы и унаследуете ее, то в унаследованной таблице индексов, ограничений и триггеров не будет.


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


В данном случае признак – это category_id=1, т.е. только записи с category_id=1 будут попадать в эту таблицу.


Какие типы проверок бывают для партицированных таблиц?


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


Тут нужно подробнее остановиться, потому что проверка поддерживает оператор BETWEEN, наверняка вы все его знаете.


И так просто его сделать можно. Но нельзя.


Можно сделать, потому что нам разрешат такое сделать, PostgreSQL поддерживает такое.


Как вы видите, у нас в 1-ую партицию попадают данные между 100 и 200, а во 2-ую – между 200 и 300.


В какую из этих партиций попадет запись с рейтингом 200? Не известно, как повезет.


Поэтому так делать нельзя, нужно указывать строгое значение, т.е. строго в 1-ую партицию будут попадать значения больше 100 и меньше либо равно 200, и во
вторую больше 200, но не 200, и меньше либо равно 300.


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


Также не стоит создавать партиции по разным полям, т.е. что в 1-ую партицию у нас будут попадать записи с category_id=1, а во 2-ую – с рейтингом 100.


Опять же, если нам придет такая запись, в которой category_id = 1 и рейтинг =100, то неизвестно в какую из партиций попадет эта запись. Партицировать стоит
по одному признаку, по какому-то одному полю – это очень важно.


Давайте рассмотрим нашу партицию целиком:


Ваша партицированная таблица будет выглядеть вот так, т.е. это таблица news_1 с признаком, что туда будут попадать записи только с category_id = 1, и эта
таблица будет унаследована от базовой таблицы news – все очень просто.


Мы на базовую таблицу должны добавить некоторое правило, чтобы, когда мы будем работать с нашей основной таблицей news, вставка на запись с category_id = 1
попала именно в ту партицию, а не в основную. Мы указываем простое правило, называем его как хотим, говорим, что когда данные будут вставляться в news с
category_id = 1, вместо этого будем вставлять данные в news_1. Тут тоже все очень просто: по шаблончику оно все меняется и будет замечательно работать. Это
правило создается на базовой таблице.


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


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


Давайте рассмотрим пример вставки данных:


Данные будем вставлять как обычно, будто у нас обычная большая толстая таблица, т.е. мы вставляем запись с category_id=1 с category_id=2, можем даже
вставить данные с category_id=3.


Вот мы выбираем данные, у нас они все есть.


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


Мы также можем сделать соответствующие запросы в определенные партиции, указывая наше условие, т.е.category_id = 1, или вхождение в числа (2, 3).


Все будет замечательно работать, все данные будут выбираться. Опять же, несмотря на то, что с партиции с category_id=3 у нас нет.


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


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


Здесь можно, как видно на слайде, вставлять данные напрямую в партицию. Можно вставлять данные с помощью правил в основную таблицу, но можно и в саму
партицию.


Если мы будем вставлять данные в партицию с каким-то чужеродным условием, например, с category_id = 4, то мы получим ошибку «сюда такие данные нельзя
вставлять» – это тоже очень удобно – мы просто будем класть данные только в те партиции, которые нам действительно нужно, и если у нас что-то пойдет не
так, мы на уровне базы все это отловим.


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


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


У нас будет Seq Scan по всей таблице целиком, потому что туда данные могут все равно попадать, и будет скан по партиции. Если мы будем указывать условия
нескольких категорий, то он будет сканировать только те таблицы, на которые есть условия. Он не будет смотреть в остальные партиции. Так работает
оптимизатор – это правильно, и так действительно быстрее.


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


Это будет обычная таблица, просто Seq Scan по ней, ничего сверхъестественного.


Точно так же будут работать update’ы и delete’ы. Мы можем update’тить основную таблицу, можем также update’ы слать напрямую в партиции. Так же и delete’ы
будут работать. На них нужно так же соответствующие правила создать, как мы создавали с insert’ом, только вместо insert написать update или delete.


Перейдем к такой вещи как Index’ы.


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


Как мы с этой проблемой боролись у себя. Мы создали замечательную утилиту PartitionMagic, которая позволяет автоматически управлять партициями и не
заморачиваться с созданием индексов, триггеров с несуществующими партициями, с какими-то бяками, которые могут происходить. Эта утилита open source’ная,
ниже будет ссылка. Мы эту утилиту в виде хранимой процедуры добавляем к нам в базу, она там лежит, не требует дополнительных extension’ов, никаких
расширений, ничего пересобирать не нужно, т.е. мы берем PostgreSQL, обычную процедуру, запихиваем в базу и с ней работаем.


Вот та же самая таблица, которую мы рассматривали, ничего нового, все то же самое.


Как же нам запартицировать ее? А просто вот так:


Мы вызываем процедуру, указываем, что таблица будет news, и партицировать будем по category_id. И все дальше будет само работать, нам больше ничего не
нужно делать. Мы так же вставляем данные.


У нас тут три записи с category_id =1, две записи с category_id=2, и одна с category_id=3.


После вставки данные автоматически попадут в нужные партиции, мы можем сделать селекты.


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


Какие мы получаем за счет этого преимущества:


· при вставке мы автоматически создаем партицию, если ее еще нет;


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


Мы получаем действительно большое преимущество в этом. Вот ссылочка: https://github.com/2gis/partition_magic.


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


Перейдем ко второй части доклада – это горизонтальный шардинг.


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


Рассматривать будем такую же структуру с двумя шардами – news_1 и news_2, но это будут разные инстансы, третьим инстансом будет основная база, с которой мы
будем работать:


Та же самая таблица:


Единственное, что туда нужно добавить, это CONSTRAINT CHECK, того, что записи будут выпадать только с category_id=1. Так же, как в предыдущем примере, но
это не унаследованная таблица, это будет таблица с шардом, которую мы делаем на сервере, который будет выступать шардом с category_id=1. Это нужно
запомнить. Единственное, что нужно сделать – это добавить CONSTRAINT.


Мы еще можем дополнительно создать индекс по category_id:


Несмотря на то, что у нас стоит check – проверка, PostgreSQL все равно обращается в этот шард, и шард может очень надолго задуматься, потому что данных
может быть очень много, а в случае с индексом он быстро ответит, потому что в индексе ничего нет по такому запросу, поэтому его лучше добавить.


Как настроить шардинг на основном сервере?


Мы подключаем EXTENSION. EXTENSION идет в Postgres’e из коробки, делается это командой CREATE EXTENSION, называется он postgres_fdw, расшифровывается как
foreign data wrapper.


Далее нам нужно завести удаленный сервер, подключить его к основному, мы называем его как угодно, указываем, что этот сервер будет использовать foreign
data wrapper, который мы указали.


Таким же образом можно использовать для шарда MySql, Oracle, Mongo… Foreign data wrapper есть для очень многих баз данных, т.е. можно отдельные шарды
хранить в разных базах.


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


Далее мы создаем маппинг для пользователя – по этим данным основной сервер будет авторизироваться к дочернему. Мы указываем, что для сервера news_1 будет
пользователь postgres, с паролем postgres. И на основную базу данных он будет маппиться как наш user postgres.


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


Далее мы заводим табличку на основном сервере


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


Схема по дефолту – это public, таблицу, которую мы завели, назвали news. Точно так же мы подключаем 2-ую таблицу к основному серверу, т.е. добавляем
сервер, добавляем маппинг, создаем таблицу. Все, что осталось – это завести нашу основную таблицу.


Это делается с помощью VIEW, через представление, мы с помощью UNION ALL склеиваем запросы из удаленных таблиц и получаем одну большую толстую таблицу news
из удаленных серверов.


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


Мы заводим основное правило, которое будет срабатывать, если ни одна проверка не сработала, чтобы не происходило ничего. Т.е. мы указываем DO INSTEAD
NOTHING и заводим такие же проверки, как мы делали ранее, но только с указанием нашего условия, т.е. category_id=1 и таблицу, в которую данные вместо этого
будут попадать.


Т.е. единственное отличие – это в category_id мы будем указывать имя таблицы.


Также посмотрим на вставку данных.


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


Выбираем данные.


Обратите внимание на сортировку идентификаторов – у нас сначала выводятся все записи из первого шарда, затем из второго. Это происходит из-за того, что
postgres ходит по VIEW последовательно. У нас указаны select’ы через UNION ALL, и он именно так исполняет – посылает запросы на удаленные машины, собирает
эти данные и склеивает, и они будут отсортированы по тому принципу, по которому мы эту VIEW создали, по которому тот сервер отдал данные.


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


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


Посмотрим на explain.


У нас foreign scan по news_1 и foreign scan по news_2, так же, как было с партицированием, только вместо Seq Scan-а у нас foreign scan – это удаленный
скан, который выполняется на другом сервере.


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


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

Facebook

Twitter

Вконтакте

Google+

← Производительность запросов в PostgreSQL – шаг за шагом

Sharding – patterns and antipatterns →

Шардинг – Что такое? Шардинг и Эфириум – BitcoinWiki

Это утверждённая версия страницы. Она же — наиболее свежая версия.

Понравилась статья? Поделись:

Шардинг – это одна из стратегий масштабирования каких-либо приложений. Шардинг стал одной из топовых тем для обсуждения в сообществе Ethereum. Потому что его считают одним из ключевых вариантов решения проблемы масштабируемости этого блокчейна. Термин sharding пришел к нам из английского языка и не имеет перевода. Что это за технология и как ее вндрять на блокчейне?

Что такое Шардинг (Sharding)?[править]

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

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

Зачем применять шардинг?[править]

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

Технология шардинга и ее применение[править]

Ethereum шардинг – Эфириум и проблема масштабируемости

Выше мы описали как технология применяется для шардинга базы данных. Технически она выглядит достаточно просто, но на практике применять ее нужно очень внимательно и аккуратно. Потому что когда база данных разбита на части и один запрос должен получить данные с разных шард (серверов), то важно чтобы разделенные данные корректно «собрались» обратно.

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

Сложности реализации блокчейн-шардинга (sharding)[править]

  1. Не существует механизма, отслеживающего какая нода обрабатывает какую транзакцию. Нужен безопасный и эффективный механизм, который будет безопасно и быстро работать в блокчейне.
  2. Не разработан алгоритм доверия между нодами, чтобы они могли доверять друг другу. В криптовалютном блокчейне ноды не имеют права просто доверять друг другу – они должны приходить к независимому консенсусу. Также нужно подтверждение того, что обе ноды закончили процесс обработки.
  3. Эффективно применять шардинг можно в блокчейнах с алгоритмом proof-of-stake (полным или частичным), а сейчас с проблемой масштабирования сталкиваются в основном криптовалюты с алгоритмом proof-of-work.

Шардинг как средство спасения эфириума[править]

С учетом перечисленных выше сложностей, за шардинг никто не берется. Никто, кроме Ethereum. Это единственный достаточно крупный блокчейн, который даже при переходе на Proof-of-stake будет заинтересован в реализации этой технологии.

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

Презентовал шардинг лично сооснователь Ethereum Виталик Бутерин. Это произошло на тайбэйской конференции BeyondBlock в конце ноября 2017 года. Тогда же была презентована дорожная карта проекта, который по словам разработчиков, должен был вывести Ethereum на уровень платежной системы Visa. Уже через два месяца разработчики отчитались, что первая часть дорожной карты шардинга в блокчейне «эфира» реализована. И в начале мая этого года Виталик Бутерин сообщил, что скоро в сети Ethereum можно будет внедрять шардинг. Правда, для этого придется провести сетевой хардфорк.

Как именно будет внедрен шардинг – пока точно не сообщается. Возможно, проблема доверия между нодами будет решена с помощью внедрения системы материальной стимуляции – когда ноды будут получать вознаграждение за достоверную информацию и будут штрафоваться в случае нарушений. Мы писали выше, что внедрение этой технологии требует алгоритма консенсуса Proof-of-Stake (PoS). И если сейчас сеть «эфира» работает на Proof-of-work (PoW), то скоро она перейдет на протокол Casper, что означает использование гибридного протокола PoW/ PoS. А это приближает внедрение шардинга. 10 мая была выложен первая версия программного обеспечения Casper FFG 0.1.0, которая доступна для тестирования. Пока точной информации о внедрении протокола в Ethereum нет.

См. также на BitcoinWiki[править]

https://ruhighload.com/Шардинг+и+репликация
http://chainmedia.ru/articles/ethereum-sharding/
https://probtc.info/materialy/35198/
https://decenter.org/blockchain/408-sharding-ethereum-rus
https://forklog.com/buterin-tehnologicheskij-sharding-pozvolit-ethereum-dostich-masshtabov-visa/
https://forklog.com/opublikovana-pervaya-versiya-koda-obnovleniya-casper-dlya-ethereum/

Горизонтальное масштабирование базы данных реального проекта с помощью SQL Azure Federations

Шардинг

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

Зачем вообще нужно горизонтальное масштабирование? Когда возникает вопрос повышения производительности приложения, то есть несколько вариантов. Как известно можно купить новое «железо» для сервера, добавить количество оперативной памяти и т. д. Этот принцип называется вертикальным масштабированием. Однако этот способ может быть достаточно дорогим, долгим, да и имеет предел. Можно конечно купить топовое железо, однако оно может не потянуть все требования вашего приложения.

Второй способ, называемый горизонтальным масштабированием, предполагает расширение вычислительных ресурсов доступных приложению за счет увеличения количества серверов или инстансов приложения, в случае PaaS, на которых размещено ваше приложение. То есть если раньше ваше приложение было расположено на одном сервере, и в какой-то момент оно перестало «вытягивать» нагрузку, можно просто купить второй точно такой же сервер. Поставить на него ваше приложение и таким образом часть запросов к приложению будет идти на первый сервер, часть — на второй.

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

Если рассматривать возможности облачной платформы от Microsoft, то они достаточно широкие. Есть auto-scaling, scaling по запросу, причем все это доступно как с помощью UI, так и с помощью SDK, REST API и PowerShell.

Однако если с масштабированием приложения (PaaS) или виртуальных машин (IaaS) все достаточно просто, указываете сколько инстансов вам необходимо, столько и будет, то в случае если ваше приложение использует базы данных MS SQL, возникает несколько вопросов. Конечно первое что приходит в голову — организовать кластер из виртуальных машин SQL Server. Решение достаточно простое и хорошо всем знакомое. А что делать, если приложение использует базу данных как сервис (SaaS)? Что если мы не хотим заниматьсянастройкой кластера SQL Server?

Конечно же, если мы говорим о Windows Azure, то в качестве SQL базы данных будет использоваться SQL Azure. Эта база данных поддерживает технологию горизонтального масштабирования (шардинг) называемую SQL Azure Federations. Принцип ее работы очень простой: логически независимые друг от друга строки одной таблицы хранятся в разных базах данных. Самый простой пример:

Это одна и та же таблица, данные которой хранятся в разных экземплярах базы данных (шардах). То есть данные аккаунта с идентификатором 1 хранятся в первой базе данных, с идентификатором 2 — во второй и т. д.

Ограничения SQL Azure

Итак, что же это нам дает? Во-первых изоляцию данных друг от друга. Данные одного аккаунта никак не связаны с данными другого. Соответственно, применением технологии шардинга мы можем реализовать не только горизонтальное масштабирование базы данных, но и multi-tenant сценарий.

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

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

Если вспомнить, архитектуру SQL Azure, то как известно сервис не поддерживает выборку данных из нескольких баз данных одновременно. То есть одна база данных — одно соединение. И шарды — не исключение. То есть, если допустим пройстейший запрос на возврат количества клиентов в базе данных необходимо выполнить на каждом шарде отдельно.

Исходный запрос:
SELECT Count(*) FROM Account

Пример запроса для конкретного шарда:
USE FEDERATION Accounts(AccountId = 4) WITH RESET, FILTERING = OFF
GO
SELECT Count(*) FROM Account

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

Предисловие

Конечно SQL Azure Federations не является панацеей и вы можете реализовать свой принцип горизонтального масштабирования баз данных. Допустим multi-tenant подход — тоже своего рода горизонтальное масштабирование базы данных. Поскольку данные одного пользователя отделены не только «логически» от данных другого пользователя, но и «физически».

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

Но вернемся к SQL Azure Federations…

Сама задумка компании Microsoft достойна всяческих похвал. Было бы неплохо получить инструмент позволяющий легко мастабировать базу данных. А еще бы сделать auto-scaling, на основе результатов определенных запросов (ну это уже фантастика)…

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

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

В общем не переключайтесь! Всем хорошего начала рабочей недели!

Масштабирование БД в высоконагруженных системах / Хабр

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

Когда сервис недоступен пользователям какое-то время, это дико неприятно, но всё же не смертельно. А вот потерять данные клиента — абсолютно недопустимо. Поэтому любую технологию для хранения данных мы скрупулезно оцениваем по двум-трем десяткам параметров. Часть из них диктует текущая нагрузка на сервис.

Текущая нагрузка. Технологию подбираем с учётом роста этих показателей.

Клиент-серверная архитектура

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

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

Что ненадёжно в клиент-серверной архитектуре? Очевидно, сервер может упасть. А когда сервер падает, все клиенты не могут работать. Чтобы этого избежать, люди придумали master-slave подключение (которое теперь политкорректно называют leader-follower). Суть — есть два сервера, все клиенты общаются с главным, а на второй просто реплицируются все данные.

Клиент-серверная архитектура с репликацией данных на фолловера.

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

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

А что будет, если лидер упадет ночью, когда все спят? Данные на фолловере есть, но ему никто не сказал, что он теперь лидер, и клиенты к нему не подключаются. ОК, давайте наделим фолловер логикой, что он начинает считать себя главным, когда связь с лидером потеряна. Тогда легко можем получить split brain — конфликт, когда связь между лидером и фолловером нарушена, и оба думают, что они главные. Это действительно происходит во многих системах, например в RabbitMQ — самой популярной сегодня технологии очередей.

Чтобы решить эти проблемы, организовывают auto failover — добавляют третий сервер (witness, свидетель). Он гарантирует, что у нас только один лидер. А если лидер отваливается, то фолловер включается автоматически с минимальным даунтаймом, который можно снизить до нескольких секунд. Конечно, клиенты в этой схеме должны заранее знать адреса лидера и фолловера и реализовывать логику автоматического переподключения между ними.

Свидетель гарантирует, что есть только один лидер. Если лидер отваливается, то фолловер включается автоматически.

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

Но и у этой схемы есть недостатки. Представьте, что вы ставите сервис паки или обновляете ОС на лидер-сервере. До этого вы вручную переключили нагрузку на фолловера и тут… он падает! Катастрофа, ваш сервис недоступен. Что делать, чтобы защититься от этого? Добавляют третий резервный сервер — ещё один фолловер. Три — что-то вроде магического числа. Если вы хотите, чтобы система работала надежно, два сервера недостаточно, нужно три. Один на обслуживании, второй падает, остаётся третий.

Третий сервер обеспечивает надежную работу, если первые два недоступны.

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

Транзакции

Хорошо известны четыре основных требования к транзакциям: атомарность, согласованность, изолированность и долговечность (Atomicity, Consistency, Isolation, Durability — ACID).

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

Dirty reads. В первой транзакции мы два раза отправляем один и тот же запрос: взять всех пользователей, у которых ID = 1. Если вторая транзакция поменяет эту строчку, а потом сделает rollback, то база данных с одной стороны никаких изменений не увидит, а с другой стороны первая транзакция прочитает разные значения возраста для Joe.

Non-repeatable reads. Другой случай — если транзакция записи завершилась успешно, а транзакция чтения при выполнении одного и того же запроса также получила разные данные.

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

Phantom reads — это когда мы в рамках одной транзакции повторно читаем какой-нибудь диапазон и получаем разный набор строк. Где-то посередине влезла другая транзакция и вставила или удалила записи.

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

Стандарт ANSI/ISO SQL определяет 4 уровня изоляции транзакций, которые влияют на степень их взаимной блокировки. Чем выше уровень изоляции, тем меньше нежелательных эффектов. Платой за это является замедление работы приложения (поскольку транзакции чаще находятся в ожидании снятия блокировки с нужных им данных) и повышение вероятности deadlocks.

Самым приятным для прикладного программиста является уровень Serializable — нет никаких нежелательных эффектов и вся сложность обеспечения целостности данных переложена на СУБД.

Давайте подумаем о наивной реализации уровня Serializable — при каждой транзакции мы просто блокируем все остальные. Каждая транзакция записи может теоретически выполняться за 50мкс (время одной операции записи у современных SSD дисков). А мы хотим сохранять данные на три машины, помните? Если они находятся в одном дата-центре, то запись займет 1-3 мс. А если они, для надежности, находятся в разных городах, то запись легко может занять 10-12мс (время путешествия сетевого пакета из Москвы в Санкт-Петербург и обратно). То есть при наивной реализации уровня Serializable последовательной записью мы сможем выполнять не больше 100 транзакций в секунду. При том, что отдельный диск SSD позволяет выполнять порядка 20 000 операций записи в секунду!

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

Шардирование

Что делать, когда данные перестают влезать на один сервер? Есть два стандартных механизма масштабирования:

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

Ключ шардирования — это параметр, по которому данные распределяются между серверами, например идентификатор клиента или организации.

Представьте, что вам нужно записать в кластер данные обо всех жителях Земли. В качестве ключа шарда можно взять, например, год рождения человека. Тогда хватит 116 серверов (и каждый год нужно будет добавлять новый сервер). Или вы можете взять в качестве ключа страну, где проживает человек, тогда вам понадобится примерно 250 серверов. Предпочтительнее всё-таки первый вариант, потому что дата рождения человека не меняется, и вам никогда не нужно будет перекидывать данные о нём между серверами.

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

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

Консенсус в кластере

Когда машин в кластере много и часть из них теряют связь с остальными, то как решить, кто хранит самую последнюю версию данных? Просто назначить witness-сервер недостаточно, ведь он тоже может потерять связь со всем кластером. Кроме того, в ситуации split brain несколько машин могут записывать разные версии одних и тех же данных — и нужно как-то определить, какая из них самая актуальная. Для решения этой задачи люди придумали консенсус-алгоритмы. Они позволяют нескольким одинаковым машинам прийти к единому результату по любому вопросу путем голосования. В 1989 году был опубликован первый такой алгоритм, Paxos, а в 2014 году ребята из Стэнфорда придумали более простой в реализации Raft. Строго говоря, чтобы кластеру из (2N+1) серверов достичь консенсуса, достаточно, чтобы в нем было одновременно не более N отказов. Чтобы переживать 2 отказа, в кластере должно быть не менее 5 серверов.

Масштабирование реляционных СУБД

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

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

Теперь представьте, что данные распределены по четырем серверам кластера и вам нужно выполнить тот же самый запрос:

Если СУБД не хочет просматривать все записи всего кластера, то она вероятно попробует найти записи с A.id равным 128, 129, или 130 и найти для них подходящие записи из таблицы B. Но если A.id не является ключом шардирования, то СУБД заранее не может знать, на каком сервере лежат данные таблицы А. Придется все равно обратиться ко всем серверам, чтобы узнать, есть ли там подходящие под наше условие записи A.id. Потом каждый сервер может сделать JOIN внутри себя, но этого не достаточно. Видите, запись на ноде 2 нам нужна в выборке, но там нет записи c A.id=128? Если ноды 1 и 2 будут делать JOIN независимо, то результат запроса будет неполным — часть данных мы не получим.

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

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

NoSQL подход

Сложности с масштабированием классических СУБД привели к тому, что люди придумали NoSQL-базы данных, в которых нет операции JOIN. Нет джойнов — нет проблем. Но нет и ACID-свойств, а об этом в маркетинговых материалах умолчали. Быстро нашлись умельцы, которые испытывают на прочность разные распределенные системы и выкладывают результаты публично. Оказалось, бывают сценарии, когда кластер Redis теряет 45% сохраненных данных, кластер RabbitMQ — 35% сообщений, MongoDB — 9% записей, Cassandra — до 5%. Причем речь идет о потере после того, как кластер сообщил клиенту об успешном сохранении. Обычно ты ожидаешь более высокий уровень надежности от выбранной технологии.

Компания Google разработала базу данных Spanner, которая работает глобально по всему миру. Spanner гарантирует ACID-свойства, Serializability и даже больше. У них в дата-центрах стоят атомные часы, которые обеспечивают точное время, и это позволяет выстраивать глобальный порядок транзакций без необходимости пересылать сетевые пакеты между континентами. Идея Spanner в том, что пусть лучше программисты разбираются с проблемами производительности, которые возникают при большом количестве транзакций, чем делают костыли вокруг отсутствия транзакций. Однако, Spanner — закрытая технология, он вам не подходит, если вы по каким-либо причинам не хотите зависеть от одного вендора.

Выходцы из Google разработали open source аналог Spanner и назвали его CockroachDB («cockroach» по-английски «таракан», что должно символизировать живучесть БД). На Хабре уже писали о неготовности продукта к production, потому что кластер терял данные. Мы решили проверить более новую версию 2.0, и пришли к аналогичному выводу. Данные мы не потеряли, но некоторые простейшие запросы выполнялись необоснованно долго.


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

Как же делать mission-critical приложения, у которых данные не умещаются на один сервер? На рынке появляются новые решения и про одно из них — FoundationDB — мы подробнее расскажем в следующей статье.

определение карикатуры по The Free Dictionary

карикатура

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

Оскорбительные, запутанные и неправильно используемые слова Мэри Эмбри Авторские права © 2007, 2013 Мэри Эмбри

car · i · ca · ture

(kăr′ĭ-kə-cho͝or ′, -ch )r) n. 1.

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

б. Искусство создания подобных изображений.

2. Гротескная имитация или искажение фактов: Судебный процесс был карикатурой на справедливость.

тр.в. автомобиль · я · ка · тьюр , автомобиль · я · ка · тур , автомобиль · я · ка · туры

Для изображения или имитации в преувеличенной, искаженной форме.


[французский язык, от итальянского caricatura, от caricare, — загрузка, преувеличить , от позднего латинского carricāre, от латинского carrus, — фургон галльского типа ; см. kers- в индоевропейских корнях.]


car′i · ca · tur′ist n.

Словарь английского языка American Heritage®, пятое издание. Авторское право © 2016 Издательская компания Houghton Mifflin Harcourt. Опубликовано Houghton Mifflin Harcourt Publishing Company. Все права защищены.

карикатура

(ˈkærɪkəˌtj) n

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

2. смехотворно неадекватная или неточная имитация: он карикатура на государственного деятеля.

vb

( tr ) для представления в карикатуре или создания карикатуры на

[C18: от итальянского caricatura искажение, преувеличение, от caricare до загрузки, преувеличения; см. груз]

caricaˌtural adj

caricaˌturist n

Словарь английского языка Коллинза — полный и несокращенный, 12-е издание 2014 г. © HarperCollins Publishers 1991, 1994, 1998, 2000, 2003, 2006, 2007 , 2011, 2014

вагон • я • ка • тура

(ˈkær ɪ kə tʃər, -ˌtʃʊər)

n., v. -tured, -tur • ing. н.

1. изображение или описание, смехотворно преувеличивающие особенности или недостатки человека или вещи.

2. искусство или процесс создания таких изображений или описаний.

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

в.т.

4. сделать карикатуру.

[1740–50; <Итальянский caricatura, производное caricat (o) поражено, буквально, загружено]

car′i • ca • tur • ist, n.

Random House Словарь колледжа Кернермана Вебстера © 2010 K Dictionaries Ltd. Авторские права 2005, 1997, 1991, Random House, Inc. Все права защищены.

карикатура

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

См. Также: Представительство

-Ologies & -Isms. Copyright 2008 The Gale Group, Inc. Все права защищены.

карикатура

Past причастие: карикатурном
герундия: выставляя

ImperativePresentPreteritePresent ContinuousPresent PerfectPast ContinuousPast PerfectFutureFuture PerfectFuture ContinuousPresent Идеальный ContinuousFuture Идеальный ContinuousPast Идеальный ContinuousConditionalPast Условный

императив
карикатура
карикатура
они

Настоящее
Я карикатура
ты карикатура
он / она / она карикатуры
мы карикатуры
9016 906

они были

они были карикатура

Претерит
Я карикатурно
вы карикатурно
он / она / она карикатурно
мы карикатурно 90 162
вы карикатурно
они карикатурно

9015 карикатурируете

9016

9015 он карикатурирую карикатурные

настоящее время непрерывное
я карикатурно
вы карикатурируете 9016
вы карикатурные
они карикатурные

карикатурно

карикатурно

карикатурно

Present Perfect
Я карикатурно
у нас есть карикатуры
вы карикатурили
они карикатурили

9015 9

Прошлое непрерывное
это было карикатурно
мы карикатурировали
вы карикатурировали
они карикатурировали
9016 это было карикатурно
Past Perfect
Я карикатурно
у нас было карикатурно
вы карикатурно
они карикатурно

9016

будущее
9015
будет карикатурным 9016 / она / это будет карикатурно
мы будем карикатурно
карикатурно
они будут карикатурно
Future Perfect
d у вас будет автомобиль карикатурный
he / она / она будет карикатурно
у нас будет карикатура
у вас будет карикатура
они будут карикатурно

9015 вы будете карикатурно

Future Continuous
он / она / она будет карикатурно
мы будем карикатурно
вы будете карикатурно
они будут карикатурно

9015

Я карикатурировал
вы карикатурировали
он / она / она карикатурировали
мы карикатурировали
вы были карикатурировали
F uture Perfect Continuous
Я буду карикатурировать
вы будете карикатурно
он / она будет карикатурно
мы будем карикатурировать
карикатурировали
они будут карикатурировать
Past Perfect Continuous
Я карикатурировал
вы карикатурировали
он / он

мы карикатурировали
вы карикатурировали
они карикатурировали
условно
я бы карикатурно
9016 карикатура 9016 / это бы карикатура
мы карикатурно
карикатурно
карикатурно

карикатурное

карикатурили
прошлое условное
карикатурно
мы бы карикатурили
вы бы карикатурили
они бы карикатурили

Collins English Verb Tables © HarperCollins Publishers

caricatures недостатки или особенности человека или идеи.

Словарь незнакомых слов от Diagram Group © 2008, Diagram Visual Information Limited

.

определение карикатур из The Free Dictionary

карикатура

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

Оскорбительные, запутанные и неправильно используемые слова Мэри Эмбри Авторские права © 2007, 2013 Мэри Эмбри

car · i · ca · ture

(kăr′ĭ-kə-cho͝or ′, -ch )r) n. 1.

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

б. Искусство создания подобных изображений.

2. Гротескная имитация или искажение фактов: Судебный процесс был карикатурой на справедливость.

тр.в. автомобиль · я · ка · тьюр , автомобиль · я · ка · тур , автомобиль · я · ка · туры

Для изображения или имитации в преувеличенной, искаженной форме.


[французский язык, от итальянского caricatura, от caricare, — загрузка, преувеличить , от позднего латинского carricāre, от латинского carrus, — фургон галльского типа ; см. kers- в индоевропейских корнях.]


car′i · ca · tur′ist n.

Словарь английского языка American Heritage®, пятое издание. Авторское право © 2016 Издательская компания Houghton Mifflin Harcourt. Опубликовано Houghton Mifflin Harcourt Publishing Company. Все права защищены.

карикатура

(ˈkærɪkəˌtj) n

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

2. смехотворно неадекватная или неточная имитация: он карикатура на государственного деятеля.

vb

( tr ) для представления в карикатуре или создания карикатуры на

[C18: от итальянского caricatura искажение, преувеличение, от caricare до загрузки, преувеличения; см. груз]

caricaˌtural adj

caricaˌturist n

Словарь английского языка Коллинза — полный и несокращенный, 12-е издание 2014 г. © HarperCollins Publishers 1991, 1994, 1998, 2000, 2003, 2006, 2007 , 2011, 2014

вагон • я • ка • тура

(ˈkær ɪ kə tʃər, -ˌtʃʊər)

n., v. -tured, -tur • ing. н.

1. изображение или описание, смехотворно преувеличивающие особенности или недостатки человека или вещи.

2. искусство или процесс создания таких изображений или описаний.

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

в.т.

4. сделать карикатуру.

[1740–50; <Итальянский caricatura, производное caricat (o) поражено, буквально, загружено]

car′i • ca • tur • ist, n.

Random House Словарь колледжа Кернермана Вебстера © 2010 K Dictionaries Ltd. Авторские права 2005, 1997, 1991, Random House, Inc. Все права защищены.

карикатура

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

См. Также: Представительство

-Ologies & -Isms. Copyright 2008 The Gale Group, Inc. Все права защищены.

карикатура

Past причастие: карикатурном
герундия: выставляя

ImperativePresentPreteritePresent ContinuousPresent PerfectPast ContinuousPast PerfectFutureFuture PerfectFuture ContinuousPresent Идеальный ContinuousFuture Идеальный ContinuousPast Идеальный ContinuousConditionalPast Условный

императив
карикатура
карикатура
они

Настоящее
Я карикатура
ты карикатура
он / она / она карикатуры
мы карикатуры
9016 906

они были

они были карикатура

Претерит
Я карикатурно
вы карикатурно
он / она / она карикатурно
мы карикатурно 90 162
вы карикатурно
они карикатурно

9015 карикатурируете

9016

9015 он карикатурирую карикатурные

настоящее время непрерывное
я карикатурно
вы карикатурируете 9016
вы карикатурные
они карикатурные

карикатурно

карикатурно

карикатурно

Present Perfect
Я карикатурно
у нас есть карикатуры
вы карикатурили
они карикатурили

9015 9

Прошлое непрерывное
это было карикатурно
мы карикатурировали
вы карикатурировали
они карикатурировали
9016 это было карикатурно
Past Perfect
Я карикатурно
у нас было карикатурно
вы карикатурно
они карикатурно

9016

будущее
9015
будет карикатурным 9016 / она / это будет карикатурно
мы будем карикатурно
карикатурно
они будут карикатурно
Future Perfect
d у вас будет автомобиль карикатурный
he / она / она будет карикатурно
у нас будет карикатура
у вас будет карикатура
они будут карикатурно

9015 вы будете карикатурно

Future Continuous
он / она / она будет карикатурно
мы будем карикатурно
вы будете карикатурно
они будут карикатурно

9015

Я карикатурировал
вы карикатурировали
он / она / она карикатурировали
мы карикатурировали
вы были карикатурировали
F uture Perfect Continuous
Я буду карикатурировать
вы будете карикатурно
он / она будет карикатурно
мы будем карикатурировать
карикатурировали
они будут карикатурировать
Past Perfect Continuous
Я карикатурировал
вы карикатурировали
он / он

мы карикатурировали
вы карикатурировали
они карикатурировали
условно
я бы карикатурно
9016 карикатура 9016 / это бы карикатура
мы карикатурно
карикатурно
карикатурно

карикатурное

карикатурили
прошлое условное
карикатурно
мы бы карикатурили
вы бы карикатурили
они бы карикатурили

Collins English Verb Tables © HarperCollins Publishers

caricatures недостатки или особенности человека или идеи.

Словарь незнакомых слов от Diagram Group © 2008, Diagram Visual Information Limited

.

определение карикатуры от The Free Dictionary

карикатура

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

Оскорбительные, запутанные и неправильно используемые слова Мэри Эмбри Авторские права © 2007, 2013 Мэри Эмбри

car · i · ca · ture

(kăr′ĭ-kə-cho͝or ′, -ch )r) n. 1.

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

б. Искусство создания подобных изображений.

2. Гротескная имитация или искажение фактов: Судебный процесс был карикатурой на справедливость.

тр.в. автомобиль · я · ка · тьюр , автомобиль · я · ка · тур , автомобиль · я · ка · туры

Для изображения или имитации в преувеличенной, искаженной форме.


[французский язык, от итальянского caricatura, от caricare, — загрузка, преувеличить , от позднего латинского carricāre, от латинского carrus, — фургон галльского типа ; см. kers- в индоевропейских корнях.]


car′i · ca · tur′ist n.

Словарь английского языка American Heritage®, пятое издание. Авторское право © 2016 Издательская компания Houghton Mifflin Harcourt. Опубликовано Houghton Mifflin Harcourt Publishing Company. Все права защищены.

карикатура

(ˈkærɪkəˌtj) n

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

2. смехотворно неадекватная или неточная имитация: он карикатура на государственного деятеля.

vb

( tr ) для представления в карикатуре или создания карикатуры на

[C18: от итальянского caricatura искажение, преувеличение, от caricare до загрузки, преувеличения; см. груз]

caricaˌtural adj

caricaˌturist n

Словарь английского языка Коллинза — полный и несокращенный, 12-е издание 2014 г. © HarperCollins Publishers 1991, 1994, 1998, 2000, 2003, 2006, 2007 , 2011, 2014

вагон • я • ка • тура

(ˈkær ɪ kə tʃər, -ˌtʃʊər)

n., v. -tured, -tur • ing. н.

1. изображение или описание, смехотворно преувеличивающие особенности или недостатки человека или вещи.

2. искусство или процесс создания таких изображений или описаний.

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

в.т.

4. сделать карикатуру.

[1740–50; <Итальянский caricatura, производное caricat (o) поражено, буквально, загружено]

car′i • ca • tur • ist, n.

Random House Словарь колледжа Кернермана Вебстера © 2010 K Dictionaries Ltd. Авторские права 2005, 1997, 1991, Random House, Inc. Все права защищены.

карикатура

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

См. Также: Представительство

-Ologies & -Isms. Copyright 2008 The Gale Group, Inc. Все права защищены.

карикатура

Past причастие: карикатурном
герундия: выставляя

ImperativePresentPreteritePresent ContinuousPresent PerfectPast ContinuousPast PerfectFutureFuture PerfectFuture ContinuousPresent Идеальный ContinuousFuture Идеальный ContinuousPast Идеальный ContinuousConditionalPast Условный

императив
карикатура
карикатура
они

Настоящее
Я карикатура
ты карикатура
он / она / она карикатуры
мы карикатуры
9016 906

они были

они были карикатура

Претерит
Я карикатурно
вы карикатурно
он / она / она карикатурно
мы карикатурно 90 162
вы карикатурно
они карикатурно

9015 карикатурируете

9016

9015 он карикатурирую карикатурные

настоящее время непрерывное
я карикатурно
вы карикатурируете 9016
вы карикатурные
они карикатурные

карикатурно

карикатурно

карикатурно

Present Perfect
Я карикатурно
у нас есть карикатуры
вы карикатурили
они карикатурили

9015 9

Прошлое непрерывное
это было карикатурно
мы карикатурировали
вы карикатурировали
они карикатурировали
9016 это было карикатурно
Past Perfect
Я карикатурно
у нас было карикатурно
вы карикатурно
они карикатурно

9016

будущее
9015
будет карикатурным 9016 / она / это будет карикатурно
мы будем карикатурно
карикатурно
они будут карикатурно
Future Perfect
d у вас будет автомобиль карикатурный
he / она / она будет карикатурно
у нас будет карикатура
у вас будет карикатура
они будут карикатурно

9015 вы будете карикатурно

Future Continuous
он / она / она будет карикатурно
мы будем карикатурно
вы будете карикатурно
они будут карикатурно

9015

Я карикатурировал
вы карикатурировали
он / она / она карикатурировали
мы карикатурировали
вы были карикатурировали
F uture Perfect Continuous
Я буду карикатурировать
вы будете карикатурно
он / она будет карикатурно
мы будем карикатурировать
карикатурировали
они будут карикатурировать
Past Perfect Continuous
Я карикатурировал
вы карикатурировали
он / он

мы карикатурировали
вы карикатурировали
они карикатурировали
условно
я бы карикатурно
9016 карикатура 9016 / это бы карикатура
мы карикатурно
карикатурно
карикатурно

карикатурное

карикатурили
прошлое условное
карикатурно
мы бы карикатурили
вы бы карикатурили
они бы карикатурили

Collins English Verb Tables © HarperCollins Publishers

caricatures недостатки или особенности человека или идеи.

Словарь незнакомых слов от Diagram Group © 2008, Diagram Visual Information Limited

.

определение карикатуры по The Free Dictionary

карикатура

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

Оскорбительные, запутанные и неправильно используемые слова Мэри Эмбри Авторские права © 2007, 2013 Мэри Эмбри

car · i · ca · ture

(kăr′ĭ-kə-cho͝or ′, -ch )r) n. 1.

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

б. Искусство создания подобных изображений.

2. Гротескная имитация или искажение фактов: Судебный процесс был карикатурой на справедливость.

тр.в. автомобиль · я · ка · тьюр , автомобиль · я · ка · тур , автомобиль · я · ка · туры

Для изображения или имитации в преувеличенной, искаженной форме.


[французский язык, от итальянского caricatura, от caricare, — загрузка, преувеличить , от позднего латинского carricāre, от латинского carrus, — фургон галльского типа ; см. kers- в индоевропейских корнях.]


car′i · ca · tur′ist n.

Словарь английского языка American Heritage®, пятое издание. Авторское право © 2016 Издательская компания Houghton Mifflin Harcourt. Опубликовано Houghton Mifflin Harcourt Publishing Company. Все права защищены.

карикатура

(ˈkærɪkəˌtj) n

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

2. смехотворно неадекватная или неточная имитация: он карикатура на государственного деятеля.

vb

( tr ) для представления в карикатуре или создания карикатуры на

[C18: от итальянского caricatura искажение, преувеличение, от caricare до загрузки, преувеличения; см. груз]

caricaˌtural adj

caricaˌturist n

Словарь английского языка Коллинза — полный и несокращенный, 12-е издание 2014 г. © HarperCollins Publishers 1991, 1994, 1998, 2000, 2003, 2006, 2007 , 2011, 2014

вагон • я • ка • тура

(ˈkær ɪ kə tʃər, -ˌtʃʊər)

n., v. -tured, -tur • ing. н.

1. изображение или описание, смехотворно преувеличивающие особенности или недостатки человека или вещи.

2. искусство или процесс создания таких изображений или описаний.

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

в.т.

4. сделать карикатуру.

[1740–50; <Итальянский caricatura, производное caricat (o) поражено, буквально, загружено]

car′i • ca • tur • ist, n.

Random House Словарь колледжа Кернермана Вебстера © 2010 K Dictionaries Ltd. Авторские права 2005, 1997, 1991, Random House, Inc. Все права защищены.

карикатура

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

См. Также: Представительство

-Ologies & -Isms. Copyright 2008 The Gale Group, Inc. Все права защищены.

карикатура

Past причастие: карикатурном
герундия: выставляя

ImperativePresentPreteritePresent ContinuousPresent PerfectPast ContinuousPast PerfectFutureFuture PerfectFuture ContinuousPresent Идеальный ContinuousFuture Идеальный ContinuousPast Идеальный ContinuousConditionalPast Условный

императив
карикатура
карикатура
они

Настоящее
Я карикатура
ты карикатура
он / она / она карикатуры
мы карикатуры
9016 906

они были

они были карикатура

Претерит
Я карикатурно
вы карикатурно
он / она / она карикатурно
мы карикатурно 90 162
вы карикатурно
они карикатурно

9015 карикатурируете

9016

9015 он карикатурирую карикатурные

настоящее время непрерывное
я карикатурно
вы карикатурируете 9016
вы карикатурные
они карикатурные

карикатурно

карикатурно

карикатурно

Present Perfect
Я карикатурно
у нас есть карикатуры
вы карикатурили
они карикатурили

9015 9

Прошлое непрерывное
это было карикатурно
мы карикатурировали
вы карикатурировали
они карикатурировали
9016 это было карикатурно
Past Perfect
Я карикатурно
у нас было карикатурно
вы карикатурно
они карикатурно

9016

будущее
9015
будет карикатурным 9016 / она / это будет карикатурно
мы будем карикатурно
карикатурно
они будут карикатурно
Future Perfect
d у вас будет автомобиль карикатурный
he / она / она будет карикатурно
у нас будет карикатура
у вас будет карикатура
они будут карикатурно

9015 вы будете карикатурно

Future Continuous
он / она / она будет карикатурно
мы будем карикатурно
вы будете карикатурно
они будут карикатурно

9015

Я карикатурировал
вы карикатурировали
он / она / она карикатурировали
мы карикатурировали
вы были карикатурировали
F uture Perfect Continuous
Я буду карикатурировать
вы будете карикатурно
он / она будет карикатурно
мы будем карикатурировать
карикатурировали
они будут карикатурировать
Past Perfect Continuous
Я карикатурировал
вы карикатурировали
он / он

мы карикатурировали
вы карикатурировали
они карикатурировали
условно
я бы карикатурно
9016 карикатура 9016 / это бы карикатура
мы карикатурно
карикатурно
карикатурно

карикатурное

карикатурили
прошлое условное
карикатурно
мы бы карикатурили
вы бы карикатурили
они бы карикатурили

Collins English Verb Tables © HarperCollins Publishers

caricatures недостатки или особенности человека или идеи.

Словарь незнакомых слов от Diagram Group © 2008, Diagram Visual Information Limited

.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

2021 © Все права защищены. Карта сайта