Диванный погромист
120 subscribers
3 links
Download Telegram
Whois

Привет! Меня зовут Дима, я ruby разработчик, люблю участвовать в рубишных срачах, с умным видом что-то кому-то рассказывать (иногда показывать)

WTFISTHIS

Частенько у меня возникает желание вынести самари из обсуждений / исследований в структурированную обучающую форму, для этого этот канал и будет нужен ❤️

Если есть желание что-то обсудить - велком ту лс, или можно в каком-нибудь рубишном чате
Эксплуатация сервиса в продакшене и немного про SRE

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

1. Доступность сервиса: должна быть высокой как по времени работы, так и по коэффициенту успешных запросов (обычно на уровне 99.9% - 99.99% uptime или success rate)
2. Воспроизводимость ошибок: важно быстро и точно воспроизводить возникающие ошибки для их эффективного устранения
3. Информирование: система мониторинга должна сообщать о проблемах сразу при их возникновении
4. Быстрая реакция и исправление: ошибки нужно устранять оперативно, чтобы минимизировать влияние на пользователей

—-

Что делать разработчику

1. Определение ключевых метрик
Перед вводом сервиса в эксплуатацию необходимо определить ключевые метрики, которые помогут отслеживать его состояние. Для этого можно использовать методологии USE, RED или Golden Signals. Подробнее об этих подходах можно узнать здесь.

2. Настройка алертинга
Важно настроить систему алертинга, которая будет оповещать о критических проблемах в работе сервиса. Чуть подробнее о настройке можно прочитать тут

3. Логирование по бестпрактикам
Для успешного дебага и мониторинга ошибок нужно настраивать логирование с учетом бестпрактисом. Начать можно отсюда

4. Сниппеты для восстановления
Если в вашем сервисе есть сущности, которые могут "застревать", создайте сниппеты для их восстановления. Что-то вроде вики с how-to рецептами (может быть обычный набор markdown файлов или статья в конфлюенс)

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

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

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

8. Дебаг в проде (не для всех)
Для быстрого поиска и исправления ошибок неплохо было бы иметь доступ к проду. Например, при отмене заказа по логам можно найти id заказа и reason отмены. Дебаг может выглядеть так:

Orders::CancelInteractor.new(order: Order.find(number: '123'), reason: :some_reason_key).pry


Если pry недоступен, можно использовать:

Orders::CancelInteractor.new(order: Order.find(number: '123'), reason: :some_reason_key).instance_eval('binding').irb

Далее выполняем по порядку содержимое метода call и глазками ищем расхождения задуманного от суровой реальности

А что почитать
Для углубления знаний по эксплуатации и методологиям SRE рекомендую почитать SRE Book от Google
Диванный погромист pinned «Whois Привет! Меня зовут Дима, я ruby разработчик, люблю участвовать в рубишных срачах, с умным видом что-то кому-то рассказывать (иногда показывать) WTFISTHIS Частенько у меня возникает желание вынести самари из обсуждений / исследований в структурированную…»
Держите аналогию про параллелизм и кошек
Forwarded from Dmitriy
Есть 10 кошек, 100 метровая полоса препятствий и миска еды на финише

В конкурентном соревновании одна миска
В параллельном соревновании 10 мисок
В однопоточном соревновании каждая следующая кошка бежит после вернувшейся

Кошка должна пробежать 100 метровку, поесть из миски 10% еды и вернуться назад

Кошке нужно 10 секунд на пробежку в одну сторону, 10 секунд чтобы поесть из миски, 10 секунд чтобы вернуться назад

В параллельном соревновании все просто - все кошки прибегут к финишу одновременно - через 30 секунд
В конкурентном - все кошки бегут к миске, по очереди едят из нее и возвращаются к финишу, получится 10+100+10 = 120 секунд
В однопоточном - 30 * 10 = 300 секунд

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

Миска - CPU bound
Пробежка - IO bound
Чел, который сидит возле миски и будет записывать результаты кто когда пришел и поел - shared memory, в параллельном соревновании он охуеет записывать за всеми и обязательно где-нибудь ошибется
Я памятку по собесам сделать
Что-то вспоминал из головы из того, что спрашивал сам, что спрашивали меня, что-то из вопросов может никогда вам не встретиться, а то, что встречаться будет всегда - в памятке будет отсутствовать

Выписывал в основном то, что сам считаю +- важным, чтобы понять потолок до которого человек ориентируется уверенно, исключительно для определения грейда мидл/мидл+/сеньор/машина

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

ПОГНАЛИ
Computer Since:
1. Алгоритмы и структуры данных (сложность по памяти/по времени, лучший/худший случай)
2. Принципы - SOLID, DRY, KISS, YAGNI
3. Паттерны (стандартные - рефакторинг гуру)
4. Отличие потока от процесса
5. Отличие параллелизма от конкурентности
6. Принципы at least once, at most once, exactly once
7. Паттерны в распределенных системах
8. Распределенные транзакции
9. механизмы обеспечения консистентности (transactional inbox/oubox, SAGA, event sourcing, двухфазный/трехфазный комит)
10. CAP теорема - consistency, availability, partition tolerance
Базы данных:
0. Advanced quering (SELECT, JOIN, GROUP, HAVING, Subqueries, Window functions, CTE)
1. Нормальные уровни бд
2. Блокировки (оптимистичные/пессимистичные) (SELECT FOR UPDATE),
3. Advisory lock - что это, зачем нужен, как сделать
4. Отличие постгри от мускуля
5. Индексы: btree, hash, gin, gist, bloom - что дают, когда какой лучше использовать, какие плюсы/минусы от использования + понимание селективности данных
6. Транзакционная целостность - понимание каждой буковки в ACID
7. Аномалии транзакции (Dirty Read, Non-Repeatable Read, Phantom Read, Lost Update)
8. Уровни изоляции транзакций (READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE)
9. Механизмы MVCC (Multi Version Concurrency Control) (можно до уровня что такое xmin/xmax, VACUUM)
10. Что такое EXPLAIN ANALYZE и что интересного там можно найти
11. Механизмы масштабирования БД - шардирование, репликация, партицирование (что выбирать при нагрузке на чтение/на запись)
12. Организация хранения данных / индексов (движни баз данных, страницы, как хранятся индексы)
13. Postgres: ltree, триграмный индекс, операции над json, materialized view, вложенные транзакции
Инфраструктура:
1. Различия между брокерами сообщений (rabbitmq/kafka/NATS/etc), отличие pull/push модели
2. Эластик (тут ничего не подскажу, я просто говорю что плотно с ним не работал)
3. Redis - что такое, какие ограничения, как сделать распределенную блокировку (документация редиса очень интересная)
Ruby:
1. Цепочка наследования и Method Lookup Chain
2. include/prepend/extend модулей чем отличается
3. Что такое GIL (GVL), GC + как работает (mark and sweep)
4. Какие знаешь вебсерверы (юникорн / пума / thin / фалкон), чем отличаются
5. Отличие файберов от потоков
6. Отличие прока от лямбды
7. Рефайнменты - что такое, зачем могут понадобится
Rails:
1. Ассоциации: includes, preload, eager_load, joins, references - зачем они все нужны, как работают, чем отличаются
2. rspec, отличие "expect(..).to receive(...)" от "allow().to receive(...)"
3. Как в ActiveRecord воспользоваться оптимистичной и пессимистичной блокировкой
4. Нужен ли dry-rb (жетально иметь свое взвешенное мнение, можно остановиться только на том, что использовали, тут вкусовщина, но обычно интересны размышления)
4. Бэкенды для ActiveJob (sidekiq, good_job, solid-que, etc) - отличия, когда лучше писать джобы в быстрый редис, а когда лучше сохранять джобы в бд (тут фишка с транзакцией)
Если встречали интересные вопросы на собесах - пишите в лс, дополню пост
Про механизмы массового UPDATE в postgresql

Вводные данные:

CREATE TABLE things (
id BIGSERIAL PRIMARY KEY,
update_counts INTEGER NOT NULL DEFAULT 0 -- Счетчик сколько раз запись была обновлена
);



INSERT INTO things (update_counts)
SELECT 0
FROM generate_series(1, 1000000);


Задача: Выбрать подмножество и выполнить обновление каждого элемента на предварительно посчитанное значение, в нашем контексте - увеличить update_counts на 1 (задать предварительно посчитанное значение)

Проблемы:
1. Данных много, нельзя использовать отдельные update
2. Нельзя сделать апдейт всего множества указав конкретное значение, значение должно быть вычисляемым на базе текущего значения
3. Дополнительно ограничение - нельзя в update написать что-то типа SET update_counts = update_counts + 1
Суть 3 пункта в том, что обновляемые значения приходят извне, то есть должны быть переданы в запрос в каком-либо виде (через values, в рамках демонстрации буду делать это через подзапросы)

Решение 0: N+1

update things SET update_counts = ? where id = ?;

Отлично, если нам не жалко бд, и если бизнес логика обрабатывает по 1 элементу

Далее 2 решения взял с https://www.iditect.com/program-example/sql--update-multiple-rows-in-same-query-using-postgresql.html
Решение 1: С использованием CASE

update things
SET update_counts =
CASE
WHEN id = 1 THEN 1
WHEN id = 2 THEN 2
WHEN id = 3 THEN 6
ELSE NULL
END
WHERE ID in (1, 2, 3)

Сразу несколько минусов в лоб:
- стейтмент будет разрастаться
- postgres не преобразует выражение case when в работу с хешем, если бы он это делал - сложность была бы O(m * log n), в нашем случае он для каждой обновляемой записи будет сравнивать весь набор условий m, получим O(m^2)

Тут и эксплейнить смысла мало, идем дальше.

Решение 2: С помощью VALUES (inline table)


UPDATE things
SET update_counts = d.new_updates_count
FROM (
VALUES
(1, 1),
(2, 2),
(3, 6)
) AS tmp_dataset(id, new_updates_count)
WHERE things.id = tmp_dataset.id

Плюсы по сравнению с предыдущим примером:
1. Запросы получаются примерно в 3 раза короче
2. Алгоритмическая сложность O(log n * m), где n - количество записей в бд, m - количество записей для обновления

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

Решение 3: Временная таблица
Почему-то в нескольких источниках натыкался на идею завести временную таблицу для массовых апдейтов. Думаю понятно, что делать это стоит в исключительных случаев и, вероятно, на обьемах не в 10, 100тыс записей а на несколько порядках выше + тут появляются возможность разделить задачу на несколько этапов, использовать индексы, COPY для наполнения, в общем

Решение 4: Удалить все и вставить заново. В принципе, если нет констрейнов на внешние ключи, и по какой-то причине не удалось дойти до решения с VALUES - то почему бы и нет
Ну нет, конечно, ерунда

Решение 5: Multi insert + ON CONFLICT(id)


INSERT INTO things (id, update_counts)
VALUES
(1, 1),
(2, 2),
(3, 6)
ON CONFLICT (id) DO UPDATE
SET update_counts = EXCLUDED.update_counts;

Что тут происходит: Мы просто выполняем массовый инсерт, в явном виде передавая id
id у нас primary key, так как запись с таким id уже существует - сработает on conflict, вместо вставки будет обновлена запись с конкретным id и переданным update_counts

Из плюсов - плоская структура запроса, любой квери билдер это поддержит, сложность - O(log n * m), log n - на поиск конфликта

В принципе, решение с values + inline tables чем-то схож с insert + on conflict, они одинаковые по размеру итогового SQL запроса, сложности
Please open Telegram to view this post
VIEW IN TELEGRAM