Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 192 additions & 0 deletions rfcs/0020-telemetry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
- Feature Name: Telemetry

- Start Date: 2024-01-24

- RFC PR: [mbta/technology-docs#0000](https://github.com/mbta/technology-docs/pull/0000)

- Asana task: [asana link](https://app.asana.com/)

- Status: Proposed

# Summary

Observability is the ability to measure the internal states of a system by examining its outputs.<sup>[1](https://www.splunk.com/en_us/blog/learn/observability.html)</sup>
It's what allows us to understand how our applications and systems work.
There are three pillars to observability:<sup>[2](https://www.crowdstrike.com/cybersecurity-101/observability/three-pillars-of-observability/)</sup>

1. **Logs** - are the archival or historical records of system events and errors, which can be plain text, binary, or structured with metadata.
2. **Metrics** - are numerical measurements of system performance and behavior, such as CPU usage, response time, or error rate.
3. **Traces** - are the representations of individual requests or transactions that flow through a system, which can help identify bottlenecks, dependencies, and root causes of issues.

TID engineers currently make extensive use of logging.
But, we don't currently have an established mechanism for collecting metrics or traces.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: we do have an "established mechanism for collecting metrics" at least, based on the definition above ("numerical measurements of system performance and behavior"). We have CPU/memory usage, response times, &c, already, via structured logs.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fact that we can log numerical values does not make them metrics. Metrics are stored and accessed differently, so they must be placed in a metric-specific index.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there another definition of metrics that talks about their storage and access? The definition you used above doesn't appear to include that.

This RFC proposes multiple pathways for collecting metrics in our current observability platform, Splunk.

# Motivation

Logs are handy for debugging and other unstructured research like a security audit.
However, many TID developers have noted that some queries based on logs are incredibly slow, if not impossible, to run.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: it would be good to include some examples here.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you take your example below and run it over a week. Took me 2 minutes and 30 seconds. Now, imagine loading a dashboard looking over the past week. It takes minutes to load. This is a real-world example from when I tried to find a good day/time to release the new caching work where we would have the fewest visitors and, therefore, incur the least risk.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's 3:30 - 4am, and it took under 10 seconds for 7 days worth of data; 30 days took 25 seconds. I'm sure a true timeseries database would be faster, but how often are we making these queries such that the extra speed is worth the complexity?

It sounds like knowledge about how to effectively query Splunk isn't well-distributed; I'll talk with Ryan about scheduling a lunch-and-learn sometime! This document with some ideas about what people are looking for will definitely be helpful.


> It might be tempting to think that logs can solve every use case. As the amount of data grows, however, a logs-only solution will become costly and relatively slow for a small set of regular searches, usually connected to alerts. This is because the process by which logs must be categorized and batched takes much more time and is much more computationally intensive than the metrics process...<sup>[3](https://www.splunk.com/en_us/blog/learn/logs-vs-metrics.html)</sup>

But, as noted above, logs are just one of three tools used to observe systems.
Metrics are optimized for storage and retrieval as numbers in a time series, which makes them cheap to store and fast to retrieve.

By adopting metrics, we can speed up slow queries, decrease the time necessary to generate dashboards and reports, and make previously impossible alerts possible.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: do you have examples of "impossible alerts"?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, let's say you want to know if the number of requests you received over the last minute was anomalous. You would want to compare that minute to other bins over the past week. Looking at logs over the past week takes too long...because they aren't optimized to be viewed that way.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a alert which does this for another application already: search for "Concentrate: Unusual Metric Drop" in the Splunk alerts.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: do we have a comparison about how much time it takes currently to make dashboards, and how much it'll take with the new metrics collection approach?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't mean how long it takes to create the dashboard but to render it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't seen an issue with dashboard queries taking a long time, so I was hoping you had examples of where metrics would measurably improve things. For example, the API and Dotcom dashboards seems to load quickly enough?


Furthermore, we can decrease costs by reducing the data we send to Splunk.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: does reducing the data we send to Splunk reduce our costs? It would also be interesting to compare with the additional resources needed to run the UF sidecar.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We pay for ingestion. Ingesting a log for every request (150 per second * 60 seconds) versus one metric for a minute is a 9,000:1 ratio.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we going to stop logging a message for each request?


# Guide-level explanation

This proposal primarily concerns capturing and transmitting metrics.

What is a metric, and how is it different from a log? Let's define a log as a structured **event** detailing information about a program's execution. Conversely, a metric is a single **number** valuable for informing us about a program's execution. Logs and metrics are helpful in different ways.

For example, you want to know how many requests your application served per minute over the last hour. Using logs, you would send a log to Splunk for every request. Dotcom regularly receives 150 requests per second or 9,000 requests per minute. So, if you wanted to look at the span of an hour, you would query 540,000 logs and bin each log by its timestamp. Lastly, you would count each log in each bin. You can do this in Splunk, but it is slow. It gets slower and slower as you increase the time horizon or amount of data because logs are stored as documents optimized to be searched rather than calculated.

If you used metrics instead, your application would keep an internal count of the requests received. Every minute (tuneable per application), it would emit that number and a timestamp to Splunk before resetting itself. So, in a minute, it would emit the number 150 once. That is one request instead of the 9,000 above. Reviewing an hour of data would entail nothing more than looking at 60 numbers. Metrics are a much better approach to answering the question.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: what is different about putting that in a "metric" vs a "log" like Dotcom.Metrics bucket="<timestamp>" request_count=<count>?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: I tried this just now on Splunk: index=dotcom-prod-application state=set | timechart spam=1m count to get a chart of requests over an hour: "This search has completed and returned 61 results by scanning 526,174 events in 2.013 seconds".

How much faster do we need data to be?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The numbers are to indicate the difference in magnitude. Here, it is 9,000:1. And, again, that 1 is stored as a time series, which is optimized to be read as a number in time. Try the same and extend the window to 7 days as you would for any kind of anomaly detection or manual analysis over time. It takes 2 minutes and 30 seconds to go over 57 million events. For the same span of time, you would look at 10,080 metrics. Again, optimized to be read that way.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can still optimize over multiple days (or longer); see above comment on when to deploy Dotcom.


Elixir libraries such as Phoenix, Ecto, Nebulex, and many others have adopted the [telemetry](https://hexdocs.pm/telemetry/readme.html) library as a standardized way to give users observability into their performance. It emits events that can be turned into metrics automatically via [Telemetry.Metrics](https://hexdocs.pm/telemetry_metrics/Telemetry.Metrics.html).

Providing custom metrics is as simple as logging:
```elixir
:telemetry.execute([:web, :request, :done], %{latency: latency}, %{request_path: path, status_code: status})
```

Once your application collects metrics, you must emit them with a reporter. Multiple reporter libraries exist, e.g., [statsd](https://github.com/beam-telemetry/telemetry_metrics_statsd) or [CloudWatch](https://github.com/bmuller/telemetry_metrics_cloudwatch). You can also write your own, such as this [custom reporter from dotcom](https://github.com/mbta/dotcom/blob/master/lib/cms/telemetry/reporter.ex).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: there's also a reporter which logs the reporter: https://hex.pm/packages/telemetry_logger (doesn't appear to get much use, however).


See More: [Introduction to Telemetry in Elixir](https://blog.miguelcoba.com/introduction-to-telemetry-in-elixir).

# Reference-level explanation

[reference-level-explanation]: #reference-level-explanation

As was mentioned previously, there are two phases to discuss: getting metrics out of our applications and getting those metrics into Splunk.

## Getting metrics out of our applications

We want to use the [telemetry](https://hexdocs.pm/telemetry/readme.html) library, [Telemetry.Metrics](https://hexdocs.pm/telemetry_metrics/Telemetry.Metrics.html), and [telemetry_poller](https://hexdocs.pm/telemetry_poller/readme.html). The telemetry library standardizes a method for emitting events. The telemetry metrics library turns those events into metrics, and the telemetry poller gathers those metrics and emits them to a reporter at whatever intervals you define. You can see an [example implementation from dotcom](https://github.com/mbta/dotcom/blob/master/lib/cms/telemetry.ex).

## Getting metrics into Splunk

We recommend two options for getting metrics into Splunk, and teams should feel free to pursue the path that best fits their application and expertise.

**Note**

There is no reason you cannot combine the two methods.
You could use the HEC for your Elixir applications and the UF for non-Elixir applications.

### Splunk Universal Forwarder

The Splunk Universal Forwarder (UF) is an agent that runs alongside an application, takes in metrics in one of many formats, and forwards those metrics to Splunk.
The UF can be used with any application, regardless of language, which makes it the most flexible way to get metrics into Splunk.

It runs in a [sidecar](https://www.oreilly.com/library/view/designing-distributed-systems/9781491983638/ch02.html) to any container that emits metrics.

To use it, create a metrics index in Splunk and add the sidecar to the `container_definition_json` of your `aws-ecs-container-definition` module:
```terraform
container_definition_json = jsonencode([
module.your-container.json_map_object,
module.sidecar-container.json_map_object
])
```

You use the StatsD telemetry reporter highlighted above to emit your metrics in the StatsD format, and the UF will automatically forward them.

#### When you should choose this method

- You maintain applications that aren't written in Elixir or want all of your applications to emit metrics in a standard format.

Every language has a library that emits metrics in the StatsD format; they are straightforward to set up.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: what other languages do we use which are emitting metrics in StatsD format?

@anthonyshull anthonyshull Feb 26, 2024

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a dotcom monitor that is written in Node. It was written in Node because it uses Playwright to run through critical user journeys and emits the time taken as metrics. Using Playwright allows us to reuse the same scenarios for end-to-end tests, load tests, and this monitoring. We also do health checks on our dependencies and emit those as metrics: 0 for down and 1 for up. That allows us to quickly calculate up-time over long periods.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's the only way it outputs information?

You get a lot of flexibility with very little work by running the UF as a sidecar and using an intermediary standard like StatsD.

#### When you shouldn't choose this method

- You only need metrics from Elixir applications.
- You are hesitant to change your infrastructure.
- You are concerned about your ECS resource usage.

The UF sidecar uses CPU and memory resources.
Though the footprint is small, you might be worried that increasing your compute usage might affect the rest of your cluster.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: do we know what the footprint is for UF running in ECS?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are the published requirements: https://docs.splunk.com/Documentation/Forwarder/9.2.0/Forwarder/Deploy. For dotcom, we use 4 3GHz processors and 16GB of RAM. So, the UF would be 1.5/12 12.5% of the CPU and 3% of RAM.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a sidecar though, correct? So it's additional resources (not many it sounds like), but some.

Resource usage is more pertinent if you run resource-lite instances, as the UF would take up a more significant percentage of CPU and memory.
Note that you can always increase CPU and memory levels for your cluster.

### Splunk HTTP Event Collector

The Splunk HTTP Event Collector (HEC) allows us to submit metrics via HTTP.
We maintain a library [TelemetryMetricsSplunk](https://github.com/anthonyshull/telemetry_metrics_splunk) that forwards telemetry metrics to Splunk via the HEC.

To use it, create a metrics index in Splunk and add its HEC token to the library configuration.

```elixir
config :app, TelemetryMetricsSplunk,
index: "your-index"
token: System.get_env("SPLUNK_TOKEN")
```

#### When you should choose this method

- You only need metrics from Elixir applications.

#### When you shouldn't choose this method

- You maintain non-Elixir applications and do not want to write a method of getting metrics into Splunk.

There aren't any libraries in other languages that transmit metrics to Splunk via the HEC.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: if we're maintaining a library to do this for Elixir, why wouldn't we maintain one for other languages we use?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We predominately use Elixir. So, it would make more sense to offer it as an option. The same ROI would not exist for languages we use less frequently.


# Drawbacks

[drawbacks]: #drawbacks

You can argue that we do not need metrics and that adding a means to collect them increases the complexity of our technology stack.
However, we hope the limitations of a logs-only approach to observability have been made apparent by this RFC.
Furthermore, we hope that the promised benefits of metrics cause excitement.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: some other drawbacks offhand:

  • additional resource usage for the Universal Forwarder
  • need to keep the UF up-to-date
  • possibly trickier to manage the connection to Splunk (vs. relying on ECS)

some questions:

  • how do we monitor the UF?
  • how does the UF handle being spun up / spun down elastically?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have addressed the resource usage above. It's a small footprint. Again, this is how Datadog et al. work. The UF has been around forever and is stable. Keeping it up-to-date would just be releasing a new image, which almost always only entails updating the version in the Dockerfile.

The UF isn't different from any other metrics agent; if you're worried that you aren't getting metrics, you write an alert that expects metrics to be sent. You don't need to scale the UF. You only need one.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought the UF was implemented as a sidecar to Dotcom? So it's being spun up / spun down as Dotcom is.


# Rationale and alternatives

[rationale-and-alternatives]: #rationale-and-alternatives

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: it would be good to include a comparison of the existing log-based approach, especially for metrics which are only logged every minute (or less frequently).


### AWS Cloudwatch

We considered using AWS CloudWatch as an intermediary for metrics.

#### Positives

Like the StatsD reporter and our HEC reporter, an AWS CloudWatch reporter already exists.
It is possible to send metrics to CloudWatch and then ingest those into Splunk.

To use it, teams must set up CloudWatch, set the correct permissions, and set the configuration in Splunk.

See more: [Configure CloudWatch inputs for the Splunk Add-on for AWS](https://docs.splunk.com/Documentation/AddOns/released/AWS/CloudWatch).

#### Negatives

AWS charges for every metric stored in CloudWatch.
It also charges for every request for a metric.
So, by using CloudWatch, we would be paying to store metrics in CloudWatch, paying to transfer them from CloudWatch to Splunk, and then paying Splunk to ingest them.

As a paper napkin exercise, consider dotcom's monitor application.
It emits 15 metrics every minute--21,600 every day.
AWS charges one penny for every 1,000 metrics requested.
Thus, it would cost $2.16 a day or $788.40 per year to transfer these 15 metrics from CloudWatch to Splunk.

Another negative is that using CloudWatch in this way might lock us into its use.
Moving away from it would entail changing every codebase.

A last negative is that using CloudWatch introduces a time delay as Splunk polls CloudWatch on an interval.
The fact that the default is 5 minutes indicates the two aren't indended to be used for alerting.

See more: [Example: Cost scenarios using polling APIs](https://docs.splunk.com/observability/en/infrastructure/monitor/aws-infra-costs.html#aws-costs-amazon).

# Future possibilities

[future-possibilities]: #future-possibilities

We have only addressed two of the three pillars of observability and have not considered [tracing](https://www.atatus.com/blog/logging-traces-metrics-observability/#tracing).
Tracing allows us to observe the complete journey of a request or workflow as it moves from one part of the system to another.
Our current Splunk instance does not support tracing as we need an add-on [Splunk Observability](https://www.splunk.com/en_us/products/observability.html).

There are also myriad alternatives, such as [Honeycomb](https://www.honeycomb.io/) and [SigNoz](https://signoz.io/).

These solutions and Splunk Observability support [OpenTelemetry](https://opentelemetry.io/), an industry standard for observability across languages.
The [OpenTelemetryTelemetry](https://github.com/open-telemetry/opentelemetry-erlang-contrib/tree/main/utilities/opentelemetry_telemetry) library can convert Telemetry (the Elixir library) events to OpenTelemetry spans.