<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Gabriel&#39;s Personal Blog on Technology</title>
    <link>https://www.gabriel-koch.dev/</link>
    <description>Gabriel&#39;s Personal Blog on Technology</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Tue, 02 Apr 2024 00:00:00 +0000</lastBuildDate>
    
    <atom:link href="https://www.gabriel-koch.dev/index.xml" rel="self" type="application/rss+xml" />
    
    
    <item>
      <title>Import PostgreSQL Query Metrics with OpenTelemetry Native Tooling</title>
      <link>https://www.gabriel-koch.dev/posts/observability/opentelemetry_pg_stat_statements/</link>
      <pubDate>Tue, 02 Apr 2024 00:00:00 +0000</pubDate>
      
      <guid>https://www.gabriel-koch.dev/posts/observability/opentelemetry_pg_stat_statements/</guid>
      <description>&lt;p&gt;OpenTelemetry can be used to easily integrate basic metrics from the
PostgreSQL statistics collector using the &lt;a href=&#34;https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/postgresqlreceiver&#34;&gt;PostgreSQL receiver&lt;/a&gt;.
However, this solution provides only insight into the overall health of your
PostgreSQL instance.
In many cases you have certain slow running queries in your application
causing a problem and you might want to find out which ones they are
or why they&amp;rsquo;re slow.
PostgreSQL&amp;rsquo;s &lt;code&gt;pg_stat_statements&lt;/code&gt; extension and its view are a popular source
for such information.
It can be enabled through an extension and provides aggregated
metrics for each query (see &lt;a href=&#34;https://www.postgresql.org/docs/current/pgstatstatements.html&#34;&gt;PostgreSQL documentation&lt;/a&gt;).
This blog shows you how you can ingest information from this view using
OpenTelemetry capabilities that are already available.&lt;/p&gt;
&lt;h2 id=&#34;importing-query-specific-metrics-from-pg_stat_statements-with-opentelemetry&#34; &gt;Importing Query Specific Metrics from pg_stat_statements with OpenTelemetry
&lt;span&gt;
    &lt;a href=&#34;#importing-query-specific-metrics-from-pg_stat_statements-with-opentelemetry&#34;&gt;
        &lt;svg viewBox=&#34;0 0 28 23&#34; height=&#34;100%&#34; width=&#34;19&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path d=&#34;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;path d=&#34;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;/svg&gt;
    &lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;&lt;p&gt;While the PostgreSQL receiver does not query data from the pg_stat_statements
view, we can use the &lt;a href=&#34;https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/sqlqueryreceiver&#34;&gt;SQL query receiver&lt;/a&gt;
for the OpenTelemetry Collector, to ingest those metrics.
The SQL query receiver can be used to convert results from
arbitrary queries into OpenTelemetry logs or metrics.&lt;/p&gt;
&lt;p&gt;Below configuration snippet configures the SQL query receiver to collect
information from the &lt;code&gt;pg_stat_statements&lt;/code&gt; view and extract two metrics
which are exposed on the collector&amp;rsquo;s Prometheus endpoint.
Additional metrics related to the statement&amp;rsquo;s execution time, plan time,
or cache usage can be extracted by adding objects to the metrics list.&lt;/p&gt;
&lt;figure class=&#34;anyfigure&#34;&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 1
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 2
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 3
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 4
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 5
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 6
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 7
&lt;/span&gt;&lt;span style=&#34;background-color:#3c3d38&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 8
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;background-color:#3c3d38&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 9
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;background-color:#3c3d38&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;10
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;background-color:#3c3d38&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;11
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;background-color:#3c3d38&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;12
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;background-color:#3c3d38&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;13
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;background-color:#3c3d38&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;14
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;background-color:#3c3d38&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;15
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;background-color:#3c3d38&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;16
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;background-color:#3c3d38&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;17
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;background-color:#3c3d38&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;18
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;background-color:#3c3d38&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;19
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;background-color:#3c3d38&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;20
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;background-color:#3c3d38&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;21
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;background-color:#3c3d38&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;22
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;background-color:#3c3d38&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;23
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;24
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;25
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;26
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;27
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;28
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;29
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;30
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;31
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;32
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;33
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;34
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;35
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;36
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;37
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;receivers&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;sqlquery/pgstatstatements&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;driver&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;postgres&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;datasource&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;host=db port=5432 user=oteluser password=otelpw sslmode=disable database=knexdb&amp;#34;&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;queries&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    - &lt;span style=&#34;color:#f92672&#34;&gt;sql&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;select * from pg_stat_statements&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;metrics&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3c3d38&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#f92672&#34;&gt;metric_name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;pg_stat_statements_total_exec_time&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3c3d38&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;value_column&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;total_exec_time&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3c3d38&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;attribute_columns&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3c3d38&#34;&gt;&lt;span&gt;          - &lt;span style=&#34;color:#ae81ff&#34;&gt;queryid&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3c3d38&#34;&gt;&lt;span&gt;          - &lt;span style=&#34;color:#ae81ff&#34;&gt;userid&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3c3d38&#34;&gt;&lt;span&gt;          - &lt;span style=&#34;color:#ae81ff&#34;&gt;dbid&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3c3d38&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;value_type&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;double&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3c3d38&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;data_type&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;sum&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3c3d38&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#f92672&#34;&gt;metric_name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;pg_stat_statements_mean_exec_time&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3c3d38&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;value_column&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;mean_exec_time&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3c3d38&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;attribute_columns&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3c3d38&#34;&gt;&lt;span&gt;          - &lt;span style=&#34;color:#ae81ff&#34;&gt;queryid&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3c3d38&#34;&gt;&lt;span&gt;          - &lt;span style=&#34;color:#ae81ff&#34;&gt;userid&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3c3d38&#34;&gt;&lt;span&gt;          - &lt;span style=&#34;color:#ae81ff&#34;&gt;dbid&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3c3d38&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;value_type&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;double&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3c3d38&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;data_type&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;gauge&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;exporters&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;prometheus&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;endpoint&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;0.0.0.0:8889&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;send_timestamps&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;metric_expiration&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;180m&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;enable_open_metrics&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;add_metric_suffixes&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;resource_to_telemetry_conversion&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;enabled&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;service&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;pipelines&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;metrics&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;receivers&lt;/span&gt;: [&lt;span style=&#34;color:#ae81ff&#34;&gt;sqlquery/pgstatstatements]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;exporters&lt;/span&gt;: [&lt;span style=&#34;color:#ae81ff&#34;&gt;prometheus]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;  &lt;figcaption&gt;Configuration for the OpenTelemetry Collector which reads pg_stat_statements information.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;One problem with this is finding your query based on the query ID.
The &lt;code&gt;pg_stat_statement&lt;/code&gt; view provides the whole query in the &lt;code&gt;query&lt;/code&gt;
column, which could be added as a label for your metrics, but this
will bloat up your metrics.
I also think that adding a label in Prometheus should lead to the creation
of a new dimension and a new time series. Since the query relates 1-1
to the query ID, this is not the case.
The query ID can be logged or queried from the table to support
interpretation. I suggest logging the query ID is the best approach.
Query ID logging in PostgreSQL can be enabled with the following
options:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-conf&#34; data-lang=&#34;conf&#34;&gt;compute_query_id = auto
log_statement_stats = on
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Additionally, this may create up to 40 metrics per query, leading to a lot
of time series. You should think about using the &lt;code&gt;WHERE&lt;/code&gt; clause to
filter queries.
Filtering for the right queries is challenging and depends on your
use case.
In any case, I suggest at least filtering by database user to avoid
PostgreSQL&amp;rsquo;s internal queries showing up.&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34; &gt;Conclusion
&lt;span&gt;
    &lt;a href=&#34;#conclusion&#34;&gt;
        &lt;svg viewBox=&#34;0 0 28 23&#34; height=&#34;100%&#34; width=&#34;19&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path d=&#34;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;path d=&#34;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;/svg&gt;
    &lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;&lt;p&gt;We can incorporate a lot of query specific and generic information about
PostgreSQL in our regular OpenTelemetry compliant observability systems.
To do so:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use the PostgreSQL receiver to extract information from the
statistics collector.&lt;/li&gt;
&lt;li&gt;Enable the &lt;code&gt;pg_stat_statements&lt;/code&gt; extension and use the SQL query receiver
to read metrics for your most important queries.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you have any questions, are interested in hearing more, or sharing
your thoughts with me, let me know on
&lt;a href=&#34;https://www.linkedin.com/in/gabriel-koch-ch/&#34;&gt;LinkedIn&lt;/a&gt;
or &lt;a href=&#34;https://mastodon.online/@elessar_ch&#34;&gt;Mastodon&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>PostgreSQL Tracing</title>
      <link>https://www.gabriel-koch.dev/posts/observability/postgresql-tracing/</link>
      <pubDate>Sun, 18 Feb 2024 13:00:00 +0100</pubDate>
      
      <guid>https://www.gabriel-koch.dev/posts/observability/postgresql-tracing/</guid>
      <description>&lt;p&gt;For a recent project of mine, I was looking into observability
around databases.
I focused on open source tooling with PostgreSQL and OpenTelemetry.
While there are various options for monitoring databases, most do not
focus on integration with OpenTelemetry or otherwise combining PostgreSQL
telemetry with application specific telemetry.
However, for anyone who prefers to see all information regarding
their application at a single glance, having a separate observability tool
for the database is not good enough.&lt;/p&gt;
&lt;p&gt;This post outlines how we can currently integrate PostgreSQL telemetry
in the OpenTelemetry world, how it could be improved, and showcases a
prototype I built to generate traces from PostgreSQL queries.&lt;/p&gt;
&lt;h2 id=&#34;propagating-trace-context-along-your-sql-statements&#34; &gt;Propagating Trace Context Along your SQL Statements
&lt;span&gt;
    &lt;a href=&#34;#propagating-trace-context-along-your-sql-statements&#34;&gt;
        &lt;svg viewBox=&#34;0 0 28 23&#34; height=&#34;100%&#34; width=&#34;19&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path d=&#34;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;path d=&#34;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;/svg&gt;
    &lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;&lt;p&gt;With Sqlcommenter Google introduced a suite of middleware and plugins
that enable your ORMs to augment SQL statements with comments containing
information about the code that causes its execution
(see &lt;a href=&#34;https://google.github.io/sqlcommenter/&#34;&gt;Sqlcommenter&lt;/a&gt;).
It has been donated to OpenTelemetry and integrated into many of the
official OpenTelemetry instrumentation libraries.&lt;/p&gt;
&lt;p&gt;If you use the OpenTelemetry instrumentation for Node.js and the
&lt;a href=&#34;https://knexjs.org/&#34;&gt;Knex SQL Query Builder&lt;/a&gt;,
the &lt;a href=&#34;https://www.npmjs.com/package/@opentelemetry/instrumentation-knex&#34;&gt;Knex instrumentation&lt;/a&gt;
can be used to add comments to your SQL queries such as shown in the
example below.&lt;/p&gt;
&lt;figure class=&#34;anyfigure&#34;&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;select&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;cartItem&amp;#34;&lt;/span&gt;.&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;id&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;cartItem&amp;#34;&lt;/span&gt;.&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;product_id&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;cartItem&amp;#34;&lt;/span&gt;.&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;quantity&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;product&amp;#34;&lt;/span&gt;.&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;name&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;       &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;product&amp;#34;&lt;/span&gt;.&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;price&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;cartItem&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;inner&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;join&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;product&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;on&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;cartItem&amp;#34;&lt;/span&gt;.&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;product_id&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;product&amp;#34;&lt;/span&gt;.&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;id&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;where&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;user_id&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;$&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3c3d38&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/*traceparent=&amp;#39;00-866e8698eac009878483cfd029f62af9-bb14062982c63db3-01&amp;#39;*/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;figcaption&gt;SQL statement that includes trace context information in a comment.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Once this instrumentation is set up, we also need to get information about the
query execution from the database, including the traceparent information.
Generally, I found that, aside from the information we can query from the
&lt;code&gt;pg_stat_statements&lt;/code&gt; view, we look for the query duration, the query plan,
and information about cache hits for a query.
We can configure PostgreSQL to log this information.
For example &lt;code&gt;log_statement = &#39;all&#39;&lt;/code&gt; creates log entries for each SQL command
sent to the server, including the duration.&lt;/p&gt;
&lt;p&gt;With the &lt;a href=&#34;https://www.postgresql.org/docs/current/auto-explain.html&#34;&gt;&lt;code&gt;auto_explain&lt;/code&gt;&lt;/a&gt;
module we can automatically log query plans. To reduce the amount of log
entries generated, we have the options to only log the query plan for slow
queries or to enable sampling.
The following configuration configures &lt;code&gt;auto_explain&lt;/code&gt;
to log all queries, including the actual execution statistics and buffer
information for each step of the query plan/executor.&lt;/p&gt;
&lt;figure class=&#34;anyfigure&#34;&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ini&#34; data-lang=&#34;ini&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;shared_preload_libraries&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;auto_explain&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;auto_explain.log_min_duration&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;0 # e.g. 10ms&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;auto_explain.log_format&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;json&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;auto_explain.log_analyze&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;auto_explain.log_buffers&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;figcaption&gt;PostgreSQL configuration for &lt;code&gt;auto_explain&lt;/code&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Now we can look at a trace and take the trace ID to search in our logs and
find context information about what happened on the database.
That way we can better investigate why some specific queries are slow.
Additionally, we can infer some information about our network latency by
comparing total elapsed time for the query in the application and the
duration on the database.&lt;/p&gt;
&lt;figure class=&#34;zoomable&#34;&gt;&lt;img src=&#34;grafana_jaeger_loki.png&#34;
         alt=&#34;Screenshot of Grafana that shows a trace panel on the left and the logs panel on the right. On the active span there is a button that states &amp;#34;Logs for this span&amp;#34;. On the logs panel, the logs query searches for a trace id.&#34;/&gt;&lt;figcaption&gt;
            &lt;p&gt;Grafana with Jaeger and Loki&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;If you use Grafana, you can set up your tracing backend, such as Jaeger
or Tempo, to jump directly from a trace to related logs using the
&lt;a href=&#34;https://grafana.com/docs/grafana/latest/datasources/jaeger/#trace-to-logs&#34;&gt;Logs-To-Traces&lt;/a&gt;
feature. The figure above shows a &amp;ldquo;Logs for this span&amp;rdquo; button on a trace span.
When clicked, this button opens the log search on the right side and
automatically searches for logs with that trace ID. In the logs, we can look
for the fields that contain the plan information as visible in the figure
below.&lt;/p&gt;
&lt;figure class=&#34;zoomable&#34;&gt;&lt;img src=&#34;grafana_loki_query_plan.png&#34;
         alt=&#34;A screenshot of a Grafana logs panel with the start of a query plan.&#34;/&gt;&lt;figcaption&gt;
            &lt;p&gt;Grafana logs panel showing the query plan.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;extracting-traces-from-postgresql&#34; &gt;Extracting Traces from PostgreSQL
&lt;span&gt;
    &lt;a href=&#34;#extracting-traces-from-postgresql&#34;&gt;
        &lt;svg viewBox=&#34;0 0 28 23&#34; height=&#34;100%&#34; width=&#34;19&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path d=&#34;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;path d=&#34;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;/svg&gt;
    &lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;&lt;p&gt;Setting up Grafana to jump from traces to logs is feasible, but it&amp;rsquo;s not
very easy.
Additionally, I think it&amp;rsquo;s still quite hard to find the plan information in
your logs.
From a usability point of view, we could do better. For example, it would
be neat to have this information directly available as trace spans in our
tracing system.
Proprietary Google Cloud SQL and pganalyze are able to do this, but for any
other instance of PostgreSQL I did not find a way to achieve this.&lt;/p&gt;
&lt;p&gt;Since most of the required information is available in our log entries, I set
out to build a prototype that extracts traces from our PostgreSQL
query plan logs and respects the &lt;code&gt;traceparent&lt;/code&gt; information propagated
to PostgreSQL from our instrumentation.&lt;/p&gt;
&lt;p&gt;The prototype is built as a connector for OpenTelemetry&amp;rsquo;s collector.
A connector is a new component type of the collector that acts as a bridge
between telemetry streams. One use case is receiving signals from one
pipeline, creating another signal type, and exporting those signals into
another pipeline.
For example, the
&lt;a href=&#34;https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/connector/spanmetricsconnector&#34;&gt;span metrics connector&lt;/a&gt;
can be used to aggregate request, error, and duration (RED) metrics
from trace spans.&lt;/p&gt;
&lt;p&gt;The connector is a good way to achieve what we desired, and I used it to
build a rough prototype that I published on
&lt;a href=&#34;https://github.com/elessar-ch/pgtraceconnector/&#34;&gt;GitHub (elessar-ch/pgtraceconnector)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The figure below shows Grafana with traces from PostgreSQL logs
using the connector. Each step of the query plan / executor is
displayed as an individual span and the information from
&lt;code&gt;auto_explain&lt;/code&gt; is available in the span attributes.
&lt;figure class=&#34;zoomable&#34;&gt;&lt;img src=&#34;grafana_tracing_with_pgtraces.png&#34;
         alt=&#34;A screenshot of Grafana with a trace panel that includes spans for a query plan.&#34;/&gt;&lt;figcaption&gt;
            &lt;p&gt;Grafana&amp;rsquo;s trace view including spans from the database.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;If you want to check it out yourself, a test setup
using Docker Compose with an appropriately configured PostgreSQL database,
an example application, and OpenTelemetry setup is available on
&lt;a href=&#34;https://github.com/elessar-ch/sql-tracing&#34;&gt;GitHub (elessar-ch/sql-tracing)&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34; &gt;Conclusion
&lt;span&gt;
    &lt;a href=&#34;#conclusion&#34;&gt;
        &lt;svg viewBox=&#34;0 0 28 23&#34; height=&#34;100%&#34; width=&#34;19&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path d=&#34;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;path d=&#34;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;/svg&gt;
    &lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;&lt;p&gt;To incorporate query specific information into our OpenTelemetry compliant
observability systems, we can propagate traceparent information to the
database using the OpenTelemetry instrumentation libraries and correlate
our traces with logs.
A more user-friendly way to get this information at a glance is to extract
traces from the logs. This is feasible and practically achievable through
implementing a connector in the OpenTelemetry collector.&lt;/p&gt;
&lt;p&gt;If you have any questions, are interested in hearing more, or want to share
your thoughts with me, let me know on
&lt;a href=&#34;https://www.linkedin.com/in/gabriel-koch-ch/&#34;&gt;LinkedIn&lt;/a&gt;
or &lt;a href=&#34;https://mastodon.online/@elessar_ch&#34;&gt;Mastodon&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>
