Martin Kleppmann — Designing Data-Intensive Applications

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

В итоге пока решил переключиться на книги, тем более, что книг по специальности я прочёл не так много — в основном потому, что читаю я в основном с читалки или телефона, и специальную литературу в pdf и там и там читать неудобно.

Но технологии не стоят на месте 🙂 Оказалось, что весьма удобную оболочку для чтения книг даёт в своём приложении O’Reilly. К тому же в полном соответствии с мотивационными теориями, если ты за что-то заплатил, то твоя мотивация это использовать заметно повышается 🙂

Это была присказка, а первой прочтённой мной на платформе O’Reilly книгой стала классическая книга с кабанчиком, о которой дальше и пойдёт речь.

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

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

Книга состоит из трёх частей. Первая — представляет собой обзор способов хранения и обработки данных в пределах одной машины:

  • Какие бывают БД, как они развивались и для каких задач лучше подходят. Тут очень интересным было читать про графовые БД, так как я сам плотно с ними не работал. Особенно порадовало сходство Datalog и пролога.
  • Как БД хранят данные внутри себя, append-only logs и структуры для индексов — очень подробное и понятное описание, мне конечно тяжело судить, но показалось, что настолько понятное, что его можно было бы читать даже без какого-то бэкграунда
  • Форматы хранения данных — текстовые, бинарные, рассуждения о forward и backward compatibility. Тут интересным было читать про Avro (опять-таки, потому что я с ним не сталкивался ранее 🙂

Вторая — распределенные данные и проблемы возникающие при этом

  • Репликация. Очень подробный обзор — синхронная / асинхронная, с одним лидером и без лидера, проблемы с лагом репликации и способы их решения
  • Партиционирование. Способы шардинга, распределённые индексы, ребалансировка, роутинг запросов.
  • Транзакции. Не просто что такое и зачем, но и трейдоффы, уровни изоляции и как они релизовываются на низком уровне.
  • Проблемы распределённых систем. Одна из самых полезных глав. Что и как может пойти не так — нестабильные сети, процессы на паузе, ненадежное время, разница между монотонным и календарным временем, Byzantine faults (не могу придумать как это корректно перевести на русский таким же коротким термином — возможно, в русском нет подобного термина для неполадок, вызванных поведением частей системы, которые нарочно врут и им нельзя доверять)
  • Распределенные транзакции — двухфазный коммит, алгоритмы консенсуса (без глубокого погружения, но общие принципы, и какие существующие инструменты их реализовывают)

Третья — пакетная и потоковая обработка данных

  • Пакетная обработка (batch processing) — MapReduce и описание какие есть способы осуществления джойнов данных при этом + рассуждения о том насколько оно похоже на Unix утилиты
  • Потоковая обработка (stream processing) — переход от пакетной обработки, message-based и log-based источники данных, change data capture, event sourcing и как и везде в книге — рассуждения о проблемах и трейдофах при потоковой обработке данных, а также как сделать её максимально ошибкоустойчивой (микробатчи, идемпотентность)
  • И последняя глава состоит из рассуждений автора о будущем обработки данных и этических вопросах. Второе неинтересно (т.к. ничего нового, всё те же опасения, и гос. регулирование нас спасёт), разве что стоит отметить сравнение текущего положения со сбором и обработкой данных с ранним капитализмом — детским трудом, 12-часовым рабочим днём и т.п.
    А вот техническая часть главы выглядит интереснее — кругом сплошное телевидение сплошные стримы и данные порождаемые из данных. Интересна идея замены двухфазного коммита на единое сообщение с бизнес-транзакцией пользователя, на основании которой уже все системы (базы данных, поисковые индексы, очереди сообщений и т.п.) делают нужные им изменения состояния, но автор сам отмечает, что тут есть большая проблема с read your own writes, которая по его мнению как-нибудь будет решена (мы же о будущем мечтаем).

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

Улучшение оптимизатора запросов в MySQL 8.0.17 (по сравнению с 8.0.16)

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

В 8.0.17 к примеру завезли оптимизацию запросов с NOT EXISTS:

The optimizer now transforms a WHERE condition having NOT IN (subquery)NOT EXISTS (subquery)IN (subquery) IS NOT TRUE, or EXISTS (subquery) IS NOT TRUE internally into an antijoin, thus removing the subquery.

Пример, имеем запрос:

SELECT task.TaskID
FROM contact
INNER JOIN login ON login.LoginID = contact.LoginID
INNER JOIN task ON task.TaskID = contact.TaskID
LEFT OUTER JOIN taskstar ON taskstar.TaskID = task.TaskID AND taskstar.LoginID = 858636
INNER JOIN taskaccess accesslogined ON task.TaskID = accesslogined.TaskID AND accesslogined.LoginID = 858636
WHERE 1
AND contact.ContactBool_1 = 0
AND contact.ContactSpam = 0
AND task.TaskType = 4
AND contact.ContactIsDeleted = 0
AND task.TaskIsDeleted = 0
AND (NOT EXISTS(SELECT 1
FROM task
WHERE ClientID = contact.ContactID
AND task.TaskIsDeleted = 0
AND (task.TaskType = 0)
AND task.TaskStatusSetID = 57450)
)
GROUP BY task.TaskID
ORDER BY task.TaskID
LIMIT 0,5

Выполняем его в MySQL 8.0.16 и получаем:

5 rows in set (1 min 9.92 sec)

Больше минуты, совсем некомфортно.
Обновляем MySQL и выполняем его же в 8.0.17:

5 rows in set (1.02 sec)

Профит 🙂

Запуск ansible c конфигом из папки со слишком широкими правами на запись

Дано: ansible запускается с виртуалки VirtualBox, файлы при этом лежат на windows хосте и примонтированы в виртуалку. При попытке запуска ansible-playbook получаем:

[WARNING]: Ansible is in a world writable directory (/media/D_DRIVE/work/ansible), ignoring it as an ansible.cfg source.

Обсуждение проблемы и workaround есть тут: https://github.com/ansible/ansible/issues/42388

Надо добавить путь к конфигу в переменные окружения. Я в виртуалке работаю один, ansible у меня там тоже один — поэтому добавил ее сразу в /etc/environment:

Идем в /etc/environment и дописываем туда путь к конфигу:

ANSIBLE_CONFIG=» /media/D_DRIVE/work/ansible/ansible.cfg»

Применяем оттуда переменные до перезагрузки:

for env in $( cat /etc/environment ); do export $(echo $env | sed -e ‘s/»//g’); done

Готово, ansible продолжает ругаться, но при этом работает

Opendkim, query timed out и _внезапно_ DNSSEC

Столкнулся с тем, что встряла входящая почта. Приходить приходит, но вся застревает в maildrop. В postqueue -p — тысячи писем. Беглый анализ логов показал, что предположительный виновник opendkim:

Oct 15 09:55:01 postnew opendkim[31282]: 221D962914: key retrieval failed (s=20161025, d=youtube.com): ‘20161025._domainkey.youtube.com’ query timed out

А вот дальше начались долгие и тяжелые поиски причины, собственно завязка такая — opendkim выдает query timed out

~# opendkim-testkey -d youtube.com -s 20161025 -v -v -v
opendkim-testkey: using default configfile /etc/opendkim.conf
opendkim-testkey: checking key ‘20161025._domainkey.youtube.com’
opendkim-testkey: ‘20161025._domainkey.youtube.com’ query timed out

а dig — нет )

# dig 20161025._domainkey.youtube.com TXT

; <<>> DiG 9.10.3-P4-Ubuntu <<>> 20161025._domainkey.youtube.com TXT
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 44900
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;20161025._domainkey.youtube.com. IN TXT

;; ANSWER SECTION:
20161025._domainkey.youtube.com. 3599 IN TXT «k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0UfgFQF/Ms63E9cKpj+WdM5RepAYbfAT+h4iAzOb93Q7eVjNd0WabrALPs3qcUEKkrhpI1nlZtOofutG8l4VgslGn7+9ggc489LWyU+u674c3eRoErGOFRq0xV8xnG+Rf» «WKF+im0t4n/QGA2ZcdOIcIfevxyPHudcJipW0G8C6vMtBmQulAfN/SgE1/cugl6VBedZIYuynEF2ttqO9vDLy90/BctMBRJGyyj/CJR7gdLl655Y2+73BFvjhCIWroDrGa2io005Hh1nkReAY9Q2BPQ2K6O7TlIq3SGFVRuPIp0cszwwIjPTy3BP92X2NfiM4CZSPr9R02UfNyPYLQv4wIDAQAB»

;; Query time: 15 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Mon Oct 15 11:01:48 GMT 2018
;; MSG SIZE rcvd: 475

Гугл говорит — ну так это дело ясное, opendkim ломится не на тот сервер — надо явно прописать Nameservers, прописываем:

Nameservers 8.8.8.8

Ноль эффекта. Пробуем 1.1.1.1 — так же ноль эффекта.

Подключаем тяжелую артиллерию:

tcpdump -i eth1 udp port 53 -n

Вау — запросы есть, и ответы приходят

11:05:45.772081 IP 144.76.68.186.30286 > 216.239.34.10.53: 37169% [1au] TXT? 20161025._domainkey.youtube.com. (60)
11:05:45.795999 IP 216.239.34.10.53 > 144.76.68.186.30286: 37169*- 1/0/0 TXT «k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0UfgFQF/Ms63E9cKpj+WdM5RepAYbfAT+h4iAzOb93Q7eVjNd0WabrALPs3qcUEKkrhpI1nlZtOofutG8l4VgslGn7+9ggc489LWyU+u674c3eRoErGOFRq0xV8xnG+Rf» «WKF+im0t4n/QGA2ZcdOIcIfevxyPHudcJipW0G8C6vMtBmQulAfN/SgE1/cugl6VBedZIYuynEF2ttqO9vDLy90/BctMBRJGyyj/CJR7gdLl655Y2+73BFvjhCIWroDrGa2io005Hh1nkReAY9Q2BPQ2K6O7TlIq3SGFVRuPIp0cszwwIjPTy3BP92X2NfiM4CZSPr9R02UfNyPYLQv4wIDAQAB» (464)
11:05:45.796262 IP 144.76.68.186.23689 > 192.112.36.4.53: 62833% [1au] DNSKEY? . (28)
11:05:45.821559 IP 192.112.36.4.53 > 144.76.68.186.23689: 62833*- 4/0/1 DNSKEY, DNSKEY, DNSKEY, RRSIG (1139)
11:05:45.821755 IP 144.76.68.186.63995 > 199.7.91.13.53: 33914% [1au] DNSKEY? . (28)
11:05:45.833611 IP 199.7.91.13.53 > 144.76.68.186.63995: 33914*- 4/0/1 DNSKEY, DNSKEY, DNSKEY, RRSIG (1139)
11:05:45.833814 IP 144.76.68.186.42019 > 202.12.27.33.53: 53753% [1au] DNSKEY? . (28)
11:05:45.848947 IP 202.12.27.33.53 > 144.76.68.186.42019: 53753*- 4/0/1 DNSKEY, DNSKEY, DNSKEY, RRSIG (1139)
11:05:45.849195 IP 144.76.68.186.9289 > 198.41.0.4.53: 19115% [1au] DNSKEY? . (28)
11:05:45.855702 IP 198.41.0.4.53 > 144.76.68.186.9289: 19115*- 4/0/1 DNSKEY, DNSKEY, DNSKEY, RRSIG (1139)
11:05:45.856003 IP 144.76.68.186.33889 > 193.0.14.129.53: 31594% [1au] DNSKEY? . (28)
11:05:45.861259 IP 193.0.14.129.53 > 144.76.68.186.33889: 31594*- 4/0/1 DNSKEY, DNSKEY, DNSKEY, RRSIG (1139)
11:05:45.861547 IP 144.76.68.186.36263 > 199.9.14.201.53: 50292% [1au] DNSKEY? . (28)
11:05:46.017319 IP 199.9.14.201.53 > 144.76.68.186.36263: 50292*- 4/0/1 DNSKEY, DNSKEY, DNSKEY, RRSIG (1139)

но opendkim упорно отвечает query timed out.
Единственное, что что за пачка DNSKEY в запросе?? Вспоминаем, что пару дней назад Эшер в телеграме писал про смену ключей DNSSEC https://t.me/usher2/444

Начинаем гуглить в эту сторону — ценнейшая статья от редхэта:
https://www.redhat.com/en/blog/what-you-need-know-about-first-ever-dnssec-root-key-rollover-october-11-2018

Ок, проверяем dnsmasq — нового ключа нет (но если виноват он — почему не работало при указании явно внешнего сервера в Nameservers). Обновляем — ключ приходит, эффекта нет.

Ищем, какой ключ таки использует opendkim, после некоторых поисков обнаруживаем, что этот:

# cat /usr/share/dns/root.key
. 172800 IN DNSKEY 257 3 8 AwEAAagAIKlVZrpC6Ia7gEzahOR+9W29euxhJhVVLOyQbSEW0O8gcCjFFVQUTf6v58fLjwBd0YI0EzrAcQqBGCzh/RStIoO8g0NfnfL2MTJRkxoXbfDaUeVPQuYEhg37NZWAJQ9VnMVDxP/VHL496M/QZxkjf5/Efucp2gaDX6RS6CXpoY68LsvPVjR0ZSwzz1apAzvN9dlzEheX7ICJBBtuA6G3LQpzW5hOA2hzCTMjJPJ8LbqF6dsV6DoBQzgul0sGIcGOYl7OyQdXfZ57relSQageu+ipAdTTJ25AsRTAoub8ONGcLmqrAmRLKBP1dfwhYB4N7knNnulqQxA+Uk1ihz0= ;{id = 19036 (ksk), size = 2048b} ;;state=2 [ VALID ] ;;count=0 ;;lastchange=1404118431 ;;Mon Jun 30 10:53:51 2014

Система обновлялась давно, нового ключа в списке нет.

Выясняем кто поставляет файл, и обновляем:

# dpkg -S /usr/share/dns/root.key
dns-root-data: /usr/share/dns/root.key

# apt-get install dns-root-data

И все тут же начинает работать:

# opendkim-testkey -d youtube.com -s 20161025 -v -v -v
opendkim-testkey: using default configfile /etc/opendkim.conf
opendkim-testkey: checking key ‘20161025._domainkey.youtube.com’
opendkim-testkey: key not secure
opendkim-testkey: key OK

Несколько фактов про Redis #painisinstructional

  1. Пустая строка — валидный ключ в редисе.
    redis.rpoplpush(list, processingList) когда processlingList — пустая строка успешно складывает данные в него.
  2. Имеем 3 сервера с автопереключением через Redis Sentinel. Из-за ошибки из пункта 1 на мастере переполняется память, но не слишком быстро — в итоге до падения по OOM не доходит, но памяти перестает хватать на bgsave. Мастер перестает обрабатывать запросы, т.к. отвалились слейвы и он не может сделать bgsave. Слейвы отваливаются, т.к. мастер не может сделать bgsave, но при этом считают его живым и не осуществляют переключение на себя.
    Единственный выход — вручную убить мастер, предварительно добавив памяти слейвам и исправив ошибку из п.1, чтобы все не началось заново.

Performance monitoring metrics из XenServer в Zabbix

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

До этого эти метрики смотрели в XenCenter — но это неудобно, т.к. сложно соотносить с бизнес-метриками из заббикса (с средним временем обработки запросов к примеру).

Метрики доступны начиная с XenServer 6.1:
https://support.citrix.com/article/CTX135033

Для их использования существует тулза rrd2csv — но для целей передачи значения из неё в заббикс она обладает рядом недостатков:

  1. работает пока не прервут, выдавая метрики раз в заданное число секунд (по-умолчанию 5)
  2. выплевывает результат в формате csv
  3. работает только под root

Проблема 1 решается с помощью timeout.

Проблема 2 решается благодаря тому, что утилите можно передать в особом формате какие именно метрики хочется получить.
В итоге получается нечто такое:

# (timeout 1s rrd2csv AVERAGE:host::cpu_avg || true) | cut -f2 -d» » | tail -n 1

возвращает текущую среднюю загрузку процессора.

Для решения проблемы 3 — поставил получение этой метрики в крон с сохранением значения в файл:

 * * * * * (timeout 1s /opt/xensource/bin/rrd2csv AVERAGE:host::cpu_avg || true) | cut -f2 -d» » | tail -n 1 > /tmp/xencpu

а UserParameter в zabbix читает уж из него:

 UserParameter=xen.cpu,cat /tmp/xencpu

timeout и конвейеры в linux

При попытке использовать timeout вместе с конвейером в лоб получается не совсем то что хотелось:
# timeout 1s rrd2csv AVERAGE:host::cpu_avg | cut -f2 -d» » | tail -n 1
Terminated
Решение — поместить timeout в subshell и сделать результат выполнения успешным:
# (timeout 1s rrd2csv AVERAGE:host::cpu_avg || true) | cut -f2 -d» » | tail -n 1