<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Sre on Bartosz&#39;s blog</title>
    <link>https://ocytko.net/tags/sre/</link>
    <description>Recent content in Sre on Bartosz&#39;s blog</description>
    <generator>Hugo -- 0.155.3</generator>
    <language>en</language>
    <copyright>Bartosz Ocytko</copyright>
    <lastBuildDate>Thu, 04 May 2023 18:49:16 +0000</lastBuildDate>
    <atom:link href="https://ocytko.net/tags/sre/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>OpenTelemetry meets OpenAI: manual instrumentation</title>
      <link>https://ocytko.net/posts/opentelemetry-meets-openai-manual-instrumentation/</link>
      <pubDate>Thu, 04 May 2023 18:49:16 +0000</pubDate>
      <guid>https://ocytko.net/posts/opentelemetry-meets-openai-manual-instrumentation/</guid>
      <description>The posts explores three approaches to manual OpenTelemetry instrumentation for OpenAI calls in Langchain and LlamaIndex.</description>
      <content:encoded><![CDATA[<p><em>Originally published on <a href="https://medium.com/@bocytko/opentelemetry-meets-openai-manual-instrumentation-d103140c67e0">medium</a>.</em></p>
<p>In the <a href="https://ocytko.net/posts/opentelemetry-meets-openai/">previous post</a>, we inspected calls to OpenAI APIs triggered within <a href="https://github.com/hwchase17/langchain">Langchain</a> and <a href="https://github.com/jerryjliu/llama_index">LlamaIndex</a> by using OpenTelemetry auto-instrumentation. The spans shown in Jaeger UI were nice to see, but were missing rich information that is expected from a proper instrumentation approach. In this post, we will explore how to enrich spans with additional information using <a href="https://opentelemetry.io/docs/instrumentation/python/getting-started/#add-manual-instrumentation-to-automatic-instrumentation">manual instrumentation</a>.</p>
<h2 id="manual-instrumentation">Manual instrumentation</h2>
<p>OpenTelemetry provides means to add additional attributes to spans. The OpenTelemetry standard defines two rules:</p>
<blockquote>
<ol>
<li>Keys must be non-null string values</li>
<li>Values must be a non-null string, boolean, floating point value, integer, or an array of these values</li>
</ol>
</blockquote>
<p>Additionally, most commonly used fields follow naming conventions and are referred to as <a href="https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/">semantic attributes</a>.</p>
<p><em><strong>Note</strong>: Beware of adding fields that may contain PII information to span context. Unless you guarantee that all systems processing the telemetry drop stored data after a fixed period of time (e.g. 30 days), you may run into challenges related to privacy regulation, such as GDPR and its ‘Right to be forgotten’.</em></p>
<h2 id="adding-instrumentation-to-owncode">Adding instrumentation to own code</h2>
<p>Instrumenting own code is as simple as shown in the code below. It starts a new span called <code>function_name</code> with an attribute <code>arg</code> with value <code>42</code>.</p>
<p>Any spans that are added using auto-instrumentation to functions called by <code>function_name</code> will automatically become its child spans.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">opentelemetry</span> <span class="kn">import</span> <span class="n">trace</span>
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="o">...</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">tracer</span> <span class="o">=</span> <span class="n">trace</span><span class="o">.</span><span class="n">get_tracer</span><span class="p">(</span><span class="vm">__name__</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="n">tracer</span><span class="o">.</span><span class="n">start_as_current_span</span><span class="p">(</span><span class="s2">&#34;function_name&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">span</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">arg</span> <span class="o">=</span> <span class="mi">42</span>
</span></span><span class="line"><span class="cl">    <span class="n">span</span><span class="o">.</span><span class="n">set_attribute</span><span class="p">(</span><span class="s2">&#34;arg&#34;</span><span class="p">,</span> <span class="n">arg</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">function_name</span><span class="p">(</span><span class="n">arg</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Alternatively, one can use the provided decorator, which results in simpler code in case it’s not necessary to capture any attributes in the spans.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nd">@tracer.start_as_current_span</span><span class="p">(</span><span class="s2">&#34;foobar&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">foobar</span><span class="p">(</span><span class="n">arg</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">foo_bar</span><span class="p">(</span><span class="n">arg</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="adding-instrumentation-to-langchains-llmchains">Adding instrumentation to Langchain’s LLM Chains</h2>
<p>Langchain offers <a href="https://python.langchain.com/en/latest/modules/callbacks/getting_started.html#creating-and-using-a-custom-callbackhandler">Custom Callback Handlers</a> as means to execute additional functions in well-defined stages of the chains. To collect statistics on the prompts and token usage from the LLM calls, we can add spans in the <code>on_llm_start</code> and <code>on_llm_end</code> calls:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">on_llm_start</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="p">,</span> <span class="n">serialized</span><span class="p">:</span> <span class="n">Dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">Any</span><span class="p">],</span> <span class="n">prompts</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">],</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">Any</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">with</span> <span class="n">tracer</span><span class="o">.</span><span class="n">start_as_current_span</span><span class="p">(</span><span class="s2">&#34;on_llm_start&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">span</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">prompts_len</span> <span class="o">+=</span> <span class="nb">sum</span><span class="p">([</span><span class="nb">len</span><span class="p">(</span><span class="n">prompt</span><span class="p">)</span> <span class="k">for</span> <span class="n">prompt</span> <span class="ow">in</span> <span class="n">prompts</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">            <span class="n">span</span><span class="o">.</span><span class="n">set_attribute</span><span class="p">(</span><span class="s2">&#34;num_processed_prompts&#34;</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">prompts</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">            <span class="n">span</span><span class="o">.</span><span class="n">set_attribute</span><span class="p">(</span><span class="s2">&#34;prompts_len&#34;</span><span class="p">,</span> <span class="n">prompts_len</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">on_llm_end</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">response</span><span class="p">:</span> <span class="n">LLMResult</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">Any</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">with</span> <span class="n">tracer</span><span class="o">.</span><span class="n">start_as_current_span</span><span class="p">(</span><span class="s2">&#34;on_llm_end&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">span</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># example output: {&#39;completion_tokens&#39;: 14, &#39;prompt_tokens&#39;: 71, &#39;total_tokens&#39;: 85}</span>
</span></span><span class="line"><span class="cl">            <span class="n">token_usage</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="n">llm_output</span><span class="p">[</span><span class="s2">&#34;token_usage&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">            <span class="k">for</span> <span class="n">k</span><span class="p">,</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">token_usage</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">                <span class="n">span</span><span class="o">.</span><span class="n">set_attribute</span><span class="p">(</span><span class="n">k</span><span class="p">,</span> <span class="n">v</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="adding-instrumentation-for-openai-embeddings-in-llamaindex">Adding instrumentation for OpenAI Embeddings in LlamaIndex</h2>
<p>LlamaIndex does not provide callback mechanisms for its <a href="https://gpt-index.readthedocs.io/en/latest/how_to/customization/embeddings.html#how-are-embeddings-generated">embeddings</a> functions. Instead, we can to extend the <code>OpenAIEmbedding</code> class, include instrumentation code in the overridden methods, and pass an instance of this class to the relevant methods of the library. In the added spans we collect the text lengths as span attributes.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">InstrumentingOpenAIEmbedding</span><span class="p">(</span><span class="n">OpenAIEmbedding</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">mode</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">OpenAIEmbeddingMode</span><span class="o">.</span><span class="n">TEXT_SEARCH_MODE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">model</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="n">OpenAIEmbeddingModelType</span><span class="o">.</span><span class="n">TEXT_EMBED_ADA_002</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">deployment_name</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="o">**</span><span class="n">kwargs</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;&#34;&#34;Init params.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">mode</span> <span class="o">=</span> <span class="n">OpenAIEmbeddingMode</span><span class="p">(</span><span class="n">mode</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">model</span> <span class="o">=</span> <span class="n">OpenAIEmbeddingModelType</span><span class="p">(</span><span class="n">model</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="bp">self</span><span class="o">.</span><span class="n">deployment_name</span> <span class="o">=</span> <span class="n">deployment_name</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">_get_query_embedding</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">query</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="nb">float</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">        <span class="k">with</span> <span class="n">tracer</span><span class="o">.</span><span class="n">start_as_current_span</span><span class="p">(</span><span class="s2">&#34;_get_query_embedding&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">span</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">span</span><span class="o">.</span><span class="n">set_attribute</span><span class="p">(</span><span class="s2">&#34;query_length&#34;</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">query</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">_get_query_embedding</span><span class="p">(</span><span class="n">query</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">_get_text_embedding</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">text</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="nb">float</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">        <span class="k">with</span> <span class="n">tracer</span><span class="o">.</span><span class="n">start_as_current_span</span><span class="p">(</span><span class="s2">&#34;_get_text_embedding&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">span</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">span</span><span class="o">.</span><span class="n">set_attribute</span><span class="p">(</span><span class="s2">&#34;text_length&#34;</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">text</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">_get_text_embedding</span><span class="p">(</span><span class="n">text</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">_aget_text_embedding</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">text</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="nb">float</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">        <span class="k">with</span> <span class="n">tracer</span><span class="o">.</span><span class="n">start_as_current_span</span><span class="p">(</span><span class="s2">&#34;_aget_text_embedding&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">span</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">span</span><span class="o">.</span><span class="n">set_attribute</span><span class="p">(</span><span class="s2">&#34;text_length&#34;</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">text</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">            <span class="n">embeddings</span> <span class="o">=</span> <span class="k">await</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">_aget_text_embedding</span><span class="p">(</span><span class="n">text</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">embeddings</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">_get_text_embeddings</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">texts</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">List</span><span class="p">[</span><span class="nb">float</span><span class="p">]]:</span>
</span></span><span class="line"><span class="cl">        <span class="k">with</span> <span class="n">tracer</span><span class="o">.</span><span class="n">start_as_current_span</span><span class="p">(</span><span class="s2">&#34;_get_text_embeddings&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">span</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">span</span><span class="o">.</span><span class="n">set_attribute</span><span class="p">(</span><span class="s2">&#34;texts_len&#34;</span><span class="p">,</span> <span class="nb">sum</span><span class="p">([</span><span class="nb">len</span><span class="p">(</span><span class="n">txt</span><span class="p">)</span> <span class="k">for</span> <span class="n">txt</span> <span class="ow">in</span> <span class="n">texts</span><span class="p">]))</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">_get_text_embeddings</span><span class="p">(</span><span class="n">texts</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">async</span> <span class="k">def</span> <span class="nf">_aget_text_embeddings</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">texts</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="n">List</span><span class="p">[</span><span class="nb">float</span><span class="p">]]:</span>
</span></span><span class="line"><span class="cl">        <span class="k">with</span> <span class="n">tracer</span><span class="o">.</span><span class="n">start_as_current_span</span><span class="p">(</span><span class="s2">&#34;_aget_text_embeddings&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">span</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">span</span><span class="o">.</span><span class="n">set_attribute</span><span class="p">(</span><span class="s2">&#34;texts_len&#34;</span><span class="p">,</span> <span class="nb">sum</span><span class="p">([</span><span class="nb">len</span><span class="p">(</span><span class="n">txt</span><span class="p">)</span> <span class="k">for</span> <span class="n">txt</span> <span class="ow">in</span> <span class="n">texts</span><span class="p">]))</span>
</span></span><span class="line"><span class="cl">            <span class="n">embeddings</span> <span class="o">=</span> <span class="k">await</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">_aget_text_embeddings</span><span class="p">(</span><span class="n">texts</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">embeddings</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The obvious downside of the approach is that the code needs to be kept in sync with the extended base class, which results in increased maintenance effort in case of library upgrades.</p>
<h2 id="inspecting-thespans">Inspecting the spans</h2>
<p>Running and using the code mentioned earlier produces two traces. First, the embedding span with the added attribute <code>texts_len</code>:</p>
<figure class="align-center ">
    <img loading="lazy" src="1_XPEH0ygNLxYu0uTo_r1usA.png#center"
         alt="Screenshot from Jaeger UI showing the added embedding span with added attribute texts_len."/> <figcaption>
            <p>Screenshot from Jaeger UI showing the added embedding span with its attributes</p>
        </figcaption>
</figure>

<p>Next, the embedding traces and <code>on_llm_start</code> and <code>on_llm_end</code> traces with the captured <code>query_length</code> and token usage attributes:</p>
<figure class="align-center ">
    <img loading="lazy" src="1_8n-j13Hjix_CpTViQm8OTA.png#center"
         alt="Screenshot from Jaeger UI showing the embedding traces and on_llm_start and on_llm_end traces with the captured query_length and token usage attributes."/> <figcaption>
            <p>Screenshot from Jaeger UI showing the captured traces and LLM token usage attributes</p>
        </figcaption>
</figure>

<h2 id="writing-an-instrumentor-for-openai-embeddings-in-llamaindex">Writing an Instrumentor for OpenAI Embeddings in LlamaIndex</h2>
<p>Extending classes can be cumbersome and an unnecessary maintenance overhead. The built-in instrumentation offered by many of the <a href="https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation">OpenTelemetry instrumentation packages for Python</a> offer inspiration for a different approach of instrumentation using function wrappers.</p>
<p>Following the example of the <a href="https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation/opentelemetry-instrumentation-redis">Redis instrumentation library</a>, we use the convenient <a href="https://pypi.org/project/wrapt/">wrapt</a> package to write a simple wrapper function for three methods in the <code>OpenAIEmbedding</code> class. The wrapper <code>_traced</code> calculates the length of the passed string(s) depending on the function’s argument type (<code>str</code> or <code>List[str]</code>).</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">wrapt</span> <span class="kn">import</span> <span class="n">wrap_function_wrapper</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">llama_index.embeddings.openai</span> <span class="kn">import</span> <span class="n">OpenAIEmbedding</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">opentelemetry</span> <span class="kn">import</span> <span class="n">trace</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">opentelemetry.instrumentation.instrumentor</span> <span class="kn">import</span> <span class="n">BaseInstrumentor</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">opentelemetry.instrumentation.utils</span> <span class="kn">import</span> <span class="n">unwrap</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">opentelemetry.trace</span> <span class="kn">import</span> <span class="n">SpanKind</span><span class="p">,</span> <span class="n">Tracer</span><span class="p">,</span> <span class="n">get_tracer</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">_instrument</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">tracer</span><span class="p">:</span> <span class="n">Tracer</span>
</span></span><span class="line"><span class="cl"><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">_traced</span><span class="p">(</span><span class="n">func</span><span class="p">,</span> <span class="n">instance</span><span class="p">,</span> <span class="n">args</span><span class="p">,</span> <span class="n">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">with</span> <span class="n">tracer</span><span class="o">.</span><span class="n">start_as_current_span</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;get_embedding&#34;</span><span class="p">,</span> <span class="n">kind</span><span class="o">=</span><span class="n">SpanKind</span><span class="o">.</span><span class="n">CLIENT</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span> <span class="k">as</span> <span class="n">span</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="n">span</span><span class="o">.</span><span class="n">is_recording</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">args</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="ow">and</span> <span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">                    <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="nb">list</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">                        <span class="n">span</span><span class="o">.</span><span class="n">set_attribute</span><span class="p">(</span><span class="s2">&#34;text_length&#34;</span><span class="p">,</span> <span class="nb">sum</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">e</span><span class="p">)</span> <span class="k">for</span> <span class="n">e</span> <span class="ow">in</span> <span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]))</span>
</span></span><span class="line"><span class="cl">                    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                        <span class="n">span</span><span class="o">.</span><span class="n">set_attribute</span><span class="p">(</span><span class="s2">&#34;text_length&#34;</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]))</span>
</span></span><span class="line"><span class="cl">         
</span></span><span class="line"><span class="cl">            <span class="n">response</span> <span class="o">=</span> <span class="n">func</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">response</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">wrap_function_wrapper</span><span class="p">(</span><span class="s2">&#34;llama_index.embeddings.openai&#34;</span><span class="p">,</span> <span class="s2">&#34;OpenAIEmbedding.get_query_embedding&#34;</span><span class="p">,</span> <span class="n">_traced</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">wrap_function_wrapper</span><span class="p">(</span><span class="s2">&#34;llama_index.embeddings.openai&#34;</span><span class="p">,</span> <span class="s2">&#34;OpenAIEmbedding.get_text_embedding&#34;</span><span class="p">,</span> <span class="n">_traced</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">wrap_function_wrapper</span><span class="p">(</span><span class="s2">&#34;llama_index.embeddings.openai&#34;</span><span class="p">,</span> <span class="s2">&#34;OpenAIEmbedding._get_text_embeddings&#34;</span><span class="p">,</span> <span class="n">_traced</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">OpenAIEmbeddingInstrumentor</span><span class="p">(</span><span class="n">BaseInstrumentor</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">instrumentation_dependencies</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Collection</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">(</span><span class="s2">&#34;llama-index ~= 0.4.32&#34;</span><span class="p">,)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">_instrument</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;&#34;&#34;Instruments llama-index module&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="n">tracer_provider</span> <span class="o">=</span> <span class="n">kwargs</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;tracer_provider&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">tracer</span> <span class="o">=</span> <span class="n">get_tracer</span><span class="p">(</span><span class="vm">__name__</span><span class="p">,</span> <span class="s2">&#34;custom-tracer-version&#34;</span><span class="p">,</span> <span class="n">tracer_provider</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">_instrument</span><span class="p">(</span><span class="n">tracer</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">_uninstrument</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="n">unwrap</span><span class="p">(</span><span class="n">OpenAIEmbedding</span><span class="p">,</span> <span class="s2">&#34;get_query_embedding&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">unwrap</span><span class="p">(</span><span class="n">OpenAIEmbedding</span><span class="p">,</span> <span class="s2">&#34;get_text_embedding&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">unwrap</span><span class="p">(</span><span class="n">OpenAIEmbedding</span><span class="p">,</span> <span class="s2">&#34;_get_text_embeddings&#34;</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>To ensure the instrumentor is actually used, it needs to be initialized with <code>OpenAIEmbeddingInstrumentor().instrument()</code> before the first library calls are initiated. The resulting traces generated by the instrumentor code are as follows:</p>
<figure class="align-center ">
    <img loading="lazy" src="1_iyVR5EJrXuZkuMBNcbtcDQ.png#center"
         alt="Screenshot from JaegerUI depicting the span generated by the generic OpenAIEmbeddingInstrumentor."/> <figcaption>
            <p>Screenshot from JaegerUI depicting the span generated by the generic <code>OpenAIEmbeddingInstrumentor</code></p>
        </figcaption>
</figure>

<h2 id="summary">Summary</h2>
<p>We explored adding additional context to spans by adding instrumentation in three different ways: (1) manual instrumentation of individual function calls, (2) extending classes to override methods with ones that include tracing code, (3) instrumenting library code using function wrappers. When to use which approach is highly contextual and depends on the use case at hand. Approach 1 is best used for one’s own code, approach 3 for instrumenting libraries, and approach 2 when a high degree of control over instrumentation is required.</p>
<p>It’s important to be careful and not <a href="https://opentelemetry.io/docs/concepts/instrumenting-library/#what-to-instrument">overdo instrumentation</a> and rely on the provided instrumentation packages whenever applicable. When considering adding manual instrumentation, it’s important to balance the benefits of additional detail with the potential complexity it may introduce. Note that in production deployments, tracing data is often <a href="https://opentelemetry.io/docs/concepts/sampling/">sampled</a> to deal with high data volume and keep the tracing cost footprint in check and this <a href="https://www.heinrichhartmann.com/sampling/">affects the accuracy</a> of the collected data.</p>
<h2 id="references">References</h2>
<ul>
<li><a href="https://opentelemetry.io/docs/instrumentation/python/getting-started/#add-manual-instrumentation-to-automatic-instrumentation">OpenTelemetry for Python: Adding manual instrumentation to automatic instrumentation</a></li>
<li><a href="https://opentelemetry.io/docs/instrumentation/python/manual/#tracing">OpenTelemetry for Python: Tracing</a></li>
<li><a href="https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/">OpenTelemetry: Trace Semantic Conventions</a></li>
<li><a href="https://opentelemetry.io/docs/concepts/instrumenting-library/#what-to-instrument">OpenTelemetry: What to instrument</a></li>
<li><a href="https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation">OpenTelemetry: Python instrumentation libraries</a></li>
<li><a href="https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation/opentelemetry-instrumentation-redis">OpenTelemetry: Redis Instrumentation library</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>OpenTelemetry meets OpenAI</title>
      <link>https://ocytko.net/posts/opentelemetry-meets-openai/</link>
      <pubDate>Sun, 23 Apr 2023 18:57:12 +0000</pubDate>
      <guid>https://ocytko.net/posts/opentelemetry-meets-openai/</guid>
      <description>Using automatic instrumentation to quickly assess which APIs are called by popular Python LLM demo apps.</description>
      <content:encoded><![CDATA[<p><em>Originally published on <a href="https://medium.com/@bocytko/opentelemetry-meets-openai-95f873aa2e41">medium</a>.</em></p>
<p>The “Hello World” of LLMs is the <a href="https://developers.llamaindex.ai/python/framework/understanding/putting_it_all_together/chatbots/building_a_chatbot/">Q&amp;A Knowledge Base</a> use case where the app creates embeddings for source documents and feeds a vector storage. Next, for each query it calculates the embedding, fetches top N documents from the vector store and uses the question and knowledge snippets to prompt the GPT-3/4 language models. Some vector stores are 3rd party stores (<a href="https://www.pinecone.io/">Pinecone</a>, <a href="https://weaviate.io/">Weaviate</a>), others in-memory (<a href="https://github.com/facebookresearch/faiss">FAISS</a>, <a href="https://github.com/jerryjliu/llama_index/blob/c54a6f2bf661135a54a75ffe90b69427d468aa34/gpt_index/indices/vector_store/vector_indices.py#L39">GPTSimpleVectorIndex</a>).</p>
<p>With the sheer amount of these and other demos and applications using popular libraries like <a href="https://github.com/hwchase17/langchain">langchain</a> and <a href="https://github.com/jerryjliu/llama_index">llama-index</a>, the question arises how to analyze new demos to quickly understand what APIs are being called and in which order? How frequently and with which latency? Checking code is tedious and provides only half the answers. Proper Telemetry tooling will help us discover the API endpoints called by the applications.</p>
<h2 id="automatic-instrumentation-for-python-applications">Automatic Instrumentation for Python applications</h2>
<p><a href="https://opentelemetry.io/">OpenTelemetry</a> provides <a href="https://opentelemetry.io/docs/instrumentation/python/automatic/">Automatic Instrumentation for Python</a> that comes to the rescue here. Using the Python agent that is attached to the application, it dynamically injects bytecode to capture telemetry from popular libraries and frameworks. The Langchain, OpenAI, and LlamaIndex libraries use Python’s <code>requests</code> under the hood, so we’ll need to make sure to use <a href="https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation/opentelemetry-instrumentation-requests">opentelemetry-instrumentation-requests</a> explicitly.</p>
<p>To visualize the telemetry, we need to setup some tools first. There is a <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/examples/demo">convenient demo</a> that ships with a docker-compose to boot up the collector and a few more tools. We ignore the two chatty demo apps that run in the background, though those are helpful to check if the telemetry setup works correctly.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ git clone git@github.com:open-telemetry/opentelemetry-collector-contrib.git
</span></span><span class="line"><span class="cl">$ <span class="nb">cd</span> opentelemetry-collector-contrib/examples/demo
</span></span><span class="line"><span class="cl">$ docker-compose up -d     
</span></span><span class="line"><span class="cl"><span class="o">[</span>+<span class="o">]</span> Running 7/7
</span></span><span class="line"><span class="cl"> ⠿ Network demo_default                Created      0.0s
</span></span><span class="line"><span class="cl"> ⠿ Container prometheus                Started      0.6s
</span></span><span class="line"><span class="cl"> ⠿ Container demo-jaeger-all-in-one-1  Started      0.5s
</span></span><span class="line"><span class="cl"> ⠿ Container demo-zipkin-all-in-one-1  Started      0.4s
</span></span><span class="line"><span class="cl"> ⠿ Container demo-otel-collector-1     Started      0.7s
</span></span><span class="line"><span class="cl"> ⠿ Container demo-demo-server-1        Started      0.9s
</span></span><span class="line"><span class="cl"> ⠿ Container demo-demo-client-1        Started      1.1s
</span></span></code></pre></div><p>The collector runs on port 4317, so any other error message than a timeout/connection error, means that the application is running:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ curl localhost:4317
</span></span><span class="line"><span class="cl">curl: <span class="o">(</span>1<span class="o">)</span> Received HTTP/0.9 when not allowed
</span></span></code></pre></div><p>With this setup, in addition to the open telemetry collector, we also get <a href="https://github.com/jaegertracing/jaeger-ui">Jaeger UI</a> running under <a href="http://0.0.0.0:16686/">http://0.0.0.0:16686/</a> which will help in visualizing the calls.</p>
<p>The next step is to run the code with the auto-instrumentation agent. For this we need a few packages to be added as part of the project setup. In the post, I use poetry to manage python projects, but you can <a href="https://opentelemetry.io/docs/instrumentation/python/automatic/#setup">install the packages</a> with pip as well. First, we add the instrumentation for the Python requests library and the telemetry exporter.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ poetry add opentelemetry-instrumentation-requests
</span></span><span class="line"><span class="cl">$ poetry add opentelemetry-exporter-otlp
</span></span></code></pre></div><p>Afterwards, we add the agent via:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ poetry add opentelemetry-distro
</span></span></code></pre></div><p>and start the application via the agent (<a href="https://opentelemetry.io/docs/instrumentation/python/automatic/#configuring-the-agent">see reference</a>) and keep a text logfile:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ poetry run opentelemetry-instrument --traces_exporter console,otlp <span class="se">\
</span></span></span><span class="line"><span class="cl">--metrics_exporter console <span class="se">\
</span></span></span><span class="line"><span class="cl">--service_name llm-playground <span class="se">\
</span></span></span><span class="line"><span class="cl">--exporter_otlp_endpoint 0.0.0.0:4317 <span class="se">\
</span></span></span><span class="line"><span class="cl">python main.py <span class="p">|</span> tee output.log
</span></span></code></pre></div><p>If the metrics collector on port 4317 is not running correctly, the app will log an error:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">WARNING:opentelemetry.exporter.otlp.proto.grpc.exporter:Transient error StatusCode.UNAVAILABLE encountered <span class="k">while</span> exporting traces, retrying in 1s.
</span></span></code></pre></div><p>In case of SSL handshake issues (or similar ones)</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">E0423 17:04:25.197068000 <span class="m">6150713344</span> ssl_transport_security.cc:1420<span class="o">]</span>    Handshake failed with fatal error SSL_ERROR_SSL: error:100000f7:SSL routines:OPENSSL_internal:WRONG_VERSION_NUMBER.
</span></span></code></pre></div><p>one can instruct the exporter with an <a href="https://github.com/open-telemetry/opentelemetry-specification/blob/773ee656f92c7f591f2fd9c38df82c264a15184d/specification/protocol/exporter.md?plain=1#L19-L21">environment variable</a> to ignore SSL errors:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ <span class="nb">export</span> <span class="nv">OTEL_EXPORTER_OTLP_INSECURE</span><span class="o">=</span><span class="nb">true</span>
</span></span></code></pre></div><p>If this does not help to establish connectivity, try <a href="https://opentelemetry.io/docs/instrumentation/python/automatic/#grpc-connectivity">increasing the verbosity of gRPC logging</a> to find the error.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ <span class="nb">export</span> <span class="nv">GRPC_VERBOSITY</span><span class="o">=</span>debug
</span></span><span class="line"><span class="cl">$ <span class="nb">export</span> <span class="nv">GRPC_TRACE</span><span class="o">=</span>http,call_error,connectivity_state
</span></span></code></pre></div><p>As configured in “traces_exporter”, in addition to the OTLP endpoint, the spans are also written to the console. In the demo I’m running, the code uses <a href="https://gradio.app/">gradio</a> to create a simple UI. I was surprised to see four calls made by gradio even if <a href="https://gradio.app/docs/#interface-launch-header">launched</a> without any public sharing: <code>launch(share=False)</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">$ cat output.log <span class="p">|</span> grep http.url
</span></span><span class="line"><span class="cl"><span class="s2">&#34;http.url&#34;</span>: <span class="s2">&#34;&lt;https://checkip.amazonaws.com/&gt;&#34;</span>,
</span></span><span class="line"><span class="cl"><span class="s2">&#34;http.url&#34;</span>: <span class="s2">&#34;&lt;https://api.gradio.app/gradio-messaging/en&gt;&#34;</span>,
</span></span><span class="line"><span class="cl"><span class="s2">&#34;http.url&#34;</span>: <span class="s2">&#34;&lt;https://api.gradio.app/pkg-version&gt;&#34;</span>,
</span></span><span class="line"><span class="cl"><span class="s2">&#34;http.url&#34;</span>: <span class="s2">&#34;&lt;http://127.0.0.1:7860/startup-events&gt;&#34;</span>,
</span></span><span class="line"><span class="cl"><span class="s2">&#34;http.url&#34;</span>: <span class="s2">&#34;&lt;http://127.0.0.1:7860/&gt;&#34;</span>,
</span></span><span class="line"><span class="cl"><span class="s2">&#34;http.url&#34;</span>: <span class="s2">&#34;&lt;https://api.gradio.app/gradio-initiated-analytics/&gt;&#34;</span>,
</span></span><span class="line"><span class="cl"><span class="s2">&#34;http.url&#34;</span>: <span class="s2">&#34;&lt;https://api.gradio.app/gradio-launched-analytics/&gt;&#34;</span>,
</span></span><span class="line"><span class="cl"><span class="s2">&#34;http.url&#34;</span>: <span class="s2">&#34;&lt;https://api.gradio.app/gradio-launched-telemetry/&gt;&#34;</span>,
</span></span></code></pre></div><p><a href="https://gradio.app/docs/#interface-launch-header">According to the docs</a>, there is a parameter <code>analytics_enabled</code> and a <code>GRADIO_ANALYTICS_ENABLED</code> environment variable. The description is really convoluted though <em>“default: None; If None, will use environment variable or default to True”</em>&hellip;</p>
<figure class="align-center ">
    <img loading="lazy" src="1_2KEDWz8PDxjPDWeWWjkseA.png#center"
         alt="Gradio documentation screenshot for property “analytics_enabled”"/> <figcaption>
            <p>Gradio documentation screenshot for property “analytics_enabled”</p>
        </figcaption>
</figure>

<p>Back in the telemetry data, we also find the expected OpenAI requests:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="s2">&#34;http.url&#34;</span>: <span class="s2">&#34;&lt;https://api.openai.com/v1/engines/text-embedding-ada-002/embeddings&gt;&#34;</span>,
</span></span><span class="line"><span class="cl"><span class="s2">&#34;http.url&#34;</span>: <span class="s2">&#34;&lt;https://api.openai.com/v1/completions&gt;&#34;</span>,
</span></span></code></pre></div><p>The collected telemetry is helpful to understand the amount and latency of the requests over time. Let’s filter in Jaeger UI (<a href="http://0.0.0.0:16686/">http://0.0.0.0:16686/</a>) for one of the URLs using the tag <code>http.url=https://api.openai.com/v1/engines/text-embedding-ada-002/embeddings</code>:</p>
<figure class="align-center ">
    <img loading="lazy" src="1_G_uIu0dM4srjXpEfXCQp0w.png#center"
         alt="Jaeger UI showing traces for calls to OpenAI’s embedding URL endpoint"/> <figcaption>
            <p>Jaeger UI showing traces for calls to OpenAI’s embedding URL endpoint</p>
        </figcaption>
</figure>

<p>We see 20 calls in the last hour ranging from 280 to 883 ms. Each call can be expanded to check for a bit more details:</p>
<figure class="align-center ">
    <img loading="lazy" src="1_dootEEKfqFmi37aeWKnv4Q.png#center"
         alt="Jaeger UI showing details of a single UI call"/> <figcaption>
            <p>Jaeger UI showing details of a single UI call</p>
        </figcaption>
</figure>

<p>Sadly, the UIs of Jaeger and Zipkin are unexpectedly basic and don’t support wildcard searches, so one still needs to <code>tee</code> the console output to a file to quickly search for all the called APIs.</p>
<h2 id="summary">Summary</h2>
<p>For more rich telemetry information with custom metadata, the calls to OpenAI (and others) would need to be <a href="https://opentelemetry.io/docs/instrumentation/python/getting-started/#add-manual-instrumentation-to-automatic-instrumentation">instrumented manually</a>. While Langchain comes with <a href="https://python.langchain.com/en/latest/modules/callbacks/getting_started.html#callbacks">Callbacks</a> that provide access to the API call results, at time of writing LlamaIndex does not have a similar mechanism for <a href="https://github.com/jerryjliu/llama_index/blob/main/gpt_index/embeddings/openai.py">OpenAI embeddings</a>.</p>
<p>The automatic instrumentation is an easy way to inspect calls made by Python applications. The early-stage libraries built in the LLM context tend to have lots of defaults that magically make calls to OpenAI or call lots of 3rd party APIs and externalize its vector data. Even supposedly <a href="https://github.com/Helicone/helicone#example-env-file">open-source applications for self-hosting</a> proxies to OpenAI, still rely on other 3rd party services. OpenTelemetry provides an easy way to verify external calls in a sandbox environment.</p>
<p><em>If you’re interested in getting more out of OpenTelemetry, check out the follow-up post, that gets into details of <em><a href="https://ocytko.net/posts/opentelemetry-meets-openai-manual-instrumentation/"><em>manual instrumentation with OpenTelemetry for Langchain and LlamaIndex</em></a></em>.</em></p>
<h2 id="references">References</h2>
<ul>
<li><a href="https://opentelemetry.io/docs/instrumentation/python/automatic/">OpenTelemetry Python Automatic Instrumentation</a></li>
<li><a href="https://github.com/open-telemetry/opentelemetry-specification/blob/773ee656f92c7f591f2fd9c38df82c264a15184d/specification/protocol/exporter.md?plain=1#L19-L21">OpenTelemetry Exporter Configuration Options</a> (environment variables)</li>
<li><a href="https://beebom-com.cdn.ampproject.org/c/s/beebom.com/how-build-own-ai-chatbot-with-chatgpt-api/amp/">Building AI Chatbot with ChatGPT API</a> and <a href="https://gpt-index.readthedocs.io/en/latest/guides/building_a_chatbot.html">LlamaIndex: Building a Chatbot</a></li>
<li>Dependencies section for poetry:</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[tool.poetry.dependencies]</span>
</span></span><span class="line"><span class="cl"><span class="na">python</span> <span class="o">=</span> <span class="s">&#34;^3.11&#34;</span>
</span></span><span class="line"><span class="cl"><span class="na">openai</span> <span class="o">=</span> <span class="s">&#34;^0.27.2&#34;</span>
</span></span><span class="line"><span class="cl"><span class="na">llama-index</span> <span class="o">=</span> <span class="s">&#34;^0.4.32&#34;</span>
</span></span><span class="line"><span class="cl"><span class="na">gradio</span> <span class="o">=</span> <span class="s">&#34;^3.22.1&#34;</span>
</span></span><span class="line"><span class="cl"><span class="na">opentelemetry-instrumentation-requests</span> <span class="o">=</span> <span class="s">&#34;^0.38b0&#34;</span>
</span></span><span class="line"><span class="cl"><span class="na">opentelemetry-distro</span> <span class="o">=</span> <span class="s">&#34;^0.38b0&#34;</span>
</span></span><span class="line"><span class="cl"><span class="na">opentelemetry-exporter-otlp</span> <span class="o">=</span> <span class="s">&#34;^1.17.0&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div>]]></content:encoded>
    </item>
    <item>
      <title>Emergency Procedures in SRE</title>
      <link>https://ocytko.net/posts/emergency-procedures-in-sre/</link>
      <pubDate>Tue, 24 Jan 2023 19:00:12 +0000</pubDate>
      <guid>https://ocytko.net/posts/emergency-procedures-in-sre/</guid>
      <description>Emergency procedures aim at stabilizing the system in a degraded state. When used properly, they result in faster incident response and &amp;hellip;</description>
      <content:encoded><![CDATA[<p><em>Originally published on <a href="https://medium.com/@bocytko/emergency-procedures-in-sre-e3297f9add66">medium</a>.</em></p>
<p>Emergency procedures used in incident response are aimed at stabilizing the system in a degraded state. When used properly, they result in faster incident response and become a foundation for further resiliency improvements in your system. In the post we’ll also explore how emergency procedures differ from runbooks.</p>
<h2 id="runbooks">Runbooks</h2>
<p>Imagine you get paged in the middle of the night. The situation you encounter looks familiar to you and you’re sure you or your colleague had dealt with it before. However, as seconds ago your were still in deep sleep, you simply can’t recall what to do. Wouldn’t it be great if you had something to help your memory? This is what <a href="https://www.pagerduty.com/resources/learn/what-is-a-runbook/">runbooks</a> are for.</p>
<p>Typically linked in the description of the alert that paged you, runbooks describe routine procedures aimed at restoring regular service of your application. Runbooks list the exact steps to take. It’s helpful to name a runbook with a short summary of the executed procedure as title (e.g. “scale up application”, “retrigger batch job”). Other types of runbooks document migrations, failovers, DB upgrades to help retain know-how within your teams as this type of work is typically executed rather infrequently. Lastly, there are also runbooks aimed at helping with triaging unknown failure situations. When followed, they help inspect typical metrics of the system across layers and components in search for the culprit of the observed issues. A good example here is the sequence of commands used in Brendan Gregg’s <a href="https://www.brendangregg.com/blog/2015-12-03/linux-perf-60s-video.html">Linux performance analysis in 60 seconds</a>.</p>
<h2 id="emergency-procedures">Emergency procedures</h2>
<p>Let’s consider a different scenario: when paged, you observe that the system is overloaded and users experience increased latencies and error rates. You can’t scale up the system due to your dependencies (or storages) taking too long to scale up. To restore service you need to reduce load by 25% as soon as possible. How will you proceed? Will you disable feature A or B? Will you degrade service for user/country X or Y? Do you even have means to do so?</p>
<p>Enter emergency procedures. Unlike for typical runbooks, the goal of an emergency procedure is to bring the system into a degraded, yet stable state. Such state needs to be acceptable to users and stakeholders while trading off availability over customer experience.</p>
<h2 id="structure">Structure</h2>
<p>Emergency procedures have defined trigger conditions and impact, both from the business and operational side. It’s important that they’re agreed with business owners ahead of time and thus do not require active approval during the incident response. Impact can be expressed in customer behavior, description how a feature will be working when the procedure has been carried out, or expressed in the change to the system’s load, for example.</p>
<p>Here an example for a food delivery application:</p>
<blockquote>
<ul>
<li><strong>title</strong>: reduce search radius to 400m</li>
<li><strong>trigger</strong>: increased latency or error rate for search queries</li>
<li><strong>business impact</strong>: as all search queries will be limited to a max. 400m radius, customers will see 10–20% less search results, leading to a drop in conversion rate</li>
<li><strong>operational impact</strong>: load on the datastore load will be reduced by 20% within 2 minutes of activating the feature toggle</li>
<li><strong>steps</strong>: &hellip;</li>
</ul>
</blockquote>
<p>It’s important that the on-call team regularly practices the emergency procedures. This will verify the correctness of the to be executed steps and operational implications. Additionally, it ensures that the team (and stakeholders) are familiar with the degraded state of the system, which would be rarely observed otherwise. It’s highly recommended to include stakeholder contacts in the procedures in order to keep them informed about the interventions taken during incident response.</p>
<h2 id="designing-for-resilience">Designing for resilience</h2>
<p>Systems need to be explicitly designed for supporting emergency procedures. Be it through runtime toggles that allow controlling certain features (e.g. on/off switches, enabling less expensive processing using cached values), or infrastructure mechanisms (e.g. short-circuiting processing for certain user groups or request types). This also requires annotating incoming requests with sufficient metadata to be able to apply differing treatment per feature, traffic origin, etc.</p>
<p>Here a few example degradations that can be introduced to a system with the system property outlined in curly braces:</p>
<ul>
<li>enforce serving data from a cache instead to reduce load on the datastore (data freshness)</li>
<li>always serve the first page of a result set to reduce load on the datastore (data completeness)</li>
<li>limit retrieved records to N reducing the working dataset of the DB (data completeness defined by amount, distance, or time)</li>
<li>switch HD video to SD (degrade quality to save bandwidth) or serve images instead (reduce load by preventing auto-play)</li>
<li>drop traffic from unauthenticated users (user coverage)</li>
<li>pause all asynchronous batch jobs (feature completeness, data freshness)</li>
<li>process only critical requests (feature degradation, data completeness)</li>
</ul>
<h2 id="automation">Automation</h2>
<p>It’s certainly advisable to automate frequently used emergency procedures over time by building them into the system as part of your resiliency patterns (fallbacks, retries on error with adjusted input, etc.). Manual execution of the emergency procedure ensures that a human assesses the situation before proceeding with the procedure, which helps harden the defined preconditions. A few manual executions enable you to evaluate if automation is really of value when compared with than other product features planned. It’s important to factor in on-call health into the prioritization. At times of <a href="https://leaddev.com/leaddev-live/scaling-incident-management-how-we-grew-google-meet-50x-during-covid19">unexpectedly high growth</a> when system availability is a concern, automation is just necessary to cope with overload scenarios efficiently.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Defining emergency procedures requires taking a different view on your system — one where some features are explicitly switched into a degraded mode, thus enabling the overall system to get healthy. The thought exercise of imagining such a degraded, yet usable state is time well spent and highly recommended throughout the design or production readiness stage for your applications. By building in the necessary failure handling mechanisms into the software, or designing it in a way that naturally accommodates the failure states, incident mitigation becomes simpler and less tedious. Having the procedures at hand, you will thank yourself next time you’re on-call in the middle of the night (or day).</p>
]]></content:encoded>
    </item>
    <item>
      <title>Most common design issues found during Production Readiness and Post-Incident Reviews</title>
      <link>https://ocytko.net/posts/most-common-design-issues-found-during-production-readiness-and-post-incident-reviews/</link>
      <pubDate>Sun, 24 May 2020 14:31:56 +0000</pubDate>
      <guid>https://ocytko.net/posts/most-common-design-issues-found-during-production-readiness-and-post-incident-reviews/</guid>
      <description>Operating software in production offers great insights into software quality. Learning from production incidents is key to improving…</description>
      <content:encoded><![CDATA[<p><em>Originally published on <a href="https://medium.com/@bocytko/most-common-design-issues-found-during-production-readiness-and-post-incident-reviews-47b2c9e14a9d">medium</a>.</em></p>
<p>Operating software in production offers great insights into software quality. Learning from production incidents is key to improving existing software and learning how to design reliable applications. The <a href="https://landing.google.com/sre/sre-book/chapters/evolving-sre-engagement-model/">Production Readiness Review</a> is an established practice in Site Reliability Engineering aiming at applying past post-incident experience and findings into the software development process. This post provides an overview of a few common themes and pitfalls I’ve experienced being surfaced during production readiness and post-incident reviews.</p>
<h2 id="using-defaults-is-just-asking-fortrouble">Using defaults is just asking for trouble</h2>
<p>Every framework, http client library or server, connection pool, database, and operating system assume defaults for configuration settings. Aside from <a href="https://blog.shodan.io/its-the-data-stupid/">risking publicly exposing sensitive data</a>, default settings are often impacting the application’s performance and reliability. There are important settings that must be revisited before deploying an application into a production environment.</p>
<p>Overall, the most commonly missed defaults that impact reliability are timeouts: <em>http client timeouts</em> (connection, read timeout), <em>DNS cache timeouts</em>, and database <em>connection pool and statement timeouts</em>. Framework authors bear great responsibility when setting default values, but often fail to make the developer’s life easy. For example, Java’s DNS cache <a href="https://stackoverflow.com/questions/1256556/how-to-make-java-honor-the-dns-caching-timeout#comment37330654_15282042">can be infinite</a>, Apache HttpClient’s v4 <a href="https://github.com/apache/httpcomponents-client/blob/62f2164b8939accfbec01cd4f923cf2202916fa4/httpclient/src/main/java/org/apache/http/client/config/RequestConfig.java#L270-L303">RequestConfig.Builder uses “-1” as default timeouts</a> with its documentation stating “<em>A negative value is interpreted as undefined (system default)</em>” and the newer v5 <a href="https://github.com/apache/httpcomponents-client/blob/92100e13a6aeb9ce04d23df2561c01b330cb961a/httpclient5/src/main/java/org/apache/hc/client5/http/config/RequestConfig.java#L45-L47">uses 3 minutes</a> instead, whereas .NET’s default timeout is <a href="https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient.timeout?view=netcore-3.1">100 seconds</a>. Timeouts need to be set with care, so it’s important to understand the meaning of the different configuration possibilities. For a great overview on the http request lifecycle and timeouts, check out <a href="https://blog.cloudflare.com/the-complete-guide-to-golang-net-http-timeouts/">guide to Go net/http timeouts</a>.</p>
<h2 id="misconfigured-reliability-patterns">Misconfigured reliability patterns</h2>
<h3 id="retries">Retries</h3>
<p><a href="https://landing.google.com/sre/sre-book/chapters/addressing-cascading-failures/#retries">Retrying</a> a failed http request is one of the simplest reliability patterns to implement. When done right, retries have an exponentially increasing wait time between attempts and <a href="https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/">leverage jitter</a> to prevent retry storms leading to the <a href="https://en.wikipedia.org/wiki/Thundering_herd_problem">thundering herd problem</a>. However, setting the timeouts to lower values than those that the dependency uses internally, will lead to subsequent retries that pile up and overload the dependency with work that is useless (given that the client won’t wait for the computation result it initially requested). Timeout values must therefore be carefully aligned with service providers, ideally based on their <a href="https://landing.google.com/sre/sre-book/chapters/service-level-objectives/">SLOs</a>.</p>
<h3 id="circuit-breakers">Circuit breakers</h3>
<p><a href="https://www.martinfowler.com/bliki/CircuitBreaker.html">Circuit breakers</a> enable the application to fail early in case of an overloaded or faulty dependency and serve a degraded experience via fallbacks. Dropping requests that would fail to be processed before its clients time out is also helping the dependency to recover from failure due to the load reduction. The additional time can be used by the service provider to stabilise the system (e.g. through provisioning of additional instances) and thus regain the ability to serve the required load.</p>
<p>Like every reliability pattern, it needs to be properly configured to function well. Configuring an execution timeout that is too high, will lead to a situation where the circuit breaker never opens, thus keeping the load on the dependency when its performance degrades and making recovery more difficult. Correctly configuring the circuit breaker requires careful planning <a href="https://github.com/Netflix/Hystrix/wiki/Configuration#threadpool-properties">based on peak load and p99 latencies</a>. The <a href="https://github.com/Netflix/Hystrix/wiki/Configuration">original Hystrix documentation</a> contains detailed guidance for this. Note that the default execution timeout is <em>1 second</em>. Too bad that official tutorials for frameworks (<a href="https://spring.io/guides/gs/circuit-breaker/">e.g. Spring</a>) fail to even mention the word “timeout” and do not link to the appropriate documentation.</p>
<h3 id="circuit-breakers-require-proper-isolation">Circuit breakers require proper isolation</h3>
<p>Even if timeouts are configured properly, the circuit breaker may not function as intended keeping the business use case in mind as the degradation will be too broad. Imagine an application <strong>A</strong> that is fetching a risk score for shipping addresses by calling system <strong>B</strong>. Because countries may be served by a different risk scoring provider, <strong>B</strong> will have multiple connectors (one for each provider) and will internally hold a logic defining which provider to choose based on the received address. Service <strong>A</strong> has a circuit breaker for calls to <strong>B</strong>. The failure rate of <strong>B</strong> however, will depend on the failure rate of the connected providers and the distribution of the requests across the providers. In such situation, a failure of a single provider can cause the circuit breaker to open preventing calls to be routed to the remaining providers thus degrading responses for all calls.</p>
<p>In the example below, <strong>B</strong> receives 300 rps and calls <strong>Provider 1</strong> with 200 rps. When <strong>Provider 1</strong> becomes unavailable, more than 50% of requests from <strong>A</strong> to <strong>B</strong> fail, causing the circuit breaker to open (following the <a href="https://github.com/Netflix/Hystrix/wiki/Configuration#circuitBreaker.errorThresholdPercentage">default configuration</a> of the popular Hystrix library) whereas requests routed to other providers would have been processed correctly.</p>
<figure class="align-center ">
    <img loading="lazy" src="1_9peO2sWzzWCC0qqNnqRwfw.png#center"
         alt="Figure 1. Provider 1 becomes unavailable, triggering the circuit breaker for calls from A to B to open and reject requests to healthy Providers 2 … N."/> <figcaption>
            <p>Figure 1. <strong>Provider 1</strong> becomes unavailable, triggering the circuit breaker for calls from <strong>A</strong> to <strong>B</strong> to open and reject requests to healthy <strong>Providers 2 … N</strong>.</p>
        </figcaption>
</figure>

<p>A potential solution for this type of insufficient isolation requires a custom strategy for counting the failure rate per provider or the creation of distinct circuit breakers per country within service <strong>A</strong>. Both solutions provide different types of isolation. Note that the former requires exposing additional information through the APIs (provider) whereas the latter is purely steered through knowledge that the caller already has based on the processed addresses (country). The right question to ask about this example is — why doesn’t <strong>B</strong> have circuit breakers to the individual providers? While these would be great to have, it’s often practically impossible, because <strong>B</strong> is a black box (e.g. a 3rd party service, a monolith that’s hard to adjust, …) and cannot be easily adjusted.</p>
<h2 id="mixing-synchronous-and-asynchronous-workloads">Mixing synchronous and asynchronous workloads</h2>
<p>Let’s imagine a service that has a spike in the p99 latencies every x minutes. Sounds familiar? Frequently, it’s a log rotation demon running on the machine where the gzip operation is eating up resources, but more often than not, it is the service itself that causes such spike. It can be a scheduled job that is fetching and processing information, for example periodically refreshing a cache or cleaning up old entries in the DB. If not designed for carefully, such asynchronous execution will impact the p99 latency of the service for synchronous calls.</p>
<p>If you really need to mix such workloads within one application, ensure at least that the http, database connection pools, and thread pools are properly isolated from one another. Otherwise, a long-running async task will impact the synchronous workloads and worst case prevent those from being processed at all.</p>
<h2 id="missing-protection-from-overload-situations">Missing protection from overload situations</h2>
<h3 id="rate-limiting-as-means-to-protect-from-incoming-requestload">Rate limiting as means to protect from incoming request load</h3>
<p>A safe strategy for preventing overload of a service is applying a rate limit to incoming requests. Rate limits are set based on the resources, which impact the scaling ability of the application. These may be driven by its dependencies (e.g. database, 3rd party API) or just costs. Rate limiting can be applied <a href="https://github.com/resilience4j/resilience4j#ratelimiter">within the application itself</a> or outside, for example in API gateways or <a href="https://kubernetes.io/docs/concepts/services-networking/ingress/">ingress</a> controllers. A stricter version of rate limiting is <a href="https://landing.google.com/sre/sre-book/chapters/addressing-cascading-failures/#xref_cascading-failure_load-shed-graceful-degredation">load shedding</a> through request rejection to signal overload situations. To implement load shedding, aside from the technical capabilities to execute this operation, it is helpful to understand per client the business impact of rejecting requests completely as this allows for easy selection of which clients to block first until the service is stabilised.</p>
<p>Rate limits ensure also that clients are forced to negotiate a limit increase with the service provider making scaling needs and capacity planning an explicit conversation. Lastly, rate limiting uncovers and helps dealing with rogue clients of the service. Imagine a service hosting static configuration, which can be cached for a long period of time (e.g. 4 hours). This service should process a request load that is dependent on the number of clients this service has. Load is therefore expected to fluctuate only due to its clients scaling up or down to accommodate incoming load. This service’s load is not expected to follow the traffic patterns of its clients. If it does, it means that such clients are not caching retrieved data correctly and just retrieve it while processing incoming requests.</p>
<h3 id="protection-from-unexpected-request-execution">Protection from unexpected request execution</h3>
<p>While rate limits provide external protection, services must implement internal protection as well. As discussed before, services should define SLOs, on which clients will base their timeout configuration. However, the service itself must be designed to honour this SLO across all operations. This is achieved using timeouts on various levels starting from the persistence layer (e.g. via database statement timeouts) through http connection and request timeouts, up to <a href="http://www.tldp.org/HOWTO/TCP-Keepalive-HOWTO/usingkeepalive.html">TCP keepalives and timeouts</a> and similar operating system settings. Pure compute operations can leverage execution budgets where a maximum execution time is defined, after which the computation will be aborted. If done correctly, it will prevent overload in cases where the calculation time is unexpectedly influenced by the processed data, for example a regex causing <a href="https://swtch.com/~rsc/regexp/regexp1.html">catastrophic backtracking</a>. As <a href="https://blog.cloudflare.com/details-of-the-cloudflare-outage-on-july-2-2019/">past incidents</a> have shown, this is important even for calculation executed in the background, because even though results are not used for the response itself, the calculation consumes CPU cycles and will overload the application anyway.</p>
<h2 id="lack-of-control-of-the-application">Lack of control of the application</h2>
<p>Having the ability to control the applications inner workings is very helpful during incident response. Though it should not be required during normal operations of the service where one relies on reliability patterns, it comes in extremely handy when mitigating incidents. This can be a capability to pause batch jobs, re-trigger processing, or disabling expensive computation in favor of a degraded, but simpler one. Too often, similar changes require a code change and a deployment of the service instead of a simple switch in a feature flag system or via a management API endpoint. The longer the execution of the CI/CD pipeline in the absence of such controls, the worse the ability to quickly react during an incident.</p>
<h2 id="insufficient-visibility">Insufficient visibility</h2>
<p>Aside from monitoring the <a href="https://landing.google.com/sre/sre-book/chapters/monitoring-distributed-systems/">four golden signals</a> (latency, traffic, errors, saturation), further metrics help to gain quick understanding of production incidents. This starts with collecting metrics on connection pools, the rate of incoming requests per client, outgoing requests per dependency, or duration of batch job execution, etc. It can also include metrics very specific to the service itself — for example, if a service offering a batch API is collecting statistics on the batch size, such data can be used to verify whether clients use its API effectively or break it with unexpectedly big batches. Plotting response times per batch size, will provide insights into the processing times and drive discussions on SLOs for the service. Detailed service instrumentation using standard formats, like <a href="https://opentelemetry.io/">OpenTelemetry</a> allows to drill down even further and find causes of incidents across the service call chain as well as identify areas that can be optimised (e.g. through parallelisation of calls) further improving performance and stabilising systems.</p>
<h2 id="summary">Summary</h2>
<p>This post provided an overview of common service design flaws and pitfalls that impact reliability. I can only encourage you to check the out the references provided, especially the <a href="https://landing.google.com/sre/sre-book/toc/">SRE book</a>, which is a great starting point to dive deep into reliability engineering. Further, if you learned something from production issues, please share your <a href="https://k8s.af/">failure stories</a>, so that others can learn from your findings.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
