postgresql cluster что это

Отказоустойчивый кластер PostgreSQL с помощью crm

Автор — Игорь Косенков, инженер Postgres Professional

Привет всем! Сегодня речь пойдет о кластере. Да, снова об отказоустойчивом кластере на базе Corosync/Pacemaker. Только настраивать мы его будем не как обычно — с помощью утилиты pcs, а с помощью мало используемой утилиты crm.

С точки зрения использования этих утилит (pcs и crm) весь мир Unix-like операционок делится на два вида:

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

Решено было исправить этот пробел и написать статью.

Причины, по которым те или иные разработчики дистрибутивов предпочитают кто crm, а кто pcs, мне неизвестны. Могу предположить, что все дело в зависимостях. Например, если сравнить количество зависимостей у pcs и crm, то получается такая картина:

Сторонники минимализма, скорей всего, предпочтут crmsh. А если еще учесть, что pcs «тянет» за собой ruby, openssl, pam и python, а crmsh только python, то выбор в некоторых случаях будет однозначно на стороне crm. В каких случаях? Ну, например, при сертификации ОС есть некоторые трудности с пакетом ruby. Также известны случаи, когда в банковских структурах служба безопасности не разрешает установку нерегламентированного ПО.

Сходства и различия

У утилиты crm есть как сходства, так и различия с известной всем утилитой pcs.
Сходства утилит приведены в таблице 1:

postgresql cluster что это. Смотреть фото postgresql cluster что это. Смотреть картинку postgresql cluster что это. Картинка про postgresql cluster что это. Фото postgresql cluster что это

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

postgresql cluster что это. Смотреть фото postgresql cluster что это. Смотреть картинку postgresql cluster что это. Картинка про postgresql cluster что это. Фото postgresql cluster что это

Различия начинаются с самого начала — с момента инициализации кластера. Инициализировать кластер с помощью crm можно одной командой, а у pcs это происходит в два этапа — авторизация и инициализация.

Удалить кластер («разобрать») у pcs можно одной командой сразу, а у crm необходимо удалять по одному узлу до тех пор, пока их не останется в кластере.

Чтобы изменить параметры ресурса, который мы уже создали в кластере, у pcs есть опция update. У crm такой опции нет, но есть команда configure edit, которая позволяет менять любые настройки кластера налету и мгновенно. Даже больше — мы можем за один прием отредактировать любое количество параметров и ресурсов, и в конце редактирования применить все изменения сразу. Удобно? Думаю, да.

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

У crm в стандартной поставке нет веб-инструмента, но зато он есть в коммерческой версии SUSE — HAWK.

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

Лучший способ узнать и познакомиться с crm — это настроить отказоустойчивый кластер.

Чем мы сейчас и займемся. Для примера возьмем ОС CentOS 7.9.

Для создания отказоустойчивого кластера PostgreSQL нам понадобится стенд, состоящий из 3-х узлов — node1, node2, node3. На каждом узле установлена ОС CentOS 7.9 и пакеты corosync, pacemaker, fence-agents* (агенты фенсинга).

В качестве СУБД будем использовать Postgres Pro Standard v.11, но вы можете с таким же успехом использовать «ванильную» версию PostgreSQL. В нашей системе установлены необходимые пакеты — postgrespro-std-11-server, postgrespro-std-11-libs, postgrespro-std-11-contrib, postgrespro-std-11-client.

Настройки СУБД (postgresql.conf) и доступа к ней (pg_hba.conf) не рассматриваются в данной статье, информации об этом достаточно в интернете. На одном из узлов (например, node1) необходимо инициализировать базу данных с помощью initdb, а на двух других узлах с помощью pg_basebackup скопировать базу данных с node1.

Кроме того, в будущем кластере должны быть настроены синхронизация времени на узлах и разрешение имен узлов в кластере с помощью файла /etc/hosts.

ПРИМЕЧАНИЕ:
В этом разделе все команды необходимо выполнить на всех узлах кластера.

Поскольку пакет crmsh не входит в состав дистрибутива ОС, то необходимо подключить репозиторий Extra OKay Packages for Enterprise Linux с этим пакетом.

Нам также понадобится репозитарий EPEL:

Устанавливаем пакет crmsh:

Сервис csync2 может использоваться не только для создания отказоустойчивого кластера Corosync/Pacemaker. Например, если есть несколько серверов, у которых меняются конфигурационные файлы и эти файлы периодически нужно синхронизировать по критерию «самый свежий файл».

Итак, устанавливаем csync2 и простейшую базу данных для хранения мета-данных (sqlite).

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

Поскольку csync2 и crmsh не являются «родными» для CentOS, то без дополнительных «танцев» сразу после установки они не заработают. Вызов crm влечет вызов утилиты csync2, которой в свою очередь не хватает парочки systemd-юнитов. Почему этих файлов нет в пакете csync2 для CentOS — мне неизвестно. Замечу, что в коммерческом дистрибутиве SLES (crmsh там «родной») все необходимые файлы есть, все работает из коробки сразу после установки пакетов.

Итак, создадим и добавим недостающие systemd-юниты.

Первый называется csync2.socket и содержит:

Второй называется csync2@.service с таким содержимым:

Оба файла нужно разместить в стандартной папке systemd — /usr/lib/systemd/system.

Юнит, относящийся к сокету, нужно активировать и установить в автозапуск при загрузке ОС:

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

Теперь у нас все готово к началу работ по настройке кластера.

Настройка кластера с помощью crm

Настройка кластера производится в 2 этапа — инициализация, затем создание и добавление ресурсов. Инициализация кластера с настроенным сервисом синхронизации конфигураций csync2 производится на одном узле.

Если по каким-то причинам вам не удалось «победить» этот сервис, то это не беда, в конце статьи я расскажу, как обойтись без csync2.

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

Далее надо выполнить команду инициализации кластера:

где demo-cluster — название нашего кластера.

По этой команде создаются необходимые файлы в папке /etc/corosync: corosync.conf, ключ авторизации authkey, а также прописываются ssh-ключи для беспарольной авторизации и выполнения команд в кластере с привилегиями суперпользователя root (на всех трех узлах кластера).

По умолчанию инициализация кластера выполняется в режиме multicast. Но есть также возможность проинициализировать кластер в режиме unicast:

Кластер проинициализирован и запущен.

Проверить работоспособность можно с помощью консольного монитора состояния кластера crm_mon:

Далее можно приступать к созданию ресурсов в кластере.

Создание ресурсов в кластере

Для начала поменяем некоторые значению по умолчанию. Например, порог миграции ресурсов migration-threshold по умолчанию равен 0. Меняем на 1, чтобы после первого сбоя ресурсы мигрировали на другой узел.

По умолчанию кластер запускается в симметричном режиме.

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

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

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

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

Мы оставим этот параметр без изменения.

Включаем механизм stonith:

Создадим и добавим ресурс «виртуальный IP адрес»:

где — виртуальный IP-адрес в кластере.

С помощью монитора состояния кластера crm_mon можно убедиться в том, что ресурс успешно создан и запущен на первом попавшемся узле:

Создадим ресурс postgresql и назовем его pg:

В данном примере пути расположения бинарников и БД указаны по умолчанию для версии Postgres Pro Std 11. Также для упрощения указан пользователь для репликации «postgres». Но ничто не мешает вам изменить «умолчательные» пути и пользователя репликации на свои.

Хочу обратить внимание на параметр rep_mode: он задан «sync». Это означает, что в отказоустойчивом кластере хотя бы одна реплика будет синхронной. Синхронность реплики в кластере обеспечивает RPO=0 (кластер без потерь данных в случае сбоя).

Зададим тип ресурса Master-Standby (ms):

Нам нужно, чтобы ресурсы vip-master и mspg в режиме «мастер» запускались на одном узле:

Указываем порядок запуска ресурсов – сначала СУБД в режиме «мастер», потом виртуальный IP:

Таким образом, мы создали 2 необходимых ресурса — виртуальный IP адрес и ресурс postgresql.

Теперь можно переходить к настройке фенсинга в отказоустойчивом кластере.

Фенсинг узлов

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

Для начала можно ознакомиться со списком всех агентов фенсинга:

На моем стенде node1, node2, node3 — это виртуальные машины, которые запущены и управляются с помощью гипервизора KVM. Соответственно, ресурс-агент фенсинга для KVM называется fence_virsh.

Вывести полную информацию о fence_virsh:

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

Проверка работоспособности фенсинга для узла node1 выглядит так:

где username & password — учетная запись на хосте гипервизора.

Фенсинг для node1 настраивается так:

ПРИМЕЧАНИЕ:
Ресурсы фенсинга не должны запускаться на «своих» узлах, иначе фенсинг может не сработать.

Следующее правило расположения запретит ресурсу фенсинга для узла node1 располагаться на этом узле:

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

Инициализация кластера с помощью crm без csync2

Как обещал выше, расскажу про вариант инициализации кластера без установки и настройки csync2 (если по каким-то причинам вам не удалось его настроить).

Сначала вариант с использованием multicast.

Все команды выполняются на одном узле, например, на node1.

По этой команде создаются необходимые файлы в папке /etc/corosync: corosync.conf, ключ авторизации authkey.

Далее нужно скопировать авторизационный файл authkey и corosync.conf на узлы node2 и node3:

На остальных узлах (на node1 кластер уже запущен) запустить кластер:

С помощью монитора crm_mon можно убедиться, что кластер проинициализирован и запущен:

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

Все команды выполняются на одном узле, например, на node1.

Открываем файл /etc/corosync/corosync.conf и добавляем строки в секцию nodelist:

В секции quorum меняем число голосов:

Далее необходим рестарт сервиса corosync на первом узле:

Затем нужно скопировать файл authkey и отредактированный corosync.conf на узлы node2 и node3:

На остальных узлах (на node1 кластер уже запущен) запустить кластер:

С помощью монитора crm_mon можно убедиться, что кластер проинициализирован и запущен:

На этом инициализация кластера без csync2 закончена.

Вспомогательные команды crm

При работе с кластером могут пригодиться некоторые crm-команды.

Для удобства команды и пояснения сведены в таблицу 3:

postgresql cluster что это. Смотреть фото postgresql cluster что это. Смотреть картинку postgresql cluster что это. Картинка про postgresql cluster что это. Фото postgresql cluster что это

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

Источник

Postgresql cluster что это

Подсказка

Этот вариант может быть удобнее, если вы используете pg_ctl для запуска и остановки сервера (см. Раздел 18.3), так как pg_ctl будет единственной командой, с помощью которой вы будете управлять экземпляром сервера баз данных.

Команда initdb попытается создать указанный вами каталог, если он не существует. Конечно, она не сможет это сделать, если initdb не будет разрешено записывать в родительский каталог. Вообще рекомендуется, чтобы пользователь PostgreSQL был владельцем не только каталога данных, но и родительского каталога, так что такой проблемы быть не должно. Если же и нужный родительский каталог не существует, вам нужно будет сначала создать его, используя права root, если вышестоящий каталог защищён от записи. Таким образом, процедура может быть такой:

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

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

Команда initdb также задаёт кодировку символов по умолчанию для кластера баз данных. Обычно она должна соответствовать кодировке локали. За подробностями обратитесь к Разделу 23.3.

18.2.1. Использование дополнительных файловых систем

Источник

Кластер PostgreSQL внутри Kubernetes: что нужно знать для успешного внедрения

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

postgresql cluster что это. Смотреть фото postgresql cluster что это. Смотреть картинку postgresql cluster что это. Картинка про postgresql cluster что это. Фото postgresql cluster что это

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

Для погружения — (совсем) краткая история PostgreSQL

Потоковая репликация

Она появилась в 2010 году вместе с релизом PostgreSQL 9.0. Прошло уже более 10 лет, однако, пока ни один релиз не включил в себя штатные механизмы переключения между мастером и репликой. Из-за этого могут возникать различные проблемы. Рассмотрим, какие они бывают, на типичных кейсах.

Кейс №1 — идеальный (без потери данных)

Тут все просто: у нас есть мастер и полностью синхронизированная с мастером реплика. В какой-то момент мы «аккуратно» останавливаем мастер и делаем standby новым мастером. В итоге мы, не теряя данные, корректно продолжаем работу.
postgresql cluster что это. Смотреть фото postgresql cluster что это. Смотреть картинку postgresql cluster что это. Картинка про postgresql cluster что это. Фото postgresql cluster что это
Кейс №2 — более проблемный (часть данных потеряна)

Представим, у нас работал мастер, но по каким-то причинам он аварийно завершил работу, и часть данных не успела «доехать» до standby. Мы принимаем решение сделать standby новым мастером. В этом случае теряется небольшая часть данных, которая не успела передаться с мастера до standby.
postgresql cluster что это. Смотреть фото postgresql cluster что это. Смотреть картинку postgresql cluster что это. Картинка про postgresql cluster что это. Фото postgresql cluster что это
Кейс №3 — самый неприятный (split brain)

В этом случае у нас также есть мастер и standby, но в какой-то момент, допустим, мастер перестал быть доступным по сети. И мы приняли решение активировать standby на запись и сделать его мастером. Но вот мастер опять вернулся в строй, он работает, а мы находимся в ситуации, когда в системе находятся одновременно два мастера, и приложение может также записывать данные в оба мастера.
postgresql cluster что это. Смотреть фото postgresql cluster что это. Смотреть картинку postgresql cluster что это. Картинка про postgresql cluster что это. Фото postgresql cluster что это

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

Есть ли решение?

Тут мы не забываем, что PostgreSQL — opensource-продукт, и его можно дорабатывать. Сторонние компании разрабатывали свои утилиты для обеспечения high availability и автоматического переключения между master и standby. Самые популярные из них:

Знакомство с Patroni

Patroni показал себя как тот самый вожак стада, который с течением времени проявил себя как самый сильный и выносливый слон. Данная утилита сейчас является де-факто стандартом для обеспечения high availability для PostgreSQL.

postgresql cluster что это. Смотреть фото postgresql cluster что это. Смотреть картинку postgresql cluster что это. Картинка про postgresql cluster что это. Фото postgresql cluster что это

Остановимся подробнее на архитектуре утилиты: у нас есть сервера, на которых установлен PostgreSQL. Между собой они связаны потоковой репликацией. Рядом с PostgreSQL установлен Patroni, который умеет управлять PostgreSQL, — останавливать, запускать, перезапускать, автоматически создавать и пересоздавать standby, если это требуется.

postgresql cluster что это. Смотреть фото postgresql cluster что это. Смотреть картинку postgresql cluster что это. Картинка про postgresql cluster что это. Фото postgresql cluster что это
Теперь появляется следующий компонент — DCS (distributed consensus system).
Из названия уже понятно, что эта система нужна для обеспечения консенсуса. С помощью DCS мы однозначно можем определить, где у нас мастер. И если у нас возникают с ним какие-то проблемы, то этот компонент позволяет нам выбрать новый мастер и продолжить работу с ним. В качестве компонента DCS могут выступать: etcd, протокол raft, Kubernetes, zookeeper, aws callbacks и так далее. Самые интересные для нас — первые три.

postgresql cluster что это. Смотреть фото postgresql cluster что это. Смотреть картинку postgresql cluster что это. Картинка про postgresql cluster что это. Фото postgresql cluster что это

Еще один компонент — Load balancer, он опциональный в архитектуре Patroni. Может быть полезен для балансировки нагрузки на primary или на standby. Еще один случай применения Load balancer — необходимость единой точки входа к нашим PostgreSQL-базам. Вы всегда можете подключиться к одному и тому же IP, который, в свою очередь, уже будет прикреплен к серверу, где у нас располагается мастер. Внутри Райффайзенбанка в качестве Load balancer для Patroni мы используем vip-manager.

postgresql cluster что это. Смотреть фото postgresql cluster что это. Смотреть картинку postgresql cluster что это. Картинка про postgresql cluster что это. Фото postgresql cluster что это

Гладя на такую архитектуру, с высокой доступностью и отказоустойчивостью, возникла идея — а что если перенести ее в Kubernetes?

Воплощаем задуманное: PostgreSQL кластер внутри Kubernetes

Механизмы для обеспечения high availability

Начнем с контроллеров — Deployment, позволяет управлять stateless-приложениями, и StatefulSet, позволяет управлять stateful-приложениями. Возможно, непонятно, что это за слова.

Stateless-приложения — приложения, которым не нужно хранить свое состояние. Самым популярным примером для stateless-приложения является web-сайт. Ему необязательно хранить свое состояние. Вы размещаете его в виде deployment внутри Kubernetes, и он отлично, при необходимости, масштабирует нагрузку: нагрузка возросла — он создал дополнительное количество pod’ов, чтобы обработать всю возросшую нагрузку на сайт.

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

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

PodDisruptionBudget тоже используется во благо high availability. Этот механизм задает в штуках или процентах количество pod’ов, которые могут быть недоступны в единый момент времени.

Опять же, наглядно, есть задача — вывести в режим обслуживания два сервера. Pod’ы, которые крутятся на этих серверах, будут недоступны. Kubernetes требуется решить такую проблему. Что делаем: задаем PodDisruptionBudget в количестве одной штуки. И, соответственно, в этой ситуации сначала у нас переедет один pod на другой сервер. Ждем, пока он станет доступен. И теперь второй pod тоже переедет на другой сервер. Приложение будет корректно продолжать свою работу.

postgresql cluster что это. Смотреть фото postgresql cluster что это. Смотреть картинку postgresql cluster что это. Картинка про postgresql cluster что это. Фото postgresql cluster что это

Хранение данных

Один из вариантов — хранить данные в сетевом блочном устройстве Network Block Device. В нашем случае в Kubernetes кластере создаем StatefulSet, с базой данных. Kubernetes умеет создавать по шаблону диски для pod’ов из Network Block Device. Соответственно, оттуда выделились диски и прикрепились к pod’ам, и наш StatefulSet корректно начал работать.

postgresql cluster что это. Смотреть фото postgresql cluster что это. Смотреть картинку postgresql cluster что это. Картинка про postgresql cluster что это. Фото postgresql cluster что это

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

postgresql cluster что это. Смотреть фото postgresql cluster что это. Смотреть картинку postgresql cluster что это. Картинка про postgresql cluster что это. Фото postgresql cluster что это

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

postgresql cluster что это. Смотреть фото postgresql cluster что это. Смотреть картинку postgresql cluster что это. Картинка про postgresql cluster что это. Фото postgresql cluster что это

Kubernetes-операторы

Познакомимся с самыми популярными операторами, которые существуют для работы PostgreSQL внутри Kubernetes.

Crunchy Data
У этого оператора есть лицензия Apache 2.0, поэтому при желании можно использовать этот продукт бесплатно. Если нужна поддержка, то ее можно приобрести за плату. Кстати, из плюсов — этот продукт поддерживается и в Kubernetes, и в OpenShift, и в VMware PKS. Ну, и ключевой особенностью для этого и для других Kubernetes-операторов (которые обсудим далее) является то, что для обеспечения high availability самого PostgreSQL используется компонент Patroni. Так что он является де-факто стандартом для обеспечения high availability как внутри Kubernetes, так и на обычных серверах.

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

Zalando postgres-operator
Еще один интересный Kubernetes-оператор. И вот почему: именно компания Zalando разработала Patroni, и в продолжение своей разработки они написали этот оператор, чтобы их продукт мог также работать в кластерах Kubernetes.
У Zalando есть лицензия MIT, которая позволяет бесплатно использовать этот продукт. Но тут ребята не предоставляют платной поддержки, и если вы решите использовать именно его, то саппортить его вам нужно будет своими силами.

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

Также стоит отметить, что Crunchy Data и Stackgres имеют внутри себя встроенные средства для мониторинга PostgreSQL, чего, к сожалению, нет в postgres-operarot’е от Zalando.

В качестве итогов: плюсы и минусы размещения БД PostgreSQL внутри Kubernetes

Почему удобно разрабатывать:

И что стоит иметь в виду при разработке:

>>> В этой статье поделился основными тезисами и добавил новые подробности из доклада на IT-конференции code/R. Посмотреть вживую и послушать все выступление можно тут.

Источник

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

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