Вступ до розвитку метрик: що таке метрики та навіщо їх використовувати?

Одне з найкрутіших речей, про які я дізнався за останній рік, - це як постійно додавати вартість у виробництво, не викликаючи занадто великого хаосу.

У цій публікації я розповім про підхід до розвитку, керований метриками, і про те, як він допоміг мені цього досягти. Наприкінці допису ви зможете відповісти на такі запитання:

  • Що таке метрики і чому я повинен їх використовувати
  • Які існують різні типи метрик
  • Які інструменти я міг використовувати для зберігання та відображення показників
  • Що є реальним прикладом розвитку, керованого метриками

Що таке метрики і чому я повинен їх використовувати?

Метрики дають вам можливість збирати інформацію про активну систему, не змінюючи її код.

Це дозволяє отримувати цінні дані про поведінку вашої програми під час її роботи, щоб ви могли приймати рішення, керовані даними, на основі реальних відгуків клієнтів та використання у виробництві.

Які типи метрик доступні мені?

Це найпоширеніші показники, що використовуються сьогодні:

  • Лічильник - представляє монотонно зростаюче значення.

У цьому прикладі метрика лічильника використовується для обчислення швидкості подій у часі шляхом підрахунку подій в секунду

  • Датчик - представляє єдине значення, яке може йти вгору або вниз.

У цьому прикладі вимірювальна метрика використовується для відстеження процесора користувача у відсотках

  • Гістограма - підрахунок спостережень (наприклад, тривалості запитів або розмірів) у настроюваних сегментах.

У цьому прикладі метрика гістограми використовується для обчислення 75-го та 90-го процентилів тривалості запиту HTTP.

Біти та байти типів: лічильник, гістограма та калібрувальний прилад можуть бути досить заплутаними. Спробуйте прочитати про це далі тут.

Які інструменти я можу використовувати для зберігання та відображення показників?

Більшість систем моніторингу складаються з декількох частин:

  1. База даних часових рядів - програмне забезпечення баз даних, що оптимізує зберігання та обслуговування даних часових рядів. Два приклади такого роду баз даних - Whisper та Prometheus.
  2. Механізм запитів (з мовою запитів) - Два приклади розповсюджених механізмів запитів: Graphite та PromQL
  3. Система оповіщення - Механізм, який дозволяє конфігурувати оповіщення на основі графіків, створених мовою запитів. Система може надсилати ці сповіщення на Mail, Slack, PagerDuty. Два приклади поширених систем оповіщення: Графана та Прометей.
  4. Інтерфейс користувача - дозволяє переглядати графіки, що генеруються за вхідними даними, та налаштовувати запити та попередження. Два приклади поширених систем інтерфейсу: Графіт та Графана

Налаштування, яке ми сьогодні використовуємо в BigPanda Engineering, є

  • Telegraf - використовується як сервер StatsD.
  • Prometheus - використовується як наш механізм скасування, база даних часових рядів та механізм запитів.
  • Графана - використовується для попередження та інтерфейсу користувача

І обмеження, про які ми мали на увазі, вибираючи цей стек, були:

  • Ми хочемо масштабованого та еластичного вискоблювання метрик
  • Ми хочемо ефективний механізм запитів
  • Ми хочемо мати можливість запитувати наші показники за допомогою спеціальних тегів (таких як імена служб, хости тощо)

Реальний приклад розробки служби аналізу настроїв на основі метрик

Давайте розробимо нову службу конвеєру, яка обчислює настрої на основі текстових введень і робить це за допомогою розробленого метриками способу!

Скажімо, мені потрібно розробити цю службу конвеєра:

І це мій звичайний процес розробки:

Тому я пишу таку реалізацію:

let senService: SentimentAnalysisService = new SentimentAnalysisService(); while (true) { let tweetInformation = kafkaConsumer.consume() let deserializedTweet: { msg: string } = deSerialize(tweetInformation) let sentimentResult = senService.calculateSentiment(deserializedTweet.msg) let serializedSentimentResult = serialize(sentimentResult) sentimentStore.store(sentimentResult); kafkaProducer.produce(serializedSentimentResult, 'sentiment_topic', 0); } 

Повний зміст можна знайти тут.

І цей метод працює цілком чудово .

Але що відбувається, коли цього не відбувається ?

Реальність така, що під час роботи (у швидкому процесі розробки) ми допускаємо помилки. Це факт життя.

Я вважаю, що справжньою проблемою при припущенні помилок є не уникнення їх, а оптимізація того, як швидко ми їх виявляємо та виправляємо. Отже, нам потрібно набути здатності швидко виявляти свої помилки.  

Настав час для MDD-шляху.

Шлях, що керується метриками (MDD)

The MDD approach is heavily inspired by the Three Commandments of Production (which I had learned about the hard way).

The Three Commandments of Production are:

  1. There are mistakes and bugs in the code you write and deploy.
  2. The data flowing in production is unpredictable and unique!
  3. Perfect your code from real customer feedback and usage in production.

And since we now know the Commandments, it's time to go over the 4 step plan of the Metrics-Driven development process.

The 4-step plan for a successful MDD

Develop code 

I write the code, and whenever possible, wrap it with a feature flag that  allows me to gradually open it for users.

Metrics

This consists of two parts:

Add metrics on relevant parts

In this part, I ask myself what are the success or failure metrics I can define to make sure my feature works? In this case, does my new pipeline application perform its logic correctly?

Add alerts on top of them so that I’ll be alerted when a bug occurs

In this part, I ask myself What metric could alert me if I forgot something or did not implement it correctly?

Deployment

I deploy the code and immediately monitor it to verify that it’s behaving as I have anticipated.

Iterate this process to perfection

And that's it! Now that we have learned the process, let's tackle an important task inside it.

Metrics to Report — what should we monitor?

One of the toughest questions for me, when I’m doing MDD, is: “what should I monitor”?

In order to answer the question, lets try to zoom out and look at the big picture.

All the possible information available to monitor can be divided into two parts:

  1. Applicative information — Information that has an applicative context and meaning. An example of this will be — “How many tweets did we classify as positive in the last hour”?
  2. Operational information — Information that is related to the infrastructure that surrounds our application — Cloud data, CPU and disk utilization, network usage, etc.

Now, since we cannot monitor everything, we need to choose what applicative and operational information we want to monitor.

  • The operational part really depends on your ops stack and has built-in solutions for (almost) all your monitoring needs.
  • The applicative part is more unique to your needs, and I'll try to explain how I think about it later in this post.

After we do that, we can ask ourselves the question: what alerts do we want to set up on top of the metrics we just defined?

The diagram (of information, metrics, alerts) can be drawn like this:

Applicative metrics

I usually add applicative metrics out of two needs:

To answer questions

A question is something like, “When my service misbehaves, what information would be helpful to know about?”

Some answers to that question can be — latencies of all IO calls, processing rate, throughput, etc…

Most of these questions will be helpful while you are searching for the answer. But once you found it, chances are you will not look at it again (since you already know the answer).

These questions are usually driven by RND and are (usually) used to gather information internally.

To add Alerts

This may sound backward, but I usually add applicative metrics in order to define alerts on top of them. Meaning, we define the list of alerts and then deduce from them what are the applicative metrics to report.

These alerts are derived from the SLA of the product and are usually treated with mission-critical importance.

Common types of alerts

Alerts can be broken down into three parts:

SLA Alerts

SLA alerts surround the places in our system where an SLA is specified to meet explicit customer or internal requirements (i.e availability, throughput, latency, etc.). SLA breaches involve paging RND and waking people up, so try to keep the alerts in this list to a minimum.

Also, we can define Degradation Alerts in addition to SLA Alerts.

Degradation alerts are defined with lower thresholds then SLA alerts, and are therefore useful in reducing the amount of SLA breaches — by giving you a proper heads-up before they happen.

An example of an SLA alert would be, “All sentiment requests must finish in under 500ms.”

An example of a Degradation Alert will be: “All sentiment requests must finish in under 400ms”.

These are the alerts I defined:

  1. Latency — I expect the 90th percentile of a single request duration not to exceed 300ms.
  2. Success/Failure ratio of requests — I expect the number of failures per second, success per second, to remain under 0.01.
  3. Throughput — I expect that the number of operations per second (ops) that the application handles will be > 200
  4. Data Size — I expect the amount of data that we store in a single day should not exceed 2GB.
200 ops * 60 bytes(Size of Sentiment Result)* 86400 sec in a day = 1GB < 2GB

Baseline Breaching Alerts

These alerts usually involve measuring and defining a baseline and making sure it doesn’t (dramatically) change over time with alerts.

For example, the 99th processing latency for an event must stay relatively the same across time unless we have made dramatic changes to the logic.

These are the alerts I defined:

  1. Amount of Positive or Neutral or Negative Sentiment tweets — If for whatever reason, the sum of Positive tweets has increased or decreased dramatically, I might have a bug somewhere in my application.
  2. All latency \ Success ratio of requests \ Throughput \ Data size must not increase\decrease dramatically over time.

Runtime Properties Alerts

I’ve given a talk about Property-Based Tests and their insane strength. As it turns out, collecting metrics allows us to run property-based tests on our system in production!

Some properties of our system:

  1. Since we consume messages from a Kafka topic, the handled offset must monotonically increase over time.
  2. 1 ≥ sentiment score ≥ 0
  3. A tweet should classify as either Negative \ Positive \ Neutral.
  4. A tweet classification must be unique.

These alerts helped me validate that:

  1. We are reading with the same group-id. Changing consumer group ids by mistake in deployment is a common mistake when using Kafka. It causes a lot of mayhem in production.
  2. The sentiment score is consistently between 0 and 1.
  3. Tweet category length should always be 1.

In order to define these alerts, you need to submit metrics from your application. Go here for the complete metrics list.

Using these metrics, I can create alerts that will “page” me whenever one of these properties do not hold anymore in production.

Let’s take a look at a possible implementation of all these metrics

import SDC = require("statsd-client"); let sdc = new SDC({ host: 'localhost' }); let senService: SentimentAnalysisService; //... while (true) { let tweetInformation = kafkaConsumer.consume() sdc.increment('incoming_requests_count') let deserializedTweet: { msg: string } = deSerialize(tweetInformation) sdc.histogram('request_size_chars', deserializedTweet.msg.length); let sentimentResult = senService.calculateSentiment(deserializedTweet.msg) if (sentimentResult !== undefined) { let serializedSentimentResult = serialize(sentimentResult) sdc.histogram('outgoing_event_size_chars', serializedSentimentResult.length); sentimentStore.store(sentimentResult) kafkaProducer.produce(serializedSentimentResult, 'sentiment_topic', 0); } } 

The full code can be found here

A few thoughts on the code example above:

  1. There has been a staggering amount of metrics added to this codebase.
  2. Metrics add complexity to the codebase, so, like all good things, add them responsibly and in moderation.
  3. Choosing correct metric names is hard. Take your time selecting proper names. Here’s an excellent post about this.
  4. You still need to collect these metrics and display them in a monitoring system (like Grafana), plus add alerts on top of them, but that’s a topic for a different post.

Did we reach the initial goal of identifying issues and resolving them faster?

We can now make sure the application latency and throughput do not degrade over time. Also, adding alerts on these metrics allows for a much faster issue discovery and resolution.

Conclusion

Metrics-driven development goes hand in hand with CI\CD, DevOps, and agile development process. If you are using any of the above keywords, then you are in the right place.

When done right, metrics make you feel more confident in your deployment in the same way that seeing passing unit-tests in your build makes you feel confident in the code you write.

Adding metrics allows you to deploy code and feel confident that your production environment is stable and that your application is behaving as expected over time. So I encourage you to try it out!

Some references

  1. Here is a link to the code shown in this post, and here is the full metrics list described.
  2. If you are eager to try writing some metrics and to connect them to a monitoring system, check out Prometheus, Grafana and possibly this post
  3. This guy wrote a delightful post about metrics-driven development. GO read it.