Как стать автором
Обновить
272.5
Конференции Олега Бунина (Онтико)
Профессиональные конференции для IT-разработчиков

Высокодоступный MySQL на конвейере

Время на прочтение15 мин
Количество просмотров3.7K

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

Поможет нам в этом Дмитрий Смаль, руководитель подразделения Managed MySQL и SQL Server в Yandex Cloud. Каждый день он занимается эксплуатацией и автоматизацией СУБД, а до этого делал нагруженные и не очень web-приложения. В целом любит делать разные интересные штуковины, которые работают! Сегодня мы с ним будем говорить про решение для высокодоступного MySQL.

MySQL в Яндексе 

В Яндексе есть две инсталляции с MySQL.

Одна из них внутри Яндекса для проектов самого Яндекса (Яндекс Директ, Adfox, Кинопоиск, Вертикали).

На сентябрь 2022-го Yandex Managed Service for MySQL это:

  • 600 TB

  • ~ 500 кластеров

  • 1000+ хостов

  • 1–15 хостов в кластере

Кластера в инсталляции очень разные. Кажется, что в каждом по 2 хоста, судя по соотношению кластеров и хостов, но на самом деле это не так. Есть тестовые кластера с одним хостом и такие, в которых 15 реплик.

И еще есть внешняя инсталляция в Яндекс.Облаке, где находятся кластера клиентов. Там их настолько много, что уже невозможно администрировать руками. В некоторых случаях вручную можно чинить, но не обслуживать.

Несколько вводных

Есть две разновидности событий, которые важно учитывать при эксплуатации:

  1. Ожидаемые сценарии. К ним можно отнести:

  • Учения в дата-центрах, о которых сообщают за месяц, если не раньше.

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

  • Изменения и апгрейд кластеров клиентами. Плюс, клиенты могут захотеть обновить MySQL, сделать какую-то операцию, при которой БД будет долго перезагружаться.

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

  1. Неожиданные поломки, происходящие без предупреждения:

  • железа под кластерами: памяти, дисков, гипервизоров — это происходит довольно часто, а на больших объёмах фактически ежедневно;

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

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

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

Задача

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

Яндекс поставил Дмитрию задачу достичь таких параметров:

  • доступность 99,99% в месяц на чтение;

  • доступность 99,95 % в месяц на запись;

  • спокойный сон дежурного;

  • лёгкая автоматизация.

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

  1. Switchover

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

  2. Failover

    Отвечает за то же самое, что и Switchover, но тогда, когда происходит авария.

  3. Переналивку реплики с мастера

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

Поэтому также желательно настраивать:

  • Бэкапы с PITR: хорошо бы делать периодические бэкапы, чтобы у них был Point In Time Recovery. Тогда можно будет наливать новые хосты без превышения нагрузки на кластер.

  • Переналивку из бэкапа.

  • Интеграцию с Service Discovery. Представим, что есть мастер, который переключается туда-сюда: с одного хоста на другой. Как проекту дать знать об этом? В случае с Postgres клиентская библиотека сама умеет определять, кто сейчас мастер и подключаться к нужному. В случае с MySQL для этого используется, например, Consul или DNS-запись, которая указывает на текущий мастер.

Варианты решения

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

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

  • Severalnines ClusterControl (платный сервис).

Ещё были разного рода утилиты, например:

  • MySQL Group Replication;

  • Master High Availability (MHA). 

Эта утилита предполагает наличие некоторого выделенного хоста. Например, если есть хосты с MySQL и вы предполагаете, что есть хост, который авария не затронет, то MHA позволит администрировать всю систему с этого хоста.

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

Что умеет MySync

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

→ Все хосты внутри эквивалентны, то есть кластер гомогенный;

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

→ Идемпотентность операций;

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

→ Без потери данных!

Нельзя решать за клиентов, какие данные им можно терять, а какие — нет.

Архитектура

MySync — это демон, который работает рядом с базой MySQL.

Здесь есть три хоста: primary и 2 реплики. Ещё используется DCS — это внешняя по отношению к кластеру база, обеспечивающая хранилище, устойчивое к сетевым сбоям. В Яндексе это zookeeper (в принципе, можно прикрутить и TCD). Благодаря этому хранилищу не приходится решать проблему с распределёнными транзакциями: не нужно реализовывать никакие Raft и Paxos внутри MySync. Они реализованы внешне благодаря внешнему хранилищу.

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

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

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

Процесс переключения мастера

Когда всё идет хорошо, то переключение (switchover) делается довольно просто. Вот что нужно сделать:

Перевести все ноды кластера в состояние read_only;

Это позволит сравнить и понять, какой кластер впереди. Нельзя сравнивать кластера, если в них продолжается запись.

Остановить репликацию, чтобы реплики не меняли свои позиции;

Выбрать лучшую реплику;

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

Повернуть все остальные реплики на самую лучшую реплику;

То есть мы промоутим, делаем лучшую реплику мастером.

Открыть новый мастер;

Обновить записи в DNS и в zookeeper.

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

Проверка состояния мастера

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

Относительно MySync были выбраны 2 способа, которые нужно обрабатывать:

  • Наличие соединения с DCS. Наличие сети определяется по наличию соединения с zookeeper. Пока хост держит соединение с zookeeper, мы считаем, что есть сеть;

  • Сервер отвечает на SELECT 1. Если кластер MySQL жив, то он отвечает на SELECT 1.

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

Но есть 99 способов ошибиться, в частности:

  • Файловая система может перейти в RO, а MySQL может не упасть. То есть сервер неработоспособен, но на SELECT 1 отвечает; 

  • Исчерпание ресурсов. Перегрузка может быть настолько большая, то сервер будет в течение минуты устанавливать TCP-соединение;

У нас были ситуации, когда работали все текущие TCP-соединения, а новые не устанавливались. Вы понимаете, что такое для клиента, когда не устанавливаются новые соединения, он не может подключиться к базе. Репликация работает — это старое соединение. Соединение с zookeeper работает, MySync пишет, что всё нормально, все живы. Но некоторые проблемы не обрабатываются.

  • Проблемы с DCS. Кластер может нормально работать, а zookeeper — просто быть сломанным.

Был интересный случай, когда мы ошиблись и неправильно разместили zookeepers по дата-центрам. По-хорошему, когда вы проектируете DCS, базу, которая обеспечивает устойчивость к повреждениям сети, если у вас есть три ноды zookeeper, вам нужно их расположить по разным дата-центрам. У нас получилось так, что 2 ноды оказались в одном дата-центре и его оторвало от сети. Но поскольку там находились 2 ноды, они обеспечивали кворум для zookeeper, поэтому тот, кто находился в оторванном дата-центре, продолжал считать себя мастером. Это miss configuration, с которым мы боролись руками. Такое случается.

Синхронная репликация в MySQL

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

Как работает вообще репликация в MySQL? Вы выполняете транзакции на мастере. Они записываются в binary log, который передаётся на реплику. Там он уже называется relay log. Реплика выполняет изменения.

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

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

Кворум

Есть распределённая система из N узлов. Мы записываем и читаем данные на какое-то количество узлов. Как должны эти цифры соотноситься, чтобы мы гарантированно читали актуальные данные? Допустим, мы записываем данные на W узлов, а читаем данные с R узлов. Это значит, что мы опрашиваем R узлов и выбираем самые актуальные среди тех, что запросили. Для этого должно быть верным следующее утверждение:

W+R >= N+1

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

В случае с MySQL и Postgres-R речь идёт не про количество узлов, с которых вы читаете, а про количество узлов, среди которых вы выбираете нового мастера. А W — это количество узлов, на которых гарантированно записана транзакция. В MySQL есть настройка rpl_semi_sync_master_wait_for_slave_count — это количество реплик, а количество W будет на единицу больше.

Рассмотрим на примере. Есть кластер из 6 хостов, которые находятся в 3 дата-центрах. Мы записываем данные в мастер. Плюс, есть настройка, что мастер ожидает одну реплику. Сколько реплик нужно иметь живыми после инцидента? Если есть мастер и он ожидает одну реплику, то живыми нужно иметь 5 реплик из 6. При этом две из них — W, а пять — R. То есть после инцидента должно остаться в живых 5 реплик для того, чтобы не потерять данные.

Очевидно, что в таком случае не получится пережить падение дата-центра, потому что при падении в живых останутся только 4 реплики.

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

Дмитрий советует запомнить эту формулу. Она полезна при проектировании распределённых систем, как самое базовое представление.

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

Каскадная репликация и приоритеты

Есть поддержка каскадной репликации и приоритетов.

Каскадные реплики — это реплики второго уровня, реплики реплик (на схеме чёрные). Зачем они нужны? Мы делим хосты кластера на два вида: 

  1. Мастер и высокодоступные реплики HA вместе образуют HA группу. Это те хосты, которые теоретически могут стать мастером.

  2. Каскадные реплики.

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

Есть хороший пример. В Яндексе есть три основных дата-центра и разные кластера, у которых, грубо говоря, 15 хостов. Попробуем разложить по 3 дата-центрам 15 хостов и посчитать, какая должна быть настройка, чтобы не терять данные. Если 15 хостов распределены по 3 дата-центрам, нужно ожидать подтверждение от 5 реплик. Это очень медленно. При синхронной репликации удобно делать небольшую HA-группу, а все дополнительные реплики, которые обслуживают нагрузку, делать каскадными.

MySync поддерживает топологию и исправляет её при необходимости. Есть приоритеты: на схеме они указаны цифрами. Их смысл в том, что при переключении MySync выбирает наиболее приоритетную реплику. Это сделано для того, чтобы мастер не попадал в какие-нибудь нежелательные дата-центры: на старое оборудование и прочее. Это позволяет выбрать приоритеты при прочих равных. Если получается так, что приоритетная реплика сильно отстаёт по каким-то причинам, будет выбрана актуальная.

Режим maintenance

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

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

Переналивка реплик

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

Но если MySync понимает, что реплика не актуальна, повреждена, то он ставит специальный touch-файл, который появляется в файловой системе. Далее можно написать скрипт, чтобы переналить реплику. После MySync удалит файлик, реплика будет считаться переналитой, и MySync возвратит её в кластер.

Как всё испортить? 

Грамотное (или неграмотное) использование MySQL может всё сломать. 

Что плохого может сделать пользователь СУБД?

  • Большие транзакции

    В OLTP-системах не надо делать большие транзакции. Транзакции должны быть маленькими, потому что большую транзакцию тяжело прервать. В Postgres это ещё не так плохо, как в MySQL. Если вы делаете switchover, а у вас бежит часовой update, то вам нужно что-то с ним делать. Всё, что вы можете с ним сделать — это kill, то есть прервать транзакцию. Но она не остановится, а начнёт откатываться, и это займёт время.

  • Отстающие реплики

    Если мы хотим сделать failover на отстающую реплику, надо будет подождать, пока она догонится. Соответственно, обещанные 10-20 секунд потратятся на это процесс.

  • Ненадёжная конфигурация MySQL

    Можно неграмотно настроить MySQL так, что он будет падать чаще.

Как избежать этих ошибок?

Чтобы не делать плохо, стоит преследовать самые лучшие практики:

  • Правильные индексы в таблицах

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

  • Небольшие транзакции

    Если нужно загрузить много, то стоит грузить маленькими чанками. Это «ОК».

  • Pt-online-schema-change

    Не нужно делать больших alter. Вместо них используйте, например, перконовскую утилиту online-schema-change или сделайте что-то своё на триггерах, чтобы это не было большой транзакцией. Синхронные репликации и большие транзакции работают особенно плохо.

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

  • slave_rows_search_algorithms = INDEX_SCAN, HASH_SCAN
    Параллельная репликация, которая действительно ускоряет процесс.

  • slave_parallel_type = LOGICAL_CLOCK
    Репликация на основе LOGICAL_CLOCK, когда маленькие независимые транзакции на репликах исполнятся параллельно

  • slave_parallel_workers = 8
    Параллельная репликация на основе  write sets.

Но всё это тоже не панацея.

MySync. Как попробовать?

MySync выложен в опенсорс. Его можно скачать с GitHub:

git clone git@github.com:yandex/mysync.git

А также собрать:

go build -o mysync ./cmd/mysync/...

Проект написан на Go, поэтому он так компилируется. Но там есть ещё makefile. Вы получаете один бинарник, раскладываете на свои кластера и пробуете.

Настройка MySync

Прежде, чем использовать MySync, его нужно настроить. Вот пример настроек MySync:

failover: true

failover_cooldown: 3600s

failover_delay: 60s

zookeeper:

 namespace: /mysql/mycluster_1

 hosts:

  - zk-dbaas02f.db.yandex.net

  - zk-dbaas02h.db.yandex.net 

  - zk-dbaas02k.db.yandex.net

mysql:

 user: admin

 password: *******

 port: 3306

 replication_port: 3306

 replication_user: repl

 replication_password: *******

Сначала указано, что вообще делает failover, а потом начинаются основные настройки:

  • cooldown — параметр, который отвечает за то, чтобы failover был, например, не чаще, чем раз в час. Слишком часто делать не надо: потому что если пользователь ломает кластер нагрузки, он сломает и реплику; 

  • delay — время, которое даётся мастеру, чтобы он очнулся, прежде чем случился failover;

  • Настройки подключения к zookeeper;

  • Настройки подключения к MySQL.

Рекомендуемые настройки MySQL

Есть 3 рекомендации.

Репликация 

Binlog должны быть включены, причём на репликах тоже, потому что реплика в любой момент может стать мастером. Реплика тоже должна писать свои binlog. Формат binlog строчный, поэтому чтобы данные не расходились, нужно делать репликацию по строкам, а не по стейтментам.

Также использование gtid — это must have; некоторый аналог LSN в Postgres; вещь, которая позволяет понять, в каком состоянии находится ваша позиция в базе.

binlog_format=ROW

gtid_mode=ON

enforce_gtid_consistency=ON

log_slave_updates=ON

Сохранность данных

Чтобы данные не терялись, нужно включить fsync, синхронизировать binlog и гарантировать транзакциям записи sync’ом в диск.

innodb_flush_log_at_trx_commit=1

sync_binlog=1

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

Избегаем split-brain

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

read_only=ON

super_read_only=ON

offline_mode=ON

В этом состоянии запись никуда не принимается и не видна для пользователя. То есть по умолчанию хост запускается невидимым, а MySync потом смотрит, входит хост в кластер или нет. Если входит, то он его вернёт и откроет.

Как начать использовать MySync?

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

# собрать первоначальную конфигурацию руками
# сообщить MySync о хостах
$ mysync host add node1.db.your.project.com
$ mysync host add node2.db.your.project.com
$ mysync host add node1.db.your.project.com

# посмотреть информацию
$ mysync info -s

# переключить мастер 
$ mysync switch --to node2
$ mysync switch --from node2

# maintenance
$ mysync maint on|off

Далее, с помощью команды mysync switch можно выгонять или приводить мастера в нужное место. Ещё можно включить или выключить maintenance.

Дополнительные возможности

В MySync есть дополнительные плюшки:

  • «Починка» кластера

    MySync умеет чинить не только кластер, состояние мастера, но ещё и реплики, то есть он может поворачивать их в нужное место; если на них не включен read_only, он его включает. В целом, он приводит состояние кластера к ожидаемой топологии: чинит её постоянно.

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

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

  • Закрытие (и открытие) отстающих реплик

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

Итоги и планы развития

Если MySync работает как запланировано, то мы имеем:

  • Довольно быстрый switchover (~10–20 c.). И это с запасом. На ненагруженных кластерах переключение может быть ещё быстрее.

  • Failover ~1–2 мин. Причём большую часть времени в failover — это специальное ожидание на случай, если мастер всё-таки успеет очнуться, перезагрузиться, восстановиться.

При этом переключение происходит без потери данных и может последовательно деградировать, то есть переходить с N на 1 хост. Например, если есть 3 хоста, то можно последовательно пережить 2 аварии: сначала переключиться на один хост, потом на второй.

Что касается планов, то Дмитрий с командой собираются ещё больше улучшить MySync, добавить к нему следующие возможности:

  • Поддержка каналов репликации. Можно поддерживать разные каналы репликации, чтобы в кластере помимо топологии, которая поддерживается MySync, была ещё внешняя пользовательская репликация между кластерами для импорта/экспорта данных.

  • Поддержка MariaDB.

  • Поддержка Etcd вместо zookeeper.

  • LibMySync, API. Можно сделать библиотеку для работы с этими структурами в zookeeper, потому что сейчас есть только два способа, как управлять MySync. Это либо через command line нужно зайти на хост, либо нужно идти в zookeeper и правильным образом редактировать записи. Это редактирование записей можно аккуратно обернуть. Так как принимаются pull requests, то здесь есть куда копать.

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

Теги:
Хабы:
Всего голосов 11: ↑11 и ↓0+11
Комментарии2

Публикации

Информация

Сайт
www.ontico.ru
Дата регистрации
Дата основания
Численность
31–50 человек
Местоположение
Россия