
В этом посте я расскажу, как воспользовался методологией управления инцидентами, чтобы справляться со сложным заболеванием. Надеюсь, вам понравится!
Британцы любят выпить. Вы когда-нибудь задумывались, сколько граммов углеводов в пинте пива? А как насчёт бургера с картошкой? Или салата, который в меню обычно указан как блюдо с низким содержанием углеводов?
Вы можете ответить: да кого это вообще волнует?
Людей заботит съедаемое количество калорий, только когда они пытаются набрать или сбросить вес. Возможно, вас немного интересуют углеводы, если вы соблюдаете кетодиету (но даже в этом случае многие из кетоблюд содержат углеводы).
Оказывается, в мире есть не менее 8 миллионов людей, которых волнуют ответы на эти вопросы, и я один из них. В 2020 году у меня диагностировали сахарный диабет первого типа. Вероятно, вы слышали о диабете, но, возможно, не очень знакомы с первым типом, который встречается реже. До постановки диагноза я тоже не был с ним знаком, так что вот краткое объяснение.
▍ Первый тип в ста словах
Сахарный диабет первого типа — это аутоиммунное заболевание, при котором поджелудочная железа не выделяет или почти не выделяет инсулин — гормон, необходимый для преобразования углеводов в энергию. Это значит, что инсулин нужно инъецировать, чтобы восполнить его недостаток. Какое количество нужно инъецировать? Это зависит от того, что вы едите и от кучи других переменных, которые настолько незаметны, что иногда кажутся случайными.

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

Мне нужно менять его каждые две недели. По сути, это маленькая кнопка, вставляемая в руку. Вот её фотография:

Устройство (оно называется Libre) снимает показания моей крови, когда я касаюсь его телефоном. Если график опускается ниже красной линии, то это значит, что мне нужно съесть углеводы. Если он выходит за пределы зелёной области, то мне нужно предпринять какие-то действия, в том числе принять инсулин.

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

В течение этого времени я не могу узнавать уровень глюкозы в крови при помощи устройства и не получаю уведомления о том, что сахар в крови должен был достичь опасных уровней. Эта ошибка любит возникать в самые стрессовые моменты.
В первые несколько месяцев после диагноза я с особым трудом контролировал уровень глюкозы в крови и полностью зависел от монитора. Даже сегодня иногда случается, что сахар падает и я не могу разобраться, почему.
Если бы на моей работе видимость вот так падала до нуля, то мы бы объявляли об инциденте и не закрывали его, пока видимость не была восстановлена. Кроме того, снижение глюкозы в крови — это первый признак каких-то проблем и необходимости принятия мер.
Я задался вопросом: можно ли применить тот же образ
▍ Краткая справка по управлению инцидентами
Хотя иногда мне кажется, что диабет занимает всё моё время, я работаю на полную ставку менеджером по разработке ПО в Cloudflare.
Для выявления инцидентов и информирования о них мы используем привычные инструменты: Prometheus, Grafana, Alertmanager и Pagerduty, а также разработанные нами инструменты. Если вы не пользовались этими инструментами, то вот краткое описание из предназначения.
Если какой-то из сотрудников компании Cloudflare (или автоматизированная система) обнаруживает, что что-то не работает, то он должен сообщить об инциденте. Мы разработали собственного бота, которого можно использовать для информирования об инцидентах, управления процессом и генерации отчётов об инциденте в конце.
- Prometheus — это опенсорсный инструмент, используемый для мониторинга событий и отправки алертов. Это невероятно мощный продукт для хранения данных временных последовательностей.
- Grafana очень хорошо работает в связке с Prometheus, позволяя создавать красивые дашборды для обзора данных.
- Alert Manager позволяет настраивать алерты под конкретные запросы для срабатывания вызовов.
- PagerDuty — единственное приложение на моём телефоне, пугающее своими пуш-уведомлениями.
Каждый менеджер по разработке ПО и некоторые из сеньор-разработчиков обучаются быть так называемыми управляющими инцидентами. Эта должность не такая гламурная, как на этом изображении, сгенерированном DALLE 3, но я действительно знаю некоторых разработчиков, имеющих такие же широкоэкранные мониторы.

Работая управляющим инцидентами, вы имеете власть призывать любого человека из любого отдела компании для максимально быстрого решения инцидентов. Под решением я подразумеваю устранение влияния на клиентов, то есть вы должны выявить, рассмотреть, устранить проблему/наложить быстродействующий «пластырь», определить и записать долговременные действия. Затем мы выполняем обзор каждого инцидента, чтобы гарантировать, что выводы сделаны и предприняты необходимые шаги, позволяющие больше ему не возникать.
Секрет успешного управления инцидентами — это совместная работа для выполнения общей цели (обычно это восстановление устоявшегося поведения). Для этого может потребоваться задействование специалиста в соответствующей предметной области или эскалация инцидента. В идеальном случае для разрешения инцидента вы задействуете минимальное количество людей, потому что их вызов может довольно сильно негативно влиять на работу. Однако это гораздо менее разрушительно, чем игнорирование сигналов и попытки починить то, что полностью сломано.
Я хотел применить ту же самую методологию к управлению глюкозой в своей крови. В случае возникновения ситуаций, приводящих к снижению глюкозы, я хотел получать уведомления, чтобы можно было принять меры до возникновения более серьёзных проблем. Однако если всё изменится в худшую сторону или если я не смогу устранить проблему самостоятельно, я хотел бы, чтобы кто-то получил уведомление о том, что я не справился сам и помог бы сделать так, чтобы не произошла эскалация инцидента с уровня P3 до уровня P0.
▍ Управление инцидентами первого типа
Я хотел бы создать систему, при которой буду уверен в том, что если устройство не может считать показания или они критически низки, то мне или кому-то другому поступит уведомление.
Исходники используемого мной устройства закрыты и не позволяют удобным образом создавать на его основе что-то ещё. Оно не раскрывает никаких API и не имеет SDK. Я пытался различными способами извлечь из него данные, но не нашёл очевидного решения, поэтому начал исследовать другие варианты. Проведя исследования, я обнаружил устройство под названием Miao Miao.

Оно надевается поверх устройства на руке, «сканирует» Libre каждые две минуты и отправляет результат в другое приложение под названием tomato. Очень удобно в нём то, что даже когда основное устройство показывает мне ошибку, оно может продолжать публиковать данные. Я поэкспериментировал с ним и мне показалось, что основное приложение не любит отправлять данные, которые кажутся ему аномальными из-за резкого изменения уровня глюкозы в крови. Однако лично я хотел бы знать об этом или, по крайней мере, сам принимать решение о полезности данных, а не чтобы это решало приложение.
Нативно у устройства есть очень продуманный «хак», каждые пять минут публикующий глюкозу в крови как события календаря Google. Благодаря этому можно использовать нативные приложения Apple для отображения уровня сахара на Apple Watch. Меня это крайне порадовало, и я сразу начал думать о том, что должен быть какой-то способ отправлять показания глюкозы стороннему API. Нужно только разобраться, как их посылать.

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

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

Чтобы решить эту проблему, я написал на Go эхо-сервер, соответствующий показанному выше маршруту. Для его развёртывания я использую encore.dev, позволяющий мне бесплатно развёртывать и запускать свою систему мониторинга. Аннотация вверху — это инструкция для encore о том, где будет запускаться мой httpHandler.

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

- SGV — это глюкоза в крови. В других странах используются не те единицы измерения, что в Великобритании, так что 73 здесь нужно поделить на 18, чтобы получить те значения, которые я буду использовать ниже.
- Он возвращает время.
- Он возвращает направление тренда.
- Этот вызов выполняется каждые две минуты.
Теперь, когда у нас есть конечная точка, мы также можем отправлять показания. Я создал gauge и фиксирую уровень своего сахара в крови каждый раз, когда его получаю.

Gauge — это метрика, обозначающая одиночное числовое значение, которое может повышаться или понижаться.
Её можно использовать для замера колеблющихся значений, например, текущего объёма занятой памяти, количества одновременных запросов, температуры или, как в нашем случае, глюкозы.
Кроме того, я «не очень упорно» пытаюсь сохранять данные в Postgres (функция insertReading). Это один из немногих случаев на моей памяти, когда хранение в базе данных не особо важно для меня и мне не хочется, чтобы из-за его сбоя пострадала остальная логика. Поэтому я просто записываю в лог результат и двигаюсь дальше.
Теперь я могу отправлять сахар крови конечной точке каждые две минуты и задавать значение метрики.
Этот gauge обновляется через каждую пару минут, так что теперь можно создать дашборд Grafana, отображающий глюкозу в реальном времени. По оси Y отложен уровень глюкозы. В идеале это значение должно поддерживаться в интервале от 4 до 9.

Как видите, теперь я могу просматривать изменение трендов по времени и делиться ими с кем угодно. Можно даже настроить для этого дэшборда монитор дома, чтобы я мог следить за ним.
Я могу делиться данными об уровне глюкозы с кем угодно, но само по себе это довольно бесполезно. Как и при любом мониторинге сложных систем, важен контекст. Например, если:
- Я только что поел
- произошёл скачок
- … но я уже сделал инъекцию инсулина
То особо беспокоиться здесь не о чем — инсулину нужно какое-то время, чтобы подействовать. Поэтому я хотел как-то создать способ удобного добавления этого контекста.
Grafana поддерживает аннотации, но я не хотел логиниться для аннотирования; мне хотелось, чтобы это было менее трудоёмко. Поэтому я создал чат-бота в Telegram, позволившего мне добавлять аннотации. Telegram можно настроить так, чтобы он отправлял webhook каждый раз, когда бот получает сообщение. Вот как это выглядит:

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

После валидации я пробую распарсить int, а затем вызываю сервис аннотирования, а затем выполняю запись обратно в Telegram и возвращаю ok.
Мой сервис аннотирования присваивает теги сообщению на основании его содержимого и отправляет их в Grafana.

В Grafana я могу присваивать цвета тегам и настраивать запросы аннотирования.

И теперь у нас есть всё необходимое для работы! Вот gif моего общения с ботом и отображения аннотаций в Grafana.


Теперь я могу добавлять к графику контекст, это очень полезно и для меня, и для других людей, чтобы понимать, что происходит с моей глюкозой в крови. Если у меня возникла ситуация падения уровня сахара, то вокруг неё будет много контекста, который очень полезен для выбора действий.
▍ А как насчёт алертов?
Итак, теперь мы достигли отличной видимости, но без алертов. Медработники сообщили мне, что если глюкоза падает ниже четырёх, я должен принимать меры. Поэтому я написал маленький cron, каждые пять минут проверяющий мои показания; если значение ниже заданного, он запускает инцидент.
Благодаря Encore сделать это очень просто, задачи cron в коде определяются следующим образом.


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

Вот как это выглядит:

Но что же на самом деле происходит при срабатывании инцидента?
При запуске инцидента код вызывает мой микросервис инцидентов, открывающий инцидент incident.io. Я выбрал incident.io, потому что это самый близкий инструмент к внутреннему инструментарию Cloudflare, да ещё и написанный целиком на Go.

Я захотел воспользоваться реальным инструментом работы с инцидентами, потому что это позволяет мне настроить политики эскалации, создавать отчёты, чтобы понять, сколько времени проведено в состоянии инцидента, а также многое другое.
Кроме того, Incident.io имеет возможность запускать workflow. Это значит, что я могу настроить правила аналогично тому, как это происходит в Zapier, и отправлять текстовые уведомления тем, кто на них подписался. В этом примере при каждом открытии мной workflow он отправляет мне текстовое сообщение.

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

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

Показанный выше график — это демонстрационные данные, которые я сгенерировал для этого поста, потому что мои реальные данные, к счастью, были довольно скучными! Однако вы видите, насколько это полезно. На основании этого графика я смогу понимать, увеличивается ли количество инцидентов с низкой глюкозой, а это может стать хорошим сигналом о необходимости визита к врачу, потому что текущий подход к лечению, очевидно, мне не подходит. Возможно, что-то изменилось в моём стиле жизни и мне нужно снизить инсулиновый режим.
▍ Дальнейшие шаги
Я описал только небольшую часть функций моей системы. Вот другие функции, которые я уже реализую или планирую реализовать:
- Автоматическое закрытие инцидентов. Пока я закрываю их вручную, чтобы иметь возможность проверить данные. Однако иногда я не успевал это сделать, что приводило к эскалации инцидента. Можно просто автоматически закрывать их, если в течение примерно 15 минут уровень глюкозы стабилен.
- Выше я говорил, что сохраняю все данные по сахару в базе данных, но пока я ничего с ними не делаю. У меня уже накопились данные за пару месяцев, поэтому было бы здорово обучить на них LLM, чтобы можно было обсуждать мои данные. Я могу задавать вопросы типа «почему в 15 часов уровень всегда низкий?» и использовать другие данные, например, календарь Google, чтобы искать на них ответы.
- Можно добавить больше графиков и метрик. Сейчас мой график прост, но можно добавить множество метрик, которые используются для измерения «успешности» диабетика, например, гликогемоглобин hba1c, который добавить очень легко.
- Повысить отказоустойчивость. Эта система критична, так что чем больше я сделаю для предотвращения отказов, тем лучше.
▍ Подведём итог
Когда у меня диагностировали диабет первого типа, я был напуган. Мне казалось, что это будет изнурительно и сильно усложнит мою жизнь. Не буду притворяться, что он мне никак не мешает, но этот проект очень помог мне понять своё заболевание и управлять им так, как мне привычнее всего: как будто это распределённая система.
Возможность писать код кажется мне сверхспособностью — она помогла автоматизировать управление заболеванием, которое ещё в 1920 году означало смертный приговор.
Надеюсь, вам понравился этот рассказ и он вдохновит вас на создание чего-то крутого.
Автор:
ru_vds