<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Elastic Observability Labs - Serverless</title>
        <link>https://www.elastic.co/observability-labs</link>
        <description>Trusted security news &amp; research from the team at Elastic.</description>
        <lastBuildDate>Wed, 08 Jul 2026 20:26:25 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Elastic Observability Labs - Serverless</title>
            <url>https://www.elastic.co/observability-labs/assets/observability-labs-thumbnail.png</url>
            <link>https://www.elastic.co/observability-labs</link>
        </image>
        <copyright>© 2026. Elasticsearch B.V. All Rights Reserved</copyright>
        <item>
            <title><![CDATA[Debugging Azure Networking for Elastic Cloud Serverless]]></title>
            <link>https://www.elastic.co/observability-labs/blog/debugging-aks-packet-loss</link>
            <guid isPermaLink="false">debugging-aks-packet-loss</guid>
            <pubDate>Thu, 05 Jun 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Learn how Elastic SREs uncovered and resolved unexpected packet loss in Azure Kubernetes Service (AKS), impacting Elastic Cloud Serverless performance.]]></description>
            <content:encoded><![CDATA[&lt;h2&gt; Summary of Findings &lt;/h2&gt; 
<p>Elastic's Site Reliability Engineering team (SRE) observed unstable throughput and packet loss in Elastic Cloud Serverless running on Azure Kubernetes Service (AKS). After investigation, we identified the primary contributing factors to be RX ring buffer overflows and kernel input queue saturation on SR-IOV interfaces. To address this, we increased RX buffer sizes and adjusted the netdev backlog, which significantly improved network stability.</p>
&lt;h2&gt; Setting the Scene &lt;/h2&gt;
<p><a href="https://www.elastic.co/cloud/serverless">Elastic Cloud Serverless</a> is a fully managed solution that allows you to deploy and use Elastic for your use cases without managing the underlying infrastructure. Built on Kubernetes, it represents a shift in how you interact with Elasticsearch. Instead of managing clusters, nodes, data tiers, and scaling, you create serverless projects that are fully managed and automatically scaled by Elastic. This abstraction of infrastructure decisions allows you to focus solely on gaining value and insight from your data.</p>
<p>Elastic Cloud Serverless is generally available (GA) on AWS, GCP and currently in <a href="https://www.elastic.co/guide/en/serverless/current/regions.html">Technical Preview on Azure</a>. As part of preparing Elastic Cloud Serverless GA on Azure, we have been conducting extensive performance and scalability tests to ensure that our users get a consistent and reliable user experience.</p>
<p>In this post, we’ll take you behind the scenes of a deep technical investigation into a surprising performance issue that affected Serverless Elasticsearch in our Azure Kubernetes clusters. At first, the network seemed like the least likely place to look, especially with a high-speed 100 Gb/s interface on the host backing it. But as we dug deeper, with help from the Microsoft Azure team, that’s exactly where the problem led us.</p>
&lt;h2&gt; Unexpected Results! &lt;/h2&gt;
<p>While the high-level architectures and system design patterns of the major cloud provider’s systems are often similar, the implementations are different, and these differences can have dramatic impacts on a system’s performance characteristics.</p>
<p>One of the most significant differences between the different cloud providers is that the underlying hypervisor software and server hardware of the Virtual Machines can vary significantly, even between instance families of the same provider.</p>
<p>There is no way to fully abstract the hardware away from an application like Elasticsearch. Fundamentally, its performance is dictated by the CPU, memory, disks, and network interfaces on the physical server. In preparation for the Elastic Cloud Serverless GA on Azure, our Elasticsearch Performance team kicked off large-scale load testing against Serverless Elasticsearch projects running on <a href="https://docs.azure.cn/en-us/aks/what-is-aks">Azure Kubernetes Service (AKS)</a>, using <a href="https://azure.microsoft.com/en-us/blog/azure-cobalt-100-based-virtual-machines-are-now-generally-available/">ARM-based VMs</a> (we’re big fans!). Throughout this process, we relied heavily on Elastic tools to analyse system behaviour, identify bottlenecks, and validate performance under load.</p>
<p>To perform these scale and load tests, the Elasticsearch Performance team use <a href="https://github.com/elastic/rally">Rally</a>, an open-source benchmarking tool designed to measure the performance of Elasticsearch clusters. The workload (or in Rally nomenclature, ‘Track’) used for these tests was the <a href="https://github.com/elastic/rally-tracks/tree/master/github_archive">GitHub Archive Track</a>. Rally collects and sends test telemetry using the <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/python">official Python client</a> to a separate Elasticsearch cluster running <a href="https://www.elastic.co/observability">Elastic Observability</a>, which allows for monitoring and analysis during these scale and load tests in real time via <a href="https://www.elastic.co/docs/explore-analyze">Kibana</a>.</p>
<p>When we looked at the results, we observed that the indexing rate (the number of docs/s) for the Serverless projects was not only much lower than we had expected for the given hardware, but the throughput was also quite unstable. There were peaks and valleys, interspersed with frequent errors, whereas we were instead expecting a stable indexing rate for the duration of the test.</p>
<p>These tests are designed to push the system to its limits, and in doing so, they surfaced unexpected behavior in the form of unstable indexing throughput and intermittent errors. This was precisely the kind of problem we'd hoped to uncover prior to going GA — giving us the opportunity to work closely with Azure.</p>
&lt;div align=&quot;center&quot;&gt;
![Indexing Rate with Packet Loss](/assets/images/debugging-aks-packet-loss/indexing-rate-before.png)
_A Kibana visualisation of Rally telemetry, showing fluctuating Elasticsearch indexing rates alongside spikes in 5xx and 4xx HTTP error responses._
&lt;/div&gt;
&lt;h2&gt; Debugging! &lt;/h2&gt;
<p>Debugging performance issues can feel a little bit like trying to find a <a href="https://www.youtube.com/watch?v=7AO4wz6gI3Q">‘Butterfly in a Hurricane’</a>, so it’s crucial that you take a methodological approach to analysing application and system performance.</p>
<p>Using methodologies helps you to be more consistent and thorough in your debugging, and avoids missing things. We started with the <a href="https://www.brendangregg.com/usemethod.html">Utilisation Saturation and Errors (USE) Method</a>, looking at both the client and server side to identify any obvious bottlenecks in the system.</p>
<p>Elastic's Site Reliability Engineers (SREs) maintain a suite of custom <a href="https://www.elastic.co/docs/solutions/observability/get-started/what-is-elastic-observability">Elastic Observability</a> dashboards designed to visualise data collected from various <a href="https://www.elastic.co/docs/extend/integrations/what-is-an-integration">Elastic Integrations</a>. These dashboards provide deep visibility into the health and performance of Elastic Cloud infrastructure and systems.</p>
<p>For this investigation, we leveraged a custom dashboard built using metrics and log data from the <a href="https://www.elastic.co/docs/reference/integrations/system">System</a> and <a href="https://www.elastic.co/docs/reference/integrations/linux">Linux</a> Integrations:</p>
&lt;div align=&quot;center&quot;&gt;
  ![Node Overview Dashboard](/assets/images/debugging-aks-packet-loss/overview-dashboard.png)
  _One of many Elastic Observability dashboards built and maintained by the SRE team._
&lt;/div&gt;
<p>Following the USE Method, these dashboards highlight resource utilisation, saturation, and errors across our systems. With their help, we quickly identified that the AKS nodes hosting the Elasticsearch pods under test were dropping thousands of packets per second.</p>
&lt;div align=&quot;center&quot;&gt;
![Node Packet Loss Before Tuning](/assets/images/debugging-aks-packet-loss/packet-loss-before.png)
_A Kibana visualisation of [Elastic Agent's System Integration](https://www.elastic.co/docs/reference/integrations/system), showing the rate of packet drops per second for AKS nodes._
&lt;/div&gt;
<p>Dropping packets forces reliable protocols, such as TCP, to retransmit any missing packets. These retransmissions can introduce significant delays, which kills the throughput of any system where client requests are only triggered upon the previous request completion (known as a <a href="https://www.usenix.org/legacy/event/nsdi06/tech/full_papers/schroeder/schroeder.pdf">Closed System</a>).</p>
<p>To investigate further, we jumped onto one of the AKS nodes exhibiting the packet loss to check the basics. First off, we wanted to identify what type of packet drops or errors we’re seeing; is it for specific pods, or the host as a whole?</p>
<pre><code>root@aks-k8s-node-1:~# ip -s link show
2: eth0: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
    link/ether 7c:1e:52:be:ce:5e brd ff:ff:ff:ff:ff:ff
    RX:    bytes   packets errors dropped  missed   mcast
    373507935420 134292481      0       0       0      15
    TX:    bytes   packets errors dropped carrier collsns
    644247778936 303191014      0       0       0       0
3: enP42266s1: &lt;BROADCAST,MULTICAST,SLAVE,UP,LOWER_UP&gt; mtu 1500 qdisc mq master eth0 state UP mode DEFAULT group default qlen 1000
    link/ether 7c:1e:52:be:ce:5e brd ff:ff:ff:ff:ff:ff
    RX:    bytes   packets errors dropped  missed   mcast
    386782548951 307000571      0       0 5321081       0
    TX:    bytes   packets errors dropped carrier collsns
    655758630548 477594747      0       0       0       0
    altname enP42266p0s2
15: lxc0ca0ec41ecd2@if14: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether f6:f5:5e:c9:4e:fb brd ff:ff:ff:ff:ff:ff link-netns cni-3f90ab53-df66-cac5-bd19-9cea4a68c29b
    RX:    bytes   packets errors dropped  missed   mcast
    627954576078  54297550      0    1600       0       0
    TX:    bytes   packets errors dropped carrier collsns
    372155326349 133538064      0    3927       0       0
</code></pre>
<p>In this output you can see the <code>enP42266s1</code> interface is showing a significant number of packets in the <code>missed</code> column. That’s interesting, sure, but what does missed actually represent? And what is <code>enP42266s1</code>?</p>
<p>To understand, let’s look at roughly what happens when a packet arrives at the NIC:</p>
<ol>
<li>A packet arrives at the NIC from the network.</li>
<li>The NIC uses DMA (Direct Memory Access) to place the packet into a receive ring buffer allocated in memory by the kernel, mapped for use by the NIC. Since our NICs supports multiple hardware queues, each queue has its own dedicated ring buffer, IRQ, and NAPI context.</li>
<li>The NIC raises a hardware interrupt (IRQ) to notify the CPU that a packet is ready.</li>
<li>The CPU runs the NIC driver’s IRQ handler. The driver schedules a NAPI (New API) poll to defer packet processing to a softirq context. A mechanism in the Linux kernel that defers work to be processed outside of the hard IRQ context, for better batching and CPU efficiency, enabling improved scalability.</li>
<li>The NAPI poll function is executed in a softirq context (<code>NET_RX_SOFTIRQ</code>) and retrieves packets from the ring buffer. This polling continues either until the driver’s packet budget is exhausted (<code>net.core.netdev_budget</code>) or the time limit is hit (<code>net.core.netdev_budget_usecs</code>).</li>
<li>Each packet is wrapped in an <code>sk_buff</code> (socket buffer) structure, which includes metadata such as protocol headers, timestamps, and interface identifiers.</li>
<li>If the networking stack is slower than the rate at which NAPI fetches packets, excess packets are queued in a per-CPU backlog queue (via <code>enqueue_to_backlog</code>). The maximum size of this backlog is controlled by the <code>net.core.netdev_max_backlog</code> sysctl.</li>
<li>Packets are then handed off to the kernel’s networking stack for routing, filtering, and protocol-specific processing (e.g. TCP, UDP).</li>
<li>Finally, packets reach the appropriate socket receive buffer, where they are available for consumption by the user-space application.</li>
</ol>
<p>Visualised, it looks something like this:</p>
&lt;div align=&quot;center&quot;&gt;
![Linux Packet Flow Diagram](/assets/images/debugging-aks-packet-loss/packet-flow.png)
_Image © 2018 Leandro Moreira. Used under the [BSD 3-Clause License](https://opensource.org/licenses/BSD-3-Clause). Source: [GitHub repository](https://github.com/leandromoreira/linux-network-performance-parameters)._
&lt;/div&gt;
<p>The <code>missed</code> counter is incremented whenever the NIC tries to DMA a packet into a fully occupied <a href="https://en.wikipedia.org/wiki/Circular_buffer">ring buffer</a>. The NIC essentially &quot;misses&quot; the chance to deliver the packet to the VM’s memory. However, what’s most interesting is that this counter seldom increments for VMs. This is because Virtual NICs are usually implemented as software via the hypervisor, which typically has much more flexible memory management compared to the physical NICs and can reduce the chance of ring buffer overflow.</p>
<p>We mentioned earlier that we’re building Azure Elasticsearch Serverless on top of Azure’s AKS service, which is important to note because all of our AKS nodes use an Azure feature called <a href="https://learn.microsoft.com/en-us/azure/virtual-network/accelerated-networking-overview">Accelerated Networking</a>. In this setup, network traffic is delivered directly to the VM’s network interface, bypassing the hypervisor. This is enabled by <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/network/overview-of-single-root-i-o-virtualization--sr-iov-">single root I/O virtualization (SR-IOV)</a>, which offers much lower latency and higher throughput than traditional VM networking. Each node is physically connected to a 100 Gb/s network interface, although the SR-IOV Virtual Function (VF) exposed to the VM typically provides only a fraction of that total bandwidth.</p>
<p>Despite the VM only having a fraction of the 100 Gb/s bandwidth, microbursts are still very possible. These physical interfaces are so fast that they can transmit and receive multiple packets in just nanoseconds, far faster than most buffers or processing queues can absorb. At these timescales, even a short-lived burst of traffic can overwhelm the receiver, leading to dropped packets and unpredictable latency.</p>
<p>Direct access to the SR-IOV interface means that our VMs are responsible for handling the hardware interrupts triggered by the NIC in a timely manner, if there's any delay in handling the hardware interrupt (e.g. waiting to be scheduled onto CPU by the hypervisor) then network packets can be missed!</p>
&lt;h2&gt; Firstly - NIC-level Tuning &lt;/h2&gt;
<p>Since we'd confirmed that our VMs were using SR-IOV, we established that the <code>enP42266s1</code> and <code>eth0</code> interfaces <a href="https://learn.microsoft.com/en-us/azure/virtual-network/accelerated-networking-how-it-works">were a bonded pair and acted as a single interface</a>. Knowing this, then we reasoned that we should be able to adjust the ring buffer values directly using <code>ethtool</code>.</p>
<pre><code>root@aks-k8s-node-1:~# ethtool -g enP42266s1
Ring parameters for enP42266s1:
Pre-set maximums:
RX:		8192
RX Mini:	n/a
RX Jumbo:	n/a
TX:		8192
Current hardware settings:
RX:		1024
RX Mini:	n/a
RX Jumbo:	n/a
TX:		1024
</code></pre>
<p>In the output above, we were using only 1/8th of the available ring buffer descriptors. These values were set by the OS defaults, which generally aim to balance performance and resource usage. Set too low, they risk packet drops under load; set too high, they can lead to unnecessary memory consumption. We knew that the VMs were backed by a virtual function carved out of the directly attached 100 Gb/s network interface, which is fast enough to deliver microbursts that could easily overwhelm small buffers. To better absorb those short, high-intensity bursts of traffic, we increased the NIC’s RX ring buffer size from 1024 to 8192. Using a privileged DaemonSet, we rolled out the change across all of our AKS nodes by installing <a href="https://en.wikipedia.org/wiki/Udev">a <code>udev</code> rule</a> to automatically increase the buffer size:</p>
<pre><code># Match Mellanox ConnectX network cards and run ethtool to update the ring buffer settings
ENV{INTERFACE}==&quot;en*&quot;, ENV{ID_NET_DRIVER}==&quot;mlx5_core&quot;, RUN+=&quot;/sbin/ethtool -G %k rx ${CONFIG_AZURE_MLX_RING_BUFFER_SIZE} tx ${CONFIG_AZURE_MLX_RING_BUFFER_SIZE}&quot;
</code></pre>
&lt;div align=&quot;center&quot;&gt;
![AKS Node Packet Loss after RX ring buffer change](/assets/images/debugging-aks-packet-loss/packet-loss-after.png)
_A Kibana visualisation of [Elastic Agent's System Integration](https://www.elastic.co/docs/reference/integrations/system), showing packet loss reduced by ~99% after increasing the NIC's RX ring buffer values._
&lt;/div&gt;
<p>As soon as the change had been applied to all AKS nodes we stopped ‘missing’ RX packets! Fantastic! As a result of this simple change we observed a significant improvement in our indexing throughput and stability.</p>
&lt;div align=&quot;center&quot;&gt;
![Indexing rate after RX ring buffer change](/assets/images/debugging-aks-packet-loss/indexing-rate-after.png)
_A Kibana visualisation of Rally telemetry, showing stable and improved Elasticsearch indexing rates after increasing the RX ring buffer size._
&lt;/div&gt;
<p>Job done, right? Not quite..</p>
&lt;h2&gt; Further improvements - Kernel-level Tuning &lt;/h2&gt;
<p>Eagle eyed readers may have noticed two things:</p>
<ol>
<li>In the previous screenshot, despite adjusting the physical RX ring buffer values, we still observed a small number of <code>dropped</code> packets on the TX side.</li>
<li>In the original <code>ip link -s show</code> output, one of the ‘logical’ interfaces used by the Elasticsearch pod was showing <code>dropped</code> packets on both the TX and RX sides.</li>
</ol>
<pre><code>15: lxc0ca0ec41ecd2@if14: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether f6:f5:5e:c9:4e:fb brd ff:ff:ff:ff:ff:ff link-netns cni-3f90ab53-df66-cac5-bd19-9cea4a68c29b
    RX:    bytes   packets errors dropped  missed   mcast
    627954576078  54297550      0    1600       0       0
    TX:    bytes   packets errors dropped carrier collsns
    372155326349 133538064      0    3927       0       0
</code></pre>
<p>So, we continued to dig. We’d eliminated ~99% of the packet loss, and the remaining loss rate wasn’t as significant as what we’d started with, but we still wanted to understand why it was occurring even after adjusting the RX ring buffer size of the NIC.</p>
<p>So what does <code>dropped</code> represent, and what is this <code>lxc0ca0ec41ecd2</code> interface? <code>dropped</code> is similar to <code>missed</code>, but only occurs when packets are deliberately dropped by the kernel or network interface. Crucially though, it doesn’t tell you why a packet was dropped. As for the <code>lxc0ca0ec41ecd2</code> interface, we use the <a href="https://learn.microsoft.com/en-us/azure/aks/azure-cni-powered-by-cilium">Azure CNI Powered by Cilium</a> to provide the network functionality to our AKS clusters. Any pod spun up on an AKS node gets a ‘logical’ interface, which is a virtual ethernet (<code>veth</code>) pair that connects the pod’s network namespace with the host’s network namespace. It was here that we were dropping packets.</p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/debugging-aks-packet-loss/aks-node-network-topology.png" alt="AKS Node Networking Diragram" /></p>
<p>In our experience, packet drops at this layer are unusual, so we started digging deeper into the cause of the drops. There are numerous ways you can debug why a packet is being dropped, but one of the easiest is <a href="https://perfwiki.github.io/main/">to use <code>perf</code></a> attach to the <code>skb:kfree_skb</code> tracepoint. The &quot;socket buffer&quot; (<code>skb</code>) is the primary data structure used to represent network packets in the Linux kernel. When a packet is dropped, its corresponding socket buffer is usually freed, triggering the <code>kfree_skb</code> tracepoint. Using <code>perf</code> to attach to this event allowed us to capture stack traces to analyze the cause of the drops.</p>
&lt;div align=&quot;center&quot;&gt;
```
# perf record -g -a -e skb:kfree_skb
```
&lt;/div&gt;
<p>We left this to run for ~10 minutes or so to capture as many drops as possible, and then ‘heavily inspired’ by <a href="https://gist.github.com/bobrik/0e57671c732d9b13ac49fed85a2b2290">this GitHub Gist by Ivan Babrou</a>, we converted the stack traces into an ‘easier’ to read <a href="https://github.com/brendangregg/FlameGraph">Flamegraphs</a>:</p>
<pre><code># perf script | sed -e 's/skb:kfree_skb:.*reason:\(.*\)/\n\tfffff \1 (unknown)/' -e 's/^\(\w\+\)\s\+/kernel /' &gt; stacks.txt
cat stacks.txt | stackcollapse-perf.pl --all | perl -pe 's/.*?;//' | sed -e 's/.*irq_exit_rcu_\[k\];/irq_exit_rcu_[k];/' | flamegraph.pl --colors=java --hash --title=aks-k8s-node-1 --width=1440 --minwidth=0.005 &gt; aks-k8s-node-1.svg
</code></pre>
&lt;div align=&quot;center&quot;&gt;
![AKS Node Packet Loss Flamegraph](/assets/images/debugging-aks-packet-loss/aks-packet-loss-flamegraph.png)
_A Flamegraph showing the various stack trace ancestry of packet loss._
&lt;/div&gt;
<p>The flamegraph here shows how often different functions appeared in stack traces for packets drops. Each box represents a function call and wider boxes mean the function appears more frequently in the traces. The stack's ancestry builds upward from the bottom with earlier calls, to the top with later calls.</p>
<p>Firstly, we quickly discovered that unfortunately the <code>skb_drop_reason</code> enum <a href="https://github.com/torvalds/linux/commit/c504e5c2f9648a1e5c2be01e8c3f59d394192bd3">was only added in Kernel 5.17</a> (Azure’s Node Image at the time was using 5.15). This meant that there was no single human readable message that told us why the packets were being dropped, instead all we got was <code>NOT_SPECIFIED</code>. To work out why packets were being dropped we needed to do a little sleuthing through the stack traces to work out what code paths were being taken when a packet was dropped.</p>
<p>In the flamegraph above you can see that many of the stack traces include <code>veth</code> driver function calls (e.g. <code>veth_xmit</code>), and many end abruptly with a call to the <code>enqueue_to_backlog</code> function. When many stacks end at the same function (like <code>enqueue_to_backlog</code>) it suggests that function is a common point where packets are being dropped. If you go back to the earlier explanation of what happens when a packet arrives at the NIC, you’ll notice that in step 7 we explained:</p>
<blockquote>
<p><em>7. If the networking stack is slower than the rate at which NAPI fetches packets, excess packets are queued in a per-CPU backlog queue (via <code>enqueue_to_backlog</code>). The maximum size of this backlog is controlled by the <code>net.core.netdev_max_backlog</code> sysctl.</em></p>
</blockquote>
<p>Using the same privileged DaemonSet method for the RX ring buffer adjustment, we set the value of the <code>net.core.netdev_max_backlog</code> adjustable kernel parameter from 1000 to 32768:</p>
<pre><code>/usr/sbin/sysctl -w net.core.netdev_max_backlog=32768
</code></pre>
<p>This value was based on the fact we knew the hosts were using a 100 Gb/s SR-IOV NIC, even if the VM was allowed only a fraction of the total bandwidth. We acknowledge that it’s worth revisiting this value in the future to see if it can be better optimised to not waste extraneous memory, but at the time “perfect was the enemy of good”.</p>
<p>We re-ran the load tests and compared the three sets of results we’d collected thus far.</p>
&lt;div align=&quot;center&quot;&gt;
![Final Indexing Rate Results](/assets/images/debugging-aks-packet-loss/indexing-rate-final.png)
_A Kibana visualisation of Rally results, comparing impact to median throughput after each configuration change._
&lt;/div&gt;
<table>
<thead>
<tr>
<th>Tuning Step</th>
<th>Packet Loss</th>
<th>Median indexing throughput</th>
</tr>
</thead>
<tbody>
<tr>
<td>Baseline</td>
<td>High</td>
<td>~18,000 docs/s</td>
</tr>
<tr>
<td>+RX Buffer</td>
<td>~99% drop ↓</td>
<td>~26,000 (+ ~40% from baseline)</td>
</tr>
<tr>
<td>+Backlog &amp; +RX Buffer</td>
<td>Near zero</td>
<td>~29,000 (+ ~60% from baseline)</td>
</tr>
</tbody>
</table>
<p>Here you can see the P50 of throughput in docs/s over the course of the hours-long load tests. Compared to the baseline, we saw a roughly <strong>~40%</strong> increase in throughput by only adjusting the RX ring buffer values, and a <strong>~50-60%</strong> increase with both the RX ring buffer and backlog changes! Hooray!</p>
<p>A great result and one more step on our journey towards better Serverless Elasticsearch performance.</p>
&lt;h2&gt; Working with Azure &lt;/h2&gt;
<p>It’s great that we were able to quickly identify and mitigate the majority of our packet loss issues, but since we were using AKS with AKS node images, it made sense to engage with Azure to understand why the defaults weren’t working for our workload.</p>
<p>We walked Azure through our investigation, mitigations and results, and asked for some additional validation of our mitigations. Azure Engineering confirmed that the host NICs were not discarding packets, which confirmed that everything arriving at the host level was passed through to the hypervisor on the host. Further investigation confirmed that no loss or discards were occurring to Azure network fabric, or internal to the hypervisor – which shifted focus from the host to the guest OS and why the guest OS kernel was slow when reading packets off of the <code>enP*</code> SR-IOV interfaces.</p>
<p>Given the complexity of our load testing scenario — which involved configuring multiple systems and tools, including <a href="https://www.elastic.co/observability">Elastic Observability</a>, we also developed a simplified reproduction of the packet loss issue using <a href="https://github.com/esnet/iperf"><code>iperf3</code></a>. This simplified test was created specifically to share with Azure for targeted analysis, and added to the broader monitoring and analysis enabled by Elastic Observability and Rally.</p>
<p>With this reproduction Azure was able to confirm the increasing <code>missed</code> and <code>dropped</code> packet counters we had observed, and confirmed the increased RX ring buffer and <code>netdev_max_backlog</code> increase as the recommended mitigations.</p>
&lt;h2&gt; Conclusion &lt;/h2&gt;
<p>While cloud providers offer various abstractions to manage your resources, the underlying hardware ultimately determines your application's performance and stability. High-performance hardware often requires tuning at the operating system level, well beyond the default settings most environments ship with. In managed platforms like AKS, where Azure controls both the node images and infrastructure, it is easy to overlook the impact of low-level configurations such as network device ring buffer sizes or sysctls like <code>net.core.netdev_max_backlog</code>.</p>
<p>Our experience shows that even with the convenience of a managed Kubernetes service, performance issues can still emerge if these hardware parameters are not tuned appropriately. It was tempting to assume that high-speed 100 Gb/s network interfaces, directly attached to the VM using SR-IOV would eliminate any chance of network-related bottlenecks. In reality, that assumption didn’t hold up.</p>
<p>Engaging early with Azure was essential, as they provided deeper visibility into the underlying infrastructure and worked with us to tune low-level, performance-critical settings. Combined with thorough load and scale testing and robust observability using tools like Elastic Observability, this collaboration helped us detect and rectify the issue early in order to deliver a consistent, reliable, and high-performing experience for our users.</p>
]]></content:encoded>
            <category>observability-labs</category>
            <enclosure url="https://www.elastic.co/observability-labs/assets/images/debugging-aks-packet-loss/debugging-aks-packet-loss.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Serverless log analytics powered by Elasticsearch, in a new low priced tier]]></title>
            <link>https://www.elastic.co/observability-labs/blog/log-analytics-elastic-serverless-logs-essentials</link>
            <guid isPermaLink="false">log-analytics-elastic-serverless-logs-essentials</guid>
            <pubDate>Thu, 07 Aug 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Elastic Observability Logs Essentials delivers cost-effective, hassle-free log analytics on Elastic Cloud Serverless. SREs can ingest, search, enrich, analyze, store, and act on logs without the operational overhead of managing the deployment.]]></description>
            <content:encoded><![CDATA[<p>We're thrilled to introduce Elastic Observability Logs Essentials (Logs Essentials), a new tier in Elastic Cloud Serverless (SaaS). Built on the same robust stateless architecture as Elastic Observability Complete, it’s designed for Site Reliability Engineers (SREs) and developers seeking powerful, efficient, and economical log analytics, without the overhead of managing the Elastic Stack. As the leader in log management, Elasticsearch powers this new tier with unmatched search and analytics. </p>
<p>Logs Essentials is ideal for teams that want Elastic’s speed and scale without paying for premium features or managing the Elastic Stack. With Elastic Cloud Serverless, there’s no infrastructure to manage, and pricing is simple and predictable, making it easy to get started, stay supported, and focus on solving problems faster.</p>
<h2>Unmatched value for log analytics</h2>
<p>Logs Essentials empowers SREs and developers with analytics capabilities designed to help them quickly pinpoint the root cause of issues. </p>
<ul>
<li>
<p>Accelerate root cause analysis with fast, precise log search using filters, pattern matching, and event identification in seconds.</p>
</li>
<li>
<p>Gain deep contextual insights through ES|QL, Elastic’s powerful piped query language that supports structured exploration and joins across indices.</p>
</li>
<li>
<p>Detect issues proactively by setting alerts for error spikes or unusual log volumes, enabling timely incident response.</p>
</li>
<li>
<p>Visualize and monitor operational health with rich dashboards built in Kibana, giving teams a clear and actionable view of system behavior.</p>
</li>
</ul>
<p>Once on Logs Essentials, if you need SLOs, AI/ML, AI Assistant, or other advanced features to analyze logs, you should upgrade to <a href="https://www.elastic.co/pricing/serverless-observability">Observability Complete</a>. Additionally, if you are also interested in expanding to traces and metrics, you should upgrade to Observability Complete.</p>
<h2>SaaS making it simple</h2>
<p>SREs don’t have to worry about managing the powerful Elastic Stack with Logs Essentials. <a href="https://www.elastic.co/blog/journey-to-build-elastic-cloud-serverless">Elastic Cloud Serverless </a>automatically scales and adjusts to needs seamlessly without impacting performance, all while keeping costs low. SREs don’t have to worry about the operational overhead of managing your deployment or being an Elastic Stack expert. SREs get the following benefits:</p>
<p><strong>No infrastructure to manage or scale:</strong> Elastic Cloud Serverless transitions from traditional stateful deployments to a fully stateless, autoscaling architecture, offloading storage to cloud-native object stores and orchestrating compute through Kubernetes. SRE teams can now focus solely on logs and insights, not capacity planning or cluster sizing.</p>
<p><strong>High reliability, resilience, and automation built-in:</strong> Elastic’s Cloud Serverless features multi-region deployments, automated control-plane and data-plane upgrades, automatic configuration updates, canary deployments, and capacity pool management to ensure always-on observability</p>
<p>These capabilities deliver what SREs need: a hassle-free, scale-as-you-go, high-availability logging solution that empowers SREs to focus entirely on operational insights, not infrastructure.</p>
<h2>Affordable log analytics</h2>
<p>Logs Essentials offers a cost-effective and predictable path to log analytics. Elastic Cloud Serverless employs advanced autoscaling controllers that adjust compute and storage dynamically, enabling a flexible pricing model that charges based on real usage (ingest and retention), enabling SREs to “sign up and use,” without upfront provisioning or surprise costs. </p>
<p>Instead of paying for idle capacity or managing infrastructure costs, users are billed based on ingest, and retention, eliminating the guesswork and overprovisioning common in traditional observability solutions. SREs can simply sign up and start analyzing logs. No infrastructure to manage, no surprise costs, just transparent, cost-effective pricing for what they use.</p>
<h2>Logs Essentials in action</h2>
<p>Let’s walk through how a Site Reliability Engineer (SRE) would use it in a real-world scenario. Customers are unable to complete transactions on an ecommerce site and the root cause isn’t clear. The issue could be in the front end, the back end, the database, or even the load balancer. Fortunately, logs are being collected from multiple components including NGINX, MySQL, and the application itself. With Elastic Observability Logs Essentials, an SRE can quickly dive into these logs to investigate the issue by starting with high-level symptoms and drill down across services using powerful search, correlation via ES|QL, and visualization tools like dashboarding.</p>
<p>The investigation continues as the SRE walks through several steps using ES|QL, search, and dashboards.</p>
<ul>
<li>There is an alert indicating a logs spike, which is triggered by a significant number of MySQL errors indicating that a database table “orders” is full. We also use ES|QL to understand how many errors have been seen in the last three hours. </li>
</ul>
<p><img src="https://www.elastic.co/observability-labs/assets/images/log-analytics-elastic-serverless-logs-essentials/logs-essentials-alerts.jpg" alt="Alerts" /></p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/log-analytics-elastic-serverless-logs-essentials/logs-essentials-sql-error.jpg" alt="MySQL error" /></p>
<ul>
<li>Next, the SRE tries to understand the impact on customers and potential revenue by looking at how many http issues are occurring and what region is seeing it most. With a significant number of &gt;=400 and the US as the main region seeing the issue, this is revenue impacting.</li>
</ul>
<p><img src="https://www.elastic.co/observability-labs/assets/images/log-analytics-elastic-serverless-logs-essentials/logs-essentials-nginx-400.jpg" alt="NGINX 400 Issues" /></p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/log-analytics-elastic-serverless-logs-essentials/logs-essentials-geo.jpg" alt="GEO Analysis" /></p>
<ul>
<li>Next, the SRE looks at whether infrastructure is being impacted by finding the related Kubernetes cluster and pod. With this the SRE can further investigate whether the MySQL pod or the Kubernetes node is having CPU or memory utilization issues.</li>
</ul>
<p><img src="https://www.elastic.co/observability-labs/assets/images/log-analytics-elastic-serverless-logs-essentials/logs-essentials-k8s.jpg" alt="K8S Cluster Analysis" /></p>
<p>SREs can also create visualizations and dashboards easily through Observability Logs Essentials’ ES|QL, discover, alerting, and dashboards capabilities.</p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/log-analytics-elastic-serverless-logs-essentials/logs-essentials-dashboard.jpg" alt="Dashboard" /></p>
<h2>Get started with Observability Logs Essentials</h2>
<p>By combining the trusted capabilities of Elasticsearch with the flexibility and scalability of Elastic Cloud Serverless offering, Log Essentials delivers a streamlined, cost-effective solution that helps teams resolve incidents faster and with greater clarity. Whether you're troubleshooting critical outages, monitoring service health, or building dashboards for proactive insight, Logs Essentials gives you the tools you need —  search, ES|QL, alerting, and visualization — in a package that’s simple to adopt and scale. </p>
<p>In order to get started, first <a href="https://cloud.elastic.co/serverless-registration">register on Elastic Cloud</a> and start a trial.</p>
]]></content:encoded>
            <category>observability-labs</category>
            <enclosure url="https://www.elastic.co/observability-labs/assets/images/log-analytics-elastic-serverless-logs-essentials/logs-essentials.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Ship Prometheus Metrics to Elasticsearch with Remote Write]]></title>
            <link>https://www.elastic.co/observability-labs/blog/prometheus-remote-write-elasticsearch</link>
            <guid isPermaLink="false">prometheus-remote-write-elasticsearch</guid>
            <pubDate>Tue, 14 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Elasticsearch natively supports Prometheus Remote Write. Add a single remote_write block to your Prometheus config and use Elasticsearch as Prometheus-compatible long-term storage.]]></description>
            <content:encoded><![CDATA[<p>Prometheus has a well-defined protocol for shipping metrics to external storage: <a href="https://prometheus.io/docs/specs/prw/remote_write_spec/">Remote Write</a>.
Elasticsearch now implements this protocol natively, so you can add it as a <code>remote_write</code> destination with a single config block.</p>
<p>This lets you bring your Prometheus metrics into the same cluster which can also store logs, traces, and other data.
One storage backend, one set of access controls, one place to query.</p>
<h2>Why store Prometheus metrics in Elasticsearch?</h2>
<p>Prometheus local storage is designed for short retention, typically 15 to 30 days.
For anything beyond that, you need a remote storage backend.</p>
<p>Elasticsearch's time series data streams (TSDS) are built for highly efficient long term metrics storage: automatic rollover, time-based partitioning, compression via index sorting, and downsampling to reduce storage costs as data ages.
Your Prometheus scrape configs stay the same.</p>
<p>Recent Elasticsearch releases have significantly reduced the storage footprint for metrics.
A dedicated post with the numbers is coming soon.</p>
<p>On the query side, ES|QL embraces PromQL: a built-in <code>PROMQL</code> function lets your existing queries run unchanged, while the rest of ES|QL is available when you want joins, aggregations, or transformations that span multiple datasets.</p>
<p>And because metrics land in the same store as your logs, traces, and profiling data, correlating signals across types becomes a single query rather than a cross-system investigation.</p>
<h2>How it works</h2>
<p>For a detailed look at what happens inside Elasticsearch when a Remote Write request arrives — protobuf parsing, metric type inference, TSDS mapping, and data stream routing — see <a href="https://www.elastic.co/observability-labs/blog/prometheus-remote-write-elasticsearch-architecture">How Prometheus Remote Write Ingestion Works in Elasticsearch</a>.</p>
<p>Prometheus sends metrics to Elasticsearch via the standard Remote Write protocol (v1).
The endpoint accepts protobuf-encoded, snappy-compressed <code>WriteRequest</code> payloads.</p>
<p>Each sample becomes an Elasticsearch document in a pre-defined time series data stream.
Prometheus labels become TSDS dimensions.
The metric value is stored in a typed field under <code>metrics.&lt;metric_name&gt;</code>.</p>
<p>Elasticsearch infers the metric type (counter vs gauge) from naming conventions.
Names ending in <code>_total</code>, <code>_sum</code>, <code>_count</code>, or <code>_bucket</code> are treated as counters.
Everything else is treated as a gauge.</p>
<h2>Setting it up</h2>
<h3>Step 1: Get an Elasticsearch endpoint</h3>
<p>You need an Elasticsearch cluster with the Prometheus endpoints enabled.
The simplest option is Elastic Cloud Serverless, where this works out of the box.</p>
<p>For serverless: sign in to <a href="https://cloud.elastic.co">cloud.elastic.co</a>, create an Observability project, and copy the Elasticsearch endpoint from the project settings page.
The endpoint looks like <code>https://&lt;project-id&gt;.es.&lt;region&gt;.&lt;provider&gt;.elastic.cloud</code>.</p>
<h3>Step 2: Create an API key</h3>
<p>Create an API key scoped to writing metrics data streams only.
In your Elastic Cloud Serverless project, go to <strong>Admin and settings</strong> (the gear icon at the bottom left of the side nav), then <strong>API keys</strong>.</p>
<p>Use the following role descriptor in the <strong>Control security privileges</strong> section:</p>
<pre><code class="language-json">{
  &quot;ingest&quot;: {
    &quot;indices&quot;: [
      {
        &quot;names&quot;: [&quot;metrics-*&quot;],
        &quot;privileges&quot;: [&quot;auto_configure&quot;, &quot;create_doc&quot;]
      }
    ]
  }
}
</code></pre>
<p>Copy the key value before closing the dialog.
You will not be able to retrieve it again.</p>
<h3>Step 3: Configure Prometheus</h3>
<p>Add the following <code>remote_write</code> block to your <code>prometheus.yml</code>:</p>
<pre><code class="language-yaml">remote_write:
  - url: &quot;https://YOUR_ES_ENDPOINT/_prometheus/api/v1/write&quot;
    authorization:
      type: ApiKey
      credentials: YOUR_API_KEY
</code></pre>
<p>That's it.
Prometheus will start shipping metrics to Elasticsearch on the next scrape interval.</p>
<p>If you use <a href="https://grafana.com/docs/alloy/latest/">Grafana Alloy</a> instead of Prometheus, the equivalent configuration is:</p>
<pre><code>prometheus.remote_write &quot;elasticsearch&quot; {
  endpoint {
    url = &quot;https://YOUR_ES_ENDPOINT/_prometheus/api/v1/write&quot;
    headers = {&quot;Authorization&quot; = &quot;ApiKey YOUR_API_KEY&quot;}
  }
}
</code></pre>
<h2>Routing metrics to separate data streams</h2>
<p>By default, all metrics land in <code>metrics-generic.prometheus-default</code>.
You can route metrics from different environments or teams into separate data streams using the dataset and namespace path segments in the URL.</p>
<p>The three URL patterns are:</p>
<ul>
<li><code>/_prometheus/api/v1/write</code> routes to <code>metrics-generic.prometheus-default</code></li>
<li><code>/_prometheus/metrics/{dataset}/api/v1/write</code> routes to <code>metrics-{dataset}.prometheus-default</code></li>
<li><code>/_prometheus/metrics/{dataset}/{namespace}/api/v1/write</code> routes to <code>metrics-{dataset}.prometheus-{namespace}</code></li>
</ul>
<p>For example, using <code>/_prometheus/metrics/infrastructure/production/api/v1/write</code> routes data to <code>metrics-infrastructure.prometheus-production</code>.</p>
<p>This is useful for separating production from staging metrics, or giving different teams their own data streams with independent lifecycle policies.</p>
<h2>What gets stored</h2>
<p>Here is what a sample document looks like in Elasticsearch:</p>
<pre><code class="language-json">{
  &quot;@timestamp&quot;: &quot;2026-04-02T10:30:00.000Z&quot;,
  &quot;data_stream&quot;: {
    &quot;type&quot;: &quot;metrics&quot;,
    &quot;dataset&quot;: &quot;generic.prometheus&quot;,
    &quot;namespace&quot;: &quot;default&quot;
  },
  &quot;labels&quot;: {
    &quot;__name__&quot;: &quot;prometheus_http_requests_total&quot;,
    &quot;handler&quot;: &quot;/api/v1/query&quot;,
    &quot;code&quot;: &quot;200&quot;,
    &quot;instance&quot;: &quot;localhost:9090&quot;,
    &quot;job&quot;: &quot;prometheus&quot;
  },
  &quot;metrics&quot;: {
    &quot;prometheus_http_requests_total&quot;: 42
  }
}
</code></pre>
<p>Labels map to keyword fields that serve as TSDS <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/time-series-data-stream-tsds#time-series-dimension">dimensions</a>.
The metric value is stored under <code>metrics.&lt;metric_name&gt;</code> with the inferred <code>time_series_metric</code> type (counter or gauge).</p>
<p>Elasticsearch installs a built-in index template matching <code>metrics-*.prometheus-*</code> that configures TSDS mode, <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/passthrough">passthrough</a> dimension container objects, and a 10,000 field limit.
The <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/mapping-limit">field limit</a> is configurable via a custom component template (see the custom metric type inference section below for how to use one).
You do not need to create any templates or mappings yourself.</p>
<h2>Custom metric type inference</h2>
<p>Metric type inference is based on naming conventions.
Metrics that don't follow Prometheus naming best practices may be classified incorrectly.
You can override the defaults by creating a <code>metrics-prometheus@custom</code> component template with your own dynamic templates.
For example, to mark all <code>*_counter</code> metrics as counters:</p>
<pre><code class="language-json">{
  &quot;template&quot;: {
    &quot;mappings&quot;: {
      &quot;dynamic_templates&quot;: [
        {
          &quot;counter&quot;: {
            &quot;path_match&quot;: &quot;metrics.*_counter&quot;,
            &quot;mapping&quot;: {
              &quot;type&quot;: &quot;double&quot;,
              &quot;time_series_metric&quot;: &quot;counter&quot;
            }
          }
        }
      ]
    }
  }
}
</code></pre>
<p>Custom rules are merged with the built-in patterns, so the defaults still apply for metrics you don't override.</p>
<h2>Current limitations</h2>
<p>Only Remote Write v1 is supported.
v2, which brings native histograms and exemplars, is planned.</p>
<p>Staleness markers (special NaN values Prometheus uses to signal a series has disappeared) are not yet stored or respected in queries.</p>
<p>Non-finite values (NaN, Infinity) are silently dropped.</p>
<h2>Get started</h2>
<p>The Prometheus Remote Write endpoint is available now on <a href="https://cloud.elastic.co/serverless-registration?onboarding_token=observability">Elasticsearch Serverless</a> with no configuration needed.
To get started with a local cluster, <a href="https://www.elastic.co/docs/deploy-manage/deploy/self-managed/local-development-installation-quickstart">start-local</a> gets you a single-node cluster in minutes.</p>
<p>Once metrics are flowing, you can query them with ES|QL using the built-in <code>PROMQL</code> function for PromQL compatibility, or write native ES|QL queries to join metrics with logs and traces in the same store.</p>
]]></content:encoded>
            <category>observability-labs</category>
            <enclosure url="https://www.elastic.co/observability-labs/assets/images/prometheus-remote-write-elasticsearch/header.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Use Elasticsearch as a Drop-In Prometheus Backend for Grafana]]></title>
            <link>https://www.elastic.co/observability-labs/blog/query-prometheus-metrics-grafana-elasticsearch</link>
            <guid isPermaLink="false">query-prometheus-metrics-grafana-elasticsearch</guid>
            <pubDate>Thu, 25 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Use Elasticsearch as a Prometheus backend for Grafana dashboards, autocomplete, Metrics Drilldown, and alerting without changing PromQL workflows.]]></description>
            <content:encoded><![CDATA[<p>Elasticsearch is already one of the most popular plugins in the Grafana ecosystem, and we have now made it much more powerful for metrics usage.
If you run Prometheus today and use Grafana to visualize your metrics, you can now point Grafana's Prometheus data source directly at Elasticsearch.
No sidecars, no adapters, no pipeline changes required.</p>
<p>Elasticsearch now implements a native Prometheus-compatible API layer, which covers <a href="https://www.elastic.co/observability-labs/blog/prometheus-remote-write-elasticsearch">ingestion via Remote Write</a> and <a href="https://www.elastic.co/observability-labs/blog/elasticsearch-supports-promql">querying via PromQL</a>.
This post shows the Grafana setup end to end.
Companion posts also cover <a href="https://www.elastic.co/observability-labs/blog/promql-queries-run-in-kibana">PromQL in Kibana</a> and the <a href="https://www.elastic.co/observability-labs/blog/prometheus-remote-write-elasticsearch-architecture">Remote Write architecture</a>.</p>
<h2>Why use Elasticsearch as a Prometheus backend?</h2>
<p>Over the last year, Elasticsearch has become a state-of-the-art metrics store: <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/time-series-data-stream-tsds">time series data streams</a>, ES|QL's <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/ts"><code>TS</code> command</a>, and storage and query optimizations that deliver strong <a href="https://www.elastic.co/search-labs/blog/elasticsearch-columnar-metrics-engine-30x-faster-prometheus">metrics performance for Prometheus-style workloads</a>.
The Prometheus-compatible API layer makes that engine reachable through the tools your team already uses.</p>
<p>Many teams have invested heavily in Prometheus-based tooling: dashboards, runbooks that reference PromQL queries, on-call workflows built around Grafana panels.
Elasticsearch's Prometheus-compatible endpoints let you move metrics storage while keeping those Grafana workflows.</p>
<p>This is particularly relevant if you already use Elasticsearch for logs or traces and want to consolidate your observability data into a single platform, while keeping your Grafana-based workflows intact.</p>
<h2>What the Elasticsearch Prometheus API includes</h2>
<p>The Elasticsearch Prometheus API exposes three endpoint groups.</p>
<h3>Query APIs</h3>
<p>The core query endpoints allow Grafana to evaluate PromQL expressions against data stored in Elasticsearch:</p>
<ul>
<li><code>GET</code> and <code>POST /_prometheus/api/v1/query_range</code> evaluate a PromQL expression over a time window and return matrix results.
This is what powers most Grafana dashboard panels.</li>
<li><code>GET</code> and <code>POST /_prometheus/api/v1/query</code> evaluate a PromQL expression at a single point in time and return vector results.</li>
</ul>
<p>Both endpoints implement the standard Prometheus response envelope, including result types (vector, matrix, scalar, string), status codes, and error handling.
For <code>POST</code>, send parameters in an <code>application/x-www-form-urlencoded</code> body, matching Prometheus client behavior.</p>
<h3>Metadata APIs</h3>
<p>Grafana's metric explorer, autocomplete, and variable dropdowns rely on metadata endpoints to discover what's available.
Elasticsearch supports:</p>
<ul>
<li><code>GET</code> and <code>POST /_prometheus/api/v1/series</code> return time series matching label selectors.</li>
<li><code>GET</code> and <code>POST /_prometheus/api/v1/labels</code> return all available label names.</li>
<li><code>GET /_prometheus/api/v1/label/{name}/values</code> returns all values for a given label.</li>
<li><code>GET /_prometheus/api/v1/metadata</code> returns type and help text for each metric name.</li>
</ul>
<p>These endpoints power autocomplete and the metric browser in Grafana.
The <code>/metadata</code> endpoint additionally enables Grafana's <a href="https://grafana.com/docs/grafana/latest/explore/explore-metrics/">Metrics Drilldown</a>: an interactive metric explorer that displays all available metrics as a grid of live sparklines and lets you drill into any metric without writing a PromQL query.</p>
<h3>Index pre-filtering</h3>
<p>All query and metadata endpoints accept an optional <code>{index}</code> path segment immediately after <code>/_prometheus/</code>, for example:</p>
<pre><code class="language-http">GET /_prometheus/metrics-prod-*/api/v1/query_range
</code></pre>
<p>This pre-filters the Elasticsearch indices that the PromQL query runs against before any expression evaluation happens.
Scoping queries to the relevant data can reduce query work for dashboards that span large volumes of metrics across different data streams.</p>
<p>You can configure a separate Grafana data source per index pattern to give teams scoped access to their own metrics.</p>
<h3>Remote Write ingestion</h3>
<p>Elasticsearch also implements the <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/tsds-ingest-prometheus-remote-write">Prometheus Remote Write protocol</a>, which lets you ship metrics from Prometheus to Elasticsearch using the standard <code>remote_write</code> configuration.
Adding Elasticsearch as a remote write destination requires a single block in your existing Prometheus config:</p>
<pre><code class="language-yaml">remote_write:
  - url: &quot;&lt;es_endpoint&gt;/_prometheus/api/v1/write&quot;
    authorization:
      type: ApiKey
      credentials: &lt;api_key&gt;
</code></pre>
<p>Metrics are stored in the <code>metrics-generic.prometheus-default</code> data stream by default.
You can route metrics from different Prometheus instances or environments into separate data streams using the dataset and namespace path segments:</p>
<ul>
<li><code>POST /_prometheus/metrics/{dataset}/api/v1/write</code> stores metrics in <code>metrics-{dataset}.prometheus-default</code></li>
<li><code>POST /_prometheus/metrics/{dataset}/{namespace}/api/v1/write</code> stores metrics in <code>metrics-{dataset}.prometheus-{namespace}</code></li>
</ul>
<h2>How to connect Grafana to Elasticsearch</h2>
<h3>Step 1: Create a serverless project</h3>
<p>Sign in to <a href="https://cloud.elastic.co">cloud.elastic.co</a> and create a new <strong>Observability</strong> serverless project.
Once the project is ready, you will land directly in Kibana.
To find the Elasticsearch endpoint, go back to the Elastic Cloud console, open <strong>Manage &gt; Application endpoints, cluster and component IDs</strong>, and click the copy icon next to <strong>Elasticsearch</strong>.
The endpoint looks like:</p>
<pre><code class="language-text">https://&lt;project-id&gt;.es.&lt;region&gt;.&lt;provider&gt;.elastic.cloud
</code></pre>
<p><img src="https://www.elastic.co/observability-labs/assets/images/query-prometheus-metrics-grafana-elasticsearch/elasticsearch-endpoint.png" alt="Elastic Cloud console showing the Application endpoints panel with the Elasticsearch endpoint and copy button" /></p>
<h3>Step 2: Create API keys</h3>
<p>Create two API keys with scoped privileges: one for ingestion, one for querying.
Using separate keys means a leaked Grafana key cannot be used to write data, and a leaked ingest key cannot be used to read it.</p>
<p>In your project, open <strong>Admin and settings</strong> (the ⚙️ icon at the bottom left of the side nav), go to <strong>API keys</strong>, and create the first key.</p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/query-prometheus-metrics-grafana-elasticsearch/create-api-key.png" alt="Create API key dialog showing the name, type, and role descriptor for the ingest key" /></p>
<p><strong>Ingest key</strong> (<code>prometheus-remote-write</code>): restricts access to writing metrics data streams only.
In the <strong>Control security privileges</strong> section, paste the following role descriptor:</p>
<pre><code class="language-json">{
  &quot;ingest&quot;: {
    &quot;indices&quot;: [
      {
        &quot;names&quot;: [&quot;metrics-*&quot;],
        &quot;privileges&quot;: [&quot;auto_configure&quot;, &quot;create_doc&quot;]
      }
    ]
  }
}
</code></pre>
<p>Create a second key for Grafana in the same section.</p>
<p><strong>Query key</strong> (<code>prometheus-grafana</code>): restricts access to reading metrics data streams only.</p>
<pre><code class="language-json">{
  &quot;query&quot;: {
    &quot;indices&quot;: [
      {
        &quot;names&quot;: [&quot;metrics-*&quot;],
        &quot;privileges&quot;: [&quot;read&quot;, &quot;view_index_metadata&quot;]
      }
    ]
  }
}
</code></pre>
<p>Copy both key values before closing. You will not be able to retrieve them again.</p>
<h3>Step 3: Run Prometheus and Grafana</h3>
<p>Create a <code>prometheus.yml</code> that scrapes Prometheus itself and forwards those metrics to Elasticsearch.
Replace <code>&lt;es_endpoint&gt;</code> with the endpoint from Step 1 and <code>&lt;ingest_api_key&gt;</code> with the ingest key from Step 2:</p>
<pre><code class="language-yaml">global:
  scrape_interval: 15s

scrape_configs:
  - job_name: &quot;prometheus&quot;
    static_configs:
      - targets: [&quot;localhost:9090&quot;]

remote_write:
  - url: &quot;&lt;es_endpoint&gt;/_prometheus/api/v1/write&quot;
    authorization:
      type: ApiKey
      credentials: &lt;ingest_api_key&gt;
</code></pre>
<p>Next, create the Grafana provisioning directories:</p>
<pre><code class="language-bash">mkdir -p grafana/provisioning/datasources grafana/provisioning/dashboards
</code></pre>
<p>Then create a Grafana data source configuration that points at the Elasticsearch Prometheus API.
Create <code>grafana/provisioning/datasources/datasource.yml</code>, replacing <code>&lt;es_endpoint&gt;</code> and <code>&lt;query_api_key&gt;</code> with the values from Steps 1 and 2:</p>
<pre><code class="language-yaml">apiVersion: 1

datasources:
  - name: Elasticsearch
    type: prometheus
    access: proxy
    url: &quot;&lt;es_endpoint&gt;/_prometheus&quot;
    uid: elasticsearch-prometheus
    isDefault: true
    jsonData:
      httpHeaderName1: Authorization
    secureJsonData:
      httpHeaderValue1: &quot;ApiKey &lt;query_api_key&gt;&quot;
</code></pre>
<p>This configures a Prometheus-type data source backed by Elasticsearch.
Grafana sends Prometheus queries with <code>POST</code> by default, which Elasticsearch accepts on authenticated HTTPS endpoints such as Serverless.</p>
<p>Create <code>grafana/provisioning/dashboards/dashboards.yml</code> to tell Grafana where to find provisioned dashboards:</p>
<pre><code class="language-yaml">apiVersion: 1

providers:
  - name: default
    type: file
    options:
      path: /var/lib/grafana/dashboards
</code></pre>
<p>Finally, create a <code>docker-compose.yml</code> to start everything:</p>
<pre><code class="language-yaml">services:
  prometheus:
    image: prom/prometheus:latest
    ports:
      - &quot;9090:9090&quot;
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro

  download-dashboard:
    image: curlimages/curl:latest
    user: root
    volumes:
      - dashboards:/dashboards
    command: &gt;
      sh -c 'curl -fsSL https://grafana.com/api/dashboards/3662/revisions/2/download
      | sed &quot;s/\$${DS_THEMIS}/elasticsearch-prometheus/g&quot;
      &gt; /dashboards/prometheus-overview.json'

  grafana:
    image: grafana/grafana:latest
    ports:
      - &quot;3000:3000&quot;
    depends_on:
      download-dashboard:
        condition: service_completed_successfully
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=grafana
    volumes:
      - ./grafana/provisioning:/etc/grafana/provisioning:ro
      - dashboards:/var/lib/grafana/dashboards:ro

volumes:
  dashboards:
</code></pre>
<p>The <code>download-dashboard</code> service fetches the <a href="https://grafana.com/grafana/dashboards/3662-prometheus-2-0-overview/">Prometheus 2.0 Overview</a> dashboard from the Grafana marketplace and patches it to use the Elasticsearch data source.
The <code>sed</code> replaces the dashboard's <code>${DS_THEMIS}</code> data source placeholder with our data source UID.
This is needed because Grafana's provisioning does not resolve these placeholders on its own (<a href="https://github.com/grafana/grafana/issues/10786">grafana#10786</a>).
Grafana waits for the download to finish before starting.</p>
<p>Start both with:</p>
<pre><code class="language-bash">docker compose up -d
</code></pre>
<p>Prometheus will start scraping its own metrics and shipping them to Elasticsearch every 15 seconds.
Give it one or two scrape intervals before opening the dashboard.</p>
<h3>Step 4: Open the dashboard</h3>
<p>Open Grafana at <code>http://localhost:3000</code> and log in with <code>admin</code> / <code>grafana</code>.
Go to <strong>Dashboards</strong> and open <strong>Prometheus 2.0 Overview</strong>.</p>
<p>The dashboard shows your Prometheus self-monitoring metrics, pulled from Elasticsearch via PromQL queries.</p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/query-prometheus-metrics-grafana-elasticsearch/grafana-dashboard.png" alt="Grafana dashboard with Elasticsearch as the Prometheus data source, showing Prometheus self-monitoring metrics rendered by PromQL queries" /></p>
<h3>Step 5: Explore metrics with Grafana's Metrics Drilldown</h3>
<p>Because Elasticsearch implements the Prometheus metadata and discovery endpoints, Grafana's <a href="https://grafana.com/docs/grafana/latest/explore/explore-metrics/">Metrics Drilldown</a> works out of the box.</p>
<p>In Grafana, go to <strong>Drilldown &gt; Metrics</strong> in the left-hand navigation and select <strong>Elasticsearch</strong> as the data source.
Grafana loads all available metrics from Elasticsearch and displays them as a grid of live sparklines.
From there you can filter by label, search by name, and drill into any metric without writing PromQL.</p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/query-prometheus-metrics-grafana-elasticsearch/grafana-drilldown.png" alt="Grafana Metrics Drilldown showing all Prometheus metrics from Elasticsearch as a grid of sparklines" /></p>
<h2>Current limitations and what's next</h2>
<p>This is the first implementation and updates should be expected.
All of the following are actively being worked on:</p>
<h3>PromQL coverage is not yet complete</h3>
<p>Queries using group modifiers (for example, <code>on(instance, job)</code>), set operators (<code>or</code>, <code>and</code>, <code>unless</code>), and certain functions like <code>topk</code> are not yet supported.</p>
<h3>Form-encoded POST has deployment requirements</h3>
<p><code>POST</code> requests with <code>application/x-www-form-urlencoded</code> bodies require security enabled, TLS on the Elasticsearch HTTP interface, and an authenticated request.
Serverless meets these requirements out of the box.
If TLS terminates before Elasticsearch and the node sees plain HTTP, use <code>GET</code> with query-string parameters instead.</p>
<h3>Only Remote Write v1 is supported</h3>
<p>Remote Write v2 support is planned.</p>
<h3>Instant queries are not point-in-time yet</h3>
<p>The instant query endpoint currently runs a short range query under the hood and returns the last sample.
It will be replaced with a proper point-in-time evaluation.</p>
<p>Coming next: broader PromQL function and operator coverage, Remote Write v2, and exemplar endpoints.</p>
<h2>Frequently asked questions</h2>
<p><strong>Can Grafana query Prometheus metrics stored in Elasticsearch?</strong>
Yes.
Grafana can use Elasticsearch as a Prometheus data source when the URL points to <code>/_prometheus</code>.
Queries use PromQL and return the standard Prometheus response format for Grafana dashboards, variables, Metrics Drilldown, and alerting.</p>
<p><strong>Do I need to change Prometheus or Grafana dashboards to use Elasticsearch?</strong>
You do not need to rewrite PromQL queries or dashboard panels for common Grafana use cases.
Configure Prometheus Remote Write to send metrics to Elasticsearch, then point Grafana's Prometheus data source at the Elasticsearch <code>/_prometheus</code> endpoint.</p>
<p><strong>Why use Elasticsearch instead of a separate Prometheus long term storage backend?</strong>
Using Elasticsearch as a Prometheus backend lets you store metrics with logs and traces under the same access controls and retention model.
Recent work on the Elasticsearch metrics engine also delivers strong performance for Prometheus-style workloads.
For the benchmark details, see the <a href="https://www.elastic.co/search-labs/blog/elasticsearch-columnar-metrics-engine-30x-faster-prometheus">Elasticsearch metrics performance post</a>.</p>
<p><strong>What PromQL features are supported in Elasticsearch today?</strong>
Elasticsearch supports common PromQL query patterns used by Grafana dashboards.
Advanced group modifiers, set operators, and <code>topk</code> are not yet supported.</p>
<p><strong>Can I limit Grafana queries to specific Elasticsearch indices?</strong>
Yes.
Add an index pattern after <code>/_prometheus/</code>, such as <code>/_prometheus/metrics-prod-*/api/v1/query_range</code>.
This pre-filters the Elasticsearch indices before PromQL evaluation and can reduce query work for large metrics deployments.</p>
<h2>Prometheus API availability</h2>
<p>The Prometheus-compatible API is available now on <a href="https://cloud.elastic.co/serverless-registration?onboarding_token=observability">Elasticsearch Serverless</a> with no additional configuration.</p>
<p>If you run into issues or have feedback, open an issue on the <a href="https://github.com/elastic/elasticsearch">Elasticsearch repository</a>.</p>
]]></content:encoded>
            <category>observability-labs</category>
            <enclosure url="https://www.elastic.co/observability-labs/assets/images/query-prometheus-metrics-grafana-elasticsearch/header.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Trace your Azure Function application with Elastic Observability]]></title>
            <link>https://www.elastic.co/observability-labs/blog/trace-azure-function-application-observability</link>
            <guid isPermaLink="false">trace-azure-function-application-observability</guid>
            <pubDate>Tue, 16 May 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Serverless applications deployed on Azure Functions are growing in usage. This blog shows how to deploy a serverless application on Azure functions with Elastic Agent and use Elastic's APM capability to manage and troubleshoot issues.]]></description>
            <content:encoded><![CDATA[<p>Adoption of Azure Functions in cloud-native applications on Microsoft Azure has been increasing exponentially over the last few years. Serverless functions, such as the Azure Functions, provide a high level of abstraction from the underlying infrastructure and orchestration, given these tasks are managed by the cloud provider. Software development teams can then focus on the implementation of business and application logic. Some additional benefits include billing for serverless functions based on the actual compute and memory resources consumed, along with automatic on-demand scaling.</p>
<p>While the benefits of using serverless functions are manifold, it is also necessary to make them observable in the wider end-to-end microservices architecture context.</p>
<h2>Elastic Observability (APM) for Azure Functions: The architecture</h2>
<p><a href="https://www.elastic.co/blog/whats-new-elastic-observability-8-7-0">Elastic Observability 8.7</a> introduced distributed tracing for Microsoft Azure Functions — available for the Elastic APM Agents for .NET, Node.js, and Python. Auto-instrumentation of HTTP requests is supported out-of-the-box, enabling the detection of performance bottlenecks and sources of errors.</p>
<p>The key components of the solution for observing Azure Functions are:</p>
<ol>
<li>The Elastic APM Agent for the relevant language</li>
<li>Elastic Observability</li>
</ol>
<p><img src="https://www.elastic.co/observability-labs/assets/images/trace-azure-function-application-observability/blog-elastic-azure-function.png" alt="azure function" /></p>
<p>The APM server validates and processes incoming events from individual APM Agents and transforms them into Elasticsearch documents. The APM Agent provides auto-instrumentation capabilities for the application being observed. The Node.js APM Agent can trace function invocations in an Azure Functions app.</p>
<h2>Setting up Elastic APM for Azure Functions</h2>
<p>To demonstrate the setup and usage of Elastic APM, we will use a <a href="https://github.com/elastic/azure-functions-apm-nodejs-sample-app">sample Node.js application</a>.</p>
<h3>Application overview</h3>
<p>The Node.js application has two <a href="https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-http-webhook">HTTP-triggered</a> functions named &quot;<a href="https://github.com/elastic/azure-functions-apm-nodejs-sample-app/blob/main/Hello/index.js">Hello</a>&quot; and &quot;<a href="https://github.com/elastic/azure-functions-apm-nodejs-sample-app/blob/main/Goodbye/index.js">Goodbye</a>.&quot; Once deployed, they can be called as follows, and tracing data will be sent to the configured Elastic Observability deployment.</p>
<pre><code class="language-bash">curl -i https://&lt;APP_NAME&gt;.azurewebsites.net/api/hello
curl -i https://&lt;APP_NAME&gt;.azurewebsites.net/api/goodbye
</code></pre>
<h3>Setup</h3>
<p><strong>Step 0. Prerequisites</strong></p>
<p>To run the sample application, you will need:</p>
<ul>
<li>
<p>An installation of <a href="https://nodejs.org/">Node.js</a> (v14 or later)</p>
</li>
<li>
<p>Access to an Azure subscription with an appropriate role to create resources</p>
</li>
<li>
<p>The <a href="https://learn.microsoft.com/en-us/cli/azure/install-azure-cli">Azure CLI (az)</a> logged into an Azure subscription</p>
<ol>
<li>Use az login to login</li>
<li>See the output of az account show</li>
</ol>
</li>
<li>
<p>The <a href="https://learn.microsoft.com/en-us/azure/azure-functions/functions-run-local?tabs=v4%2Cwindows%2Ccsharp%2Cportal%2Cbash#install-the-azure-functions-core-tools">Azure Functions Core Tools (func)</a> (func --version should show a 4.x version)</p>
</li>
<li>
<p>An Elastic Observability deployment to which monitoring data will be sent</p>
<ol>
<li>The simplest way to get started with Elastic APM Microsoft Azure is through Elastic Cloud. <a href="https://www.elastic.co/guide/en/elastic-stack-deploy/current/azure-marketplace-getting-started.html">Get started with Elastic Cloud on Azure Marketplace</a> or <a href="https://www.elastic.co/cloud/elasticsearch-service/signup">sign up for a trial on Elastic Cloud</a>.</li>
</ol>
</li>
<li>
<p>The APM server URL (serverUrl) and secret token (secretToken) from your Elastic stack deployment for configuration below</p>
<ol>
<li><a href="https://www.elastic.co/guide/en/apm/guide/8.7/install-and-run.html">How to get the serverUrl and secretToken documentation</a></li>
</ol>
</li>
</ul>
<p><strong>Step 1. Clone the sample application repo and install dependencies</strong></p>
<pre><code class="language-bash">git clone https://github.com/elastic/azure-functions-apm-nodejs-sample-app.git
cd azure-functions-apm-nodejs-sample-app
npm install
</code></pre>
<p><strong>Step 2. Deploy the Azure Function App</strong><br />
Caution icon! Deploying a function app to Azure can incur <a href="https://azure.microsoft.com/en-us/pricing/details/functions/">costs</a>. The following setup uses the free tier of Azure Functions. Step 5 covers the clean-up of resources.</p>
<p><strong>Step 2.1</strong><br />
To avoid name collisions with others that have independently run this demo, we need a short unique identifier for some resource names that need to be globally unique. We'll call it the DEMO_ID. You can run the following to generate one and save it to DEMO_ID and the &quot;demo-id&quot; file.</p>
<pre><code class="language-bash">if [[ ! -f demo-id ]]; then node -e 'console.log(crypto.randomBytes(3).toString(&quot;hex&quot;))' &gt;demo-id; fi
export DEMO_ID=$(cat demo-id)
echo $DEMO_ID
</code></pre>
<p><strong>Step 2.2</strong><br />
Before you can deploy to Azure, you will need to create some Azure resources: a Resource Group, Storage Account, and the Function App. For this demo, you can use the following commands. (See <a href="https://learn.microsoft.com/en-us/azure/azure-functions/create-first-function-cli-node#create-supporting-azure-resources-for-your-function">this Azure docs section</a> for more details.)</p>
<pre><code class="language-bash">REGION=westus2   # Or use another region listed in 'az account list-locations'.
az group create --name &quot;AzureFnElasticApmNodeSample-rg&quot; --location &quot;$REGION&quot;
az storage account create --name &quot;eapmdemostor${DEMO_ID}&quot; --location &quot;$REGION&quot; \
    --resource-group &quot;AzureFnElasticApmNodeSample-rg&quot; --sku Standard_LRS
az functionapp create --name &quot;azure-functions-apm-nodejs-sample-app-${DEMO_ID}&quot; \
    --resource-group &quot;AzureFnElasticApmNodeSample-rg&quot; \
    --consumption-plan-location &quot;$REGION&quot; --runtime node --runtime-version 18 \
    --functions-version 4 --storage-account &quot;eapmdemostor${DEMO_ID}&quot;
</code></pre>
<p><strong>Step 2.3</strong><br />
Next, configure your Function App with the APM server URL and secret token for your Elastic deployment. This can be done in the <a href="https://portal.azure.com/">Azure Portal</a> or with the az CLI.</p>
<p>In the Azure portal, browse to your Function App, then its Application Settings (<a href="https://learn.microsoft.com/en-us/azure/azure-functions/functions-how-to-use-azure-function-app-settings?tabs=portal#settings">Azure user guide</a>). You'll need to add two settings:</p>
<p>First set your APM URL and token.</p>
<pre><code class="language-bash">export ELASTIC_APM_SERVER_URL=&quot;&lt;your serverUrl&gt;&quot;
export ELASTIC_APM_SECRET_TOKEN=&quot;&lt;your secretToken&gt;&quot;
</code></pre>
<p>Or you can use the az functionapp config appsettings set ... CLI command as follows:</p>
<pre><code class="language-bash">az functionapp config appsettings set \
  -g &quot;AzureFnElasticApmNodeSample-rg&quot; -n &quot;azure-functions-apm-nodejs-sample-app-${DEMO_ID}&quot; \
  --settings &quot;ELASTIC_APM_SERVER_URL=${ELASTIC_APM_SERVER_URL}&quot;
az functionapp config appsettings set \
  -g &quot;AzureFnElasticApmNodeSample-rg&quot; -n &quot;azure-functions-apm-nodejs-sample-app-${DEMO_ID}&quot; \
  --settings &quot;ELASTIC_APM_SECRET_TOKEN=${ELASTIC_APM_SECRET_TOKEN}&quot;
</code></pre>
<p>The ELASTIC_APM_SERVER_URL and ELASTIC_APM_SECRET_TOKEN are set in Azure function’s settings for the app and used by the Elastic APM Agent. This is initiated by the initapm.js file, which starts the Elastic APM agent with:</p>
<pre><code class="language-javascript">require(&quot;elastic-apm-node&quot;).start();
</code></pre>
<p>When you log in to Azure and look at the function’s configuration, you will see them set:</p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/trace-azure-function-application-observability/blog-elastic-azure-functions-application-settings.png" alt="azure functions application settings" /></p>
<p><strong>Step 2.4</strong><br />
Now you can publish your app. (Re-run this command every time you make a code change.)</p>
<pre><code class="language-bash">func azure functionapp publish &quot;azure-functions-apm-nodejs-sample-app-${DEMO_ID}&quot;
</code></pre>
<p>You should log in to Azure to see the function running.</p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/trace-azure-function-application-observability/blog-elastic-azure-function-app.png" alt="azure function app" /></p>
<p><strong>Step 3. Try it out</strong></p>
<pre><code class="language-bash">% curl https://azure-functions-apm-nodejs-sample-app-${DEMO_ID}.azurewebsites.net/api/Hello
{&quot;message&quot;:&quot;Hello.&quot;}
% curl https://azure-functions-apm-nodejs-sample-app-${DEMO_ID}.azurewebsites.net/api/Goodbye
{&quot;message&quot;:&quot;Goodbye.&quot;}
</code></pre>
<p>In a few moments, the APM app in your Elastic deployment will show tracing data for your Azure Function app.</p>
<p><strong>Step 4. Apply some load to your app</strong><br />
To get some more interesting data, you can run the following to generate some load on your deployed function app:</p>
<pre><code class="language-bash">npm run loadgen
</code></pre>
<p>This uses the <a href="https://github.com/mcollina/autocannon">autocannon</a> node package to generate some light load (2 concurrent users, each calling at 5 requests/s for 60s) on the &quot;Goodbye&quot; function.</p>
<p><strong>Step 5. Clean up resources</strong><br />
If you deployed to Azure, you should make sure to delete any resources so you don't incur any costs.</p>
<pre><code class="language-bash">az group delete --name &quot;AzureFnElasticApmNodeSample-rg&quot;
</code></pre>
<h2>Analyzing Azure Function APM data in Elastic</h2>
<p>Once you have successfully set up the sample application and started generating load, you should see APM data appearing in the Elastic Observability APM Services capability.</p>
<h2>Service map</h2>
<p>With the default setup, you will see two services in the APM Service map.</p>
<p>The main function: azure-functions-apm-nodejs-sample-app</p>
<p>And the end point where your function is accessible: azure-functions-apm-nodejs-sample-app-ec7d4c.azurewebsites.net</p>
<p>You will see that there is a connection between the two as your application is taking requests and answering through the endpoint.</p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/trace-azure-function-application-observability/blog-elastic-observability-services.png" alt="observability services" /></p>
<p>From the <a href="https://www.elastic.co/observability/application-performance-monitoring">APM Service</a> map you can further investigate the function, analyze traces, look at logs, and more.</p>
<h3>Service details</h3>
<p>When we dive into the details, we can see several items.</p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/trace-azure-function-application-observability/blog-elastic-observability-azure-functions-apm.png" alt="observability azure functions apm" /></p>
<ul>
<li>Latency for the recent load we ran against the application</li>
<li>Transactions (Goodbye and Hello)</li>
<li>Average throughput</li>
<li>And more</li>
</ul>
<h3>Transaction details</h3>
<p>We can see transaction details.</p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/trace-azure-function-application-observability/blog-elastic-observability-get-api-goodbye.png" alt="observability get api goodbye" /></p>
<p>An individual trace shows us that the &quot;Goodbye&quot; function <a href="https://github.com/elastic/azure-functions-apm-nodejs-sample-app/blob/main/Goodbye/index.js#L6-L10">calls the &quot;Hello&quot; function</a> in the same function app before returning:</p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/trace-azure-function-application-observability/blog-elastic-latency-distribution-trace-sample.png" alt="latency distribution trace sample" /></p>
<h3>Machine learning based latency correlation</h3>
<p>As we’ve mentioned in other blogs, we can also correlate issues such as higher than normal latency. Since we see a spike at 1s, we run the embedded latency correlation, which uses machine learning to help analyze the potential impacting component by analyzing logs, metrics, and traces.</p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/trace-azure-function-application-observability/blog-elastic-latency-distribution-correlations.png" alt="latency distribution correlations" /></p>
<p>The correlation indicated there is a potential cause (25%) due to the host sending the load (my machine).</p>
<h3>Cold start detection</h3>
<p>Also, we can see the impact a <a href="https://azure.microsoft.com/en-ca/blog/understanding-serverless-cold-start/">cold start</a> can have on the latency of a request:</p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/trace-azure-function-application-observability/blog-elastic-trace-sample.png" alt="trace sample" /></p>
<h2>Summary</h2>
<p>Elastic Observability provides real-time monitoring of Azure Functions in your production environment for a broad range of use cases. Curated dashboards assist DevOps teams in performing root cause analysis for performance bottlenecks and errors. SRE teams can quickly view upstream and downstream dependencies, as well as perform analyses in the context of distributed microservices architecture.</p>
<h2>Learn more</h2>
<p>To learn how to add the Elastic APM Agent to an existing Node.js Azure Function app, read <a href="https://www.elastic.co/guide/en/apm/agent/nodejs/master/azure-functions.html">Monitoring Node.js Azure Functions</a>. Additional resources include:</p>
<ul>
<li><a href="https://www.elastic.co/blog/getting-started-with-the-azure-integration-enhancement">How to deploy and manage Elastic Observability on Microsoft Azure</a></li>
<li><a href="https://www.elastic.co/guide/en/apm/guide/current/apm-quick-start.html">Elastic APM Quickstart</a></li>
</ul>
]]></content:encoded>
            <category>observability-labs</category>
            <enclosure url="https://www.elastic.co/observability-labs/assets/images/trace-azure-function-application-observability/09-road.jpeg" length="0" type="image/jpeg"/>
        </item>
    </channel>
</rss>