<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Saket</title>
    <description>The latest articles on DEV Community by Saket (@archcode01).</description>
    <link>https://dev.to/archcode01</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F458131%2F4613eb16-9d13-42d1-8933-0566a8210398.png</url>
      <title>DEV Community: Saket</title>
      <link>https://dev.to/archcode01</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/archcode01"/>
    <language>en</language>
    <item>
      <title>From A10 to M60: An Architect's Journey into Azure GPU VM Sizing for Kubernetes Inference Workloads</title>
      <dc:creator>Saket</dc:creator>
      <pubDate>Wed, 15 Jul 2026 12:45:45 +0000</pubDate>
      <link>https://dev.to/archcode01/from-a10-to-m60-an-architects-journey-into-azure-gpu-vm-sizing-for-kubernetes-inference-workloads-4hjj</link>
      <guid>https://dev.to/archcode01/from-a10-to-m60-an-architects-journey-into-azure-gpu-vm-sizing-for-kubernetes-inference-workloads-4hjj</guid>
      <description>&lt;p&gt;How an unexpected regional constraint forced us to deeply understand Azure GPU VM families, naming conventions, and workload fit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;As architects, we often assume that infrastructure decisions are straightforward:&lt;/p&gt;

&lt;p&gt;"The workload is already running successfully in Region A. Let's deploy the same Kubernetes workload in Region B."&lt;/p&gt;

&lt;p&gt;&lt;u&gt;That's exactly what we thought.&lt;/u&gt;&lt;/p&gt;

&lt;p&gt;Our workload consisted of a &lt;strong&gt;Visual Element Detection (VED)&lt;/strong&gt; service hosted on Kubernetes. The application uses a PyTorch model to analyze images and detect various visual elements in an image file. The service was already running successfully on a node pool backed by Azure's NVads_A10_v5 GPU VMs.&lt;br&gt;
Then we hit an unexpected challenge.&lt;br&gt;
The target region did not offer NVads_A10_v5 instances.&lt;br&gt;
What looked like a simple deployment exercise became a deep dive into Azure GPU virtual machine families, GPU architectures, VM naming conventions, and workload characteristics.&lt;br&gt;
This article shares what I learned in the hope that it helps others who find themselves evaluating Azure GPU SKUs for AI inference workloads. I am relatively new to the world of MLOps, Model deployments, GPU Workloads etc and equally interested and excited to learn more on this front. &lt;/p&gt;


&lt;h3&gt;
  
  
  The Workload
&lt;/h3&gt;

&lt;p&gt;Before discussing VM selection, let's understand the workload characteristics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="err"&gt;Model&lt;/span&gt; &lt;span class="py"&gt;Type&lt;/span&gt;        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PyTorch&lt;/span&gt;
&lt;span class="err"&gt;Model&lt;/span&gt; &lt;span class="py"&gt;Size&lt;/span&gt;        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;less than 200 MB (.pth)&lt;/span&gt;
&lt;span class="err"&gt;Image&lt;/span&gt; &lt;span class="py"&gt;Resolution&lt;/span&gt;  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;~2000 x 2000&lt;/span&gt;
&lt;span class="err"&gt;Expected&lt;/span&gt; &lt;span class="py"&gt;Throughput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5-7 requests/sec&lt;/span&gt;
&lt;span class="py"&gt;Platform&lt;/span&gt;          &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AKS (Kubernetes)&lt;/span&gt;
&lt;span class="err"&gt;Workload&lt;/span&gt; &lt;span class="py"&gt;Type&lt;/span&gt;     &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Inference only&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is important because GPU sizing should always start from the workload and not from the VM catalog.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Understanding Azure GPU VM Families
&lt;/h2&gt;

&lt;p&gt;Many engineers first encounter Azure GPU machines through names like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NV12s_v3
NV6ads_A10_v5
NC4as_T4_v3
ND96isr_H100_v5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The naming can be intimidating.&lt;br&gt;
The first breakthrough was understanding that Azure organizes GPU VMs into three primary families:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;N-Series
├── NV
├── NC
└── ND
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  NV Series – Visualization and Graphics
&lt;/h3&gt;

&lt;p&gt;NV-series VMs are designed primarily for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Virtual Desktop Infrastructure (VDI)&lt;/li&gt;
&lt;li&gt;Graphics rendering&lt;/li&gt;
&lt;li&gt;CAD/CAM workloads&lt;/li&gt;
&lt;li&gt;Remote workstations&lt;/li&gt;
&lt;li&gt;Visualization workloads&lt;/li&gt;
&lt;li&gt;Image processing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examples:&lt;br&gt;
NV12s_v32&lt;br&gt;
NV24s_v33&lt;br&gt;
NV6ads_A10_v54&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Typical GPUs:&lt;/strong&gt;&lt;br&gt;
NVIDIA Tesla M60&lt;br&gt;
NVIDIA A10&lt;br&gt;
AMD MI25&lt;/p&gt;

&lt;p&gt;Historically, these SKUs were optimized for graphics workloads, although many organizations now use them for lightweight AI inference workloads.&lt;/p&gt;
&lt;h3&gt;
  
  
  NC Series – Compute
&lt;/h3&gt;

&lt;p&gt;NC-series VMs are optimized for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI/ML workloads&lt;/li&gt;
&lt;li&gt;PyTorch&lt;/li&gt;
&lt;li&gt;TensorFlow&lt;/li&gt;
&lt;li&gt;CUDA applications&lt;/li&gt;
&lt;li&gt;HPC workloads&lt;/li&gt;
&lt;li&gt;Inference APIs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examples:&lt;br&gt;
NC4as_T4_v32&lt;br&gt;
NC8as_T4_v33&lt;br&gt;
NC_A100_v44&lt;br&gt;
NCads_H100_v5&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Typical GPUs:&lt;/strong&gt;&lt;br&gt;
T4&lt;br&gt;
V100&lt;br&gt;
A100&lt;br&gt;
H100&lt;/p&gt;

&lt;p&gt;&lt;em&gt;For pure AI workloads, NC is often the most natural fit.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  ND Series – Deep Learning at Scale
&lt;/h3&gt;

&lt;p&gt;ND-series is designed for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multi-GPU training&lt;/li&gt;
&lt;li&gt;Large Language Model training&lt;/li&gt;
&lt;li&gt;Distributed AI&lt;/li&gt;
&lt;li&gt;NVLink-enabled workloads&lt;/li&gt;
&lt;li&gt;InfiniBand-connected GPU clusters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examples:&lt;br&gt;
ND_A100_v42&lt;br&gt;
ND_H100_v5&lt;/p&gt;

&lt;p&gt;If you're training LLMs, ND is your friend.&lt;br&gt;
If you're serving a 200 MB inference model, ND is probably overkill.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 2: Decoding Azure VM Names
&lt;/h2&gt;

&lt;p&gt;One of the biggest learnings from this exercise was understanding Azure's VM naming scheme.&lt;br&gt;
Let's decode the VM we were already using:&lt;br&gt;
&lt;strong&gt;NV6ads_A10_v5&lt;/strong&gt;&lt;br&gt;
Breaking it apart:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;NV&lt;/td&gt;
&lt;td&gt;Visualization family&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;vCPU count&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;a&lt;/td&gt;
&lt;td&gt;AMD processor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;d&lt;/td&gt;
&lt;td&gt;Local temporary disk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;s&lt;/td&gt;
&lt;td&gt;Premium SSD support&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A10&lt;/td&gt;
&lt;td&gt;NVIDIA A10 GPU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v5&lt;/td&gt;
&lt;td&gt;Generation 5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Once you learn the modifiers, every Azure VM becomes easier to understand.&lt;/p&gt;

&lt;p&gt;Common Suffixes You'll Encounter&lt;br&gt;
as - NC4as_T4_v3&lt;br&gt;
ads - NV6ads_A10_v5&lt;br&gt;
adms - NV36adms_A10_v5&lt;br&gt;
These memory-optimized variants generally provide significantly more RAM than their standard counterparts.&lt;/p&gt;

&lt;p&gt;Common Letters we will usually see - &lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Letter&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;a&lt;/td&gt;
&lt;td&gt;AMD CPU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;b&lt;/td&gt;
&lt;td&gt;Higher storage bandwidth&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;d&lt;/td&gt;
&lt;td&gt;Local/temp disk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;e&lt;/td&gt;
&lt;td&gt;Confidential/encrypted capabilities&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;m&lt;/td&gt;
&lt;td&gt;Memory optimized&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;n&lt;/td&gt;
&lt;td&gt;Network optimized&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;p&lt;/td&gt;
&lt;td&gt;ARM processor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;r&lt;/td&gt;
&lt;td&gt;RDMA / InfiniBand&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;s&lt;/td&gt;
&lt;td&gt;Premium SSD support&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  Step 3: Understanding Fractional GPUs
&lt;/h2&gt;

&lt;p&gt;One detail many people miss about NVads_A10_v5 is that you may not receive a full GPU.&lt;br&gt;
For example:&lt;br&gt;
NV6ads_A10_v5 provides: 1/6th of an NVIDIA A10 ≈ 4 GB VRAM&lt;br&gt;
Azure uses GPU partitioning to expose slices of the physical A10 GPU.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A10 GPU (24 GB)
 ├─ VM1 -&amp;gt; 4 GB
 ├─ VM2 -&amp;gt; 4 GB
 ├─ VM3 -&amp;gt; 4 GB
 ├─ VM4 -&amp;gt; 4 GB
 ├─ VM5 -&amp;gt; 4 GB
 └─ VM6 -&amp;gt; 4 GB
``
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates cost-effective GPU options, especially for inference workloads.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Evaluating NVv3 as an Alternative
&lt;/h2&gt;

&lt;p&gt;Because the A10-based SKUs were unavailable in our target region, we began evaluating:&lt;br&gt;
NV12s_v3, NV24s_v3 and NV48s_v3&lt;br&gt;
These machines use the much older - NVIDIA Tesla M60 GPU.&lt;br&gt;
The key specifications:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;SKU&lt;/th&gt;
&lt;th&gt;GPUs&lt;/th&gt;
&lt;th&gt;GPU Memory&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;NV12s_v3&lt;/td&gt;
&lt;td&gt;1 × M60&lt;/td&gt;
&lt;td&gt;16 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NV24s_v3&lt;/td&gt;
&lt;td&gt;2 × M60&lt;/td&gt;
&lt;td&gt;32 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NV48s_v3&lt;/td&gt;
&lt;td&gt;4 × M60&lt;/td&gt;
&lt;td&gt;64 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;At first glance, moving from an A10 to an M60 looked risky.&lt;br&gt;
However, after analyzing the workload, we realized something important:&lt;br&gt;
The model itself was only less than 200MB so memory wasn't the challenge.&lt;br&gt;
The real challenge was 2000 x 2000 image processing which drives compute consumption much more than model size.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Right-Sizing the Kubernetes Node Pool
&lt;/h2&gt;

&lt;p&gt;A common temptation in infrastructure planning is:&lt;/p&gt;

&lt;p&gt;"Let's buy the biggest VM and move on."&lt;/p&gt;

&lt;p&gt;We resisted that temptation.&lt;br&gt;
For our workload:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;5–7 requests/sec&lt;/li&gt;
&lt;li&gt;163 MB model&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Inference only&lt;br&gt;
the most logical starting point became:&lt;br&gt;
NV12s_v3&lt;br&gt;
Why?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;16 GB GPU memory&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sufficient CPU capacity&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lower cost&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Easier horizontal scaling in Kubernetes&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rather than using:&lt;br&gt;
1 × NV48s_v3, we preferred the Kubernetes-native approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple smaller nodes&lt;/li&gt;
&lt;li&gt;Horizontal Pod Autoscaling&lt;/li&gt;
&lt;li&gt;Cluster Autoscaling
This usually provides better resilience and resource efficiency.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Key Takeaways
&lt;/h3&gt;

&lt;p&gt;If you're evaluating Azure GPU machines for inference workloads, remember:&lt;br&gt;
Understand the family first&lt;br&gt;
&lt;strong&gt;NV&lt;/strong&gt; = Visualization&lt;br&gt;
&lt;strong&gt;NC&lt;/strong&gt;= Compute&lt;br&gt;
&lt;strong&gt;ND&lt;/strong&gt;= Deep Learning&lt;br&gt;
Understand the suffixes&lt;br&gt;
&lt;strong&gt;as&lt;/strong&gt;   = AMD + Premium SSD&lt;br&gt;
&lt;strong&gt;ads&lt;/strong&gt;  = AMD + Local Disk + Premium SSD&lt;br&gt;
&lt;strong&gt;adms&lt;/strong&gt; = AMD + Local Disk + Memory Optimized + Premium SSD&lt;br&gt;
Don't focus only on GPU names&lt;br&gt;
A larger GPU doesn't automatically mean you need a larger VM.&lt;br&gt;
Consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Model size&lt;/li&gt;
&lt;li&gt;Input resolution&lt;/li&gt;
&lt;li&gt;Throughput requirements&lt;/li&gt;
&lt;li&gt;Concurrency levels&lt;/li&gt;
&lt;li&gt;Kubernetes scaling strategy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Start with measurement&lt;/strong&gt; using Benchmark:&lt;br&gt;
&lt;em&gt;nvidia-smi&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Monitor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GPU utilization&lt;/li&gt;
&lt;li&gt;Memory usage&lt;/li&gt;
&lt;li&gt;Inference latency&lt;/li&gt;
&lt;li&gt;Requests/sec&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then scale based on evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;What began as a regional availability issue became a valuable learning opportunity.&lt;br&gt;
We started with a simple question:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"What should we use if NVads_A10_v5 isn't available?"&lt;/em&gt;&lt;br&gt;
We ended up gaining a much deeper understanding of:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Azure GPU VM families&lt;/li&gt;
&lt;li&gt;GPU partitioning&lt;/li&gt;
&lt;li&gt;A10 vs M60 trade-offs&lt;/li&gt;
&lt;li&gt;AKS node pool sizing&lt;/li&gt;
&lt;li&gt;Azure VM naming conventions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As architects, these are the moments that help us move beyond simply selecting SKUs and toward making informed infrastructure decisions based on workload characteristics.&lt;br&gt;
Sometimes the best architecture lessons come from constraints rather than from choice.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Have you had to migrate inference workloads between Azure regions and deal with GPU availability differences? I'd love to hear what SKUs and strategies worked for your teams.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>azure</category>
      <category>kubernetes</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Observability in Agentic AI: What I Learned After Instrumenting a Real LLM Agent with OpenTelemetry</title>
      <dc:creator>Saket</dc:creator>
      <pubDate>Sun, 28 Jun 2026 15:32:56 +0000</pubDate>
      <link>https://dev.to/archcode01/observability-in-agentic-ai-what-i-learned-after-instrumenting-a-real-llm-agent-with-opentelemetry-4h1</link>
      <guid>https://dev.to/archcode01/observability-in-agentic-ai-what-i-learned-after-instrumenting-a-real-llm-agent-with-opentelemetry-4h1</guid>
      <description>&lt;p&gt;&lt;em&gt;A hands-on walkthrough for AI architects who want visibility into tools, API calls, MCP servers, and model interactions—not just “did the API return 200?”&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;If you ship traditional microservices, observability is a solved problem in principle: traces show request paths, metrics expose latency and error rates, and logs give you the narrative. Agentic AI breaks that mental model.&lt;/p&gt;

&lt;p&gt;The same user question can produce different outputs. A single “chat turn” might trigger guardrail checks, session reads, multiple LLM calls, web search, custom tool execution, and follow-up reasoning loops. Failures are often subtle—slow tools, ballooning context windows, guardrails firing too often, or models that degrade under load rather than returning hard errors.&lt;/p&gt;

&lt;p&gt;I recently worked through the &lt;a href="https://signoz.io/blog/opentelemetry-llm/" rel="noopener noreferrer"&gt;SigNoz guide on observing LLM applications with OpenTelemetry&lt;/a&gt; and ran the &lt;a href="https://github.com/SigNoz/examples/tree/main/python/opentelemetry-llm-demo" rel="noopener noreferrer"&gt;OpenTelemetry NBA Agent demo&lt;/a&gt; end to end. This post is my architect’s take on what that exercise taught me—and how observability needs to span the &lt;strong&gt;entire agentic ecosystem&lt;/strong&gt;, not just the model call at the center.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Agentic AI Demands a Different Observability Mindset
&lt;/h2&gt;

&lt;p&gt;Three ideas from the SigNoz article stuck with me after running the demo myself:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Non-determinism is the default
&lt;/h3&gt;

&lt;p&gt;The same prompt can yield different answers across runs. That makes regression testing harder and makes &lt;strong&gt;distributed traces&lt;/strong&gt; more valuable than unit tests alone. You need to see &lt;em&gt;what path&lt;/em&gt; the agent took, not just &lt;em&gt;what text&lt;/em&gt; came back.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. “Correct” is contextual
&lt;/h3&gt;

&lt;p&gt;A one-word answer might be fine for “Will it rain tomorrow?” and unacceptable for “Should I rebalance my portfolio?” Observability helps you correlate &lt;strong&gt;guardrail decisions&lt;/strong&gt;, &lt;strong&gt;tool usage&lt;/strong&gt;, and &lt;strong&gt;response structure&lt;/strong&gt; with user intent and topic.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The stack moves fast
&lt;/h3&gt;

&lt;p&gt;Model updates, provider brownouts, new APIs (Responses vs Chat Completions), and evolving OpenTelemetry GenAI conventions all change behavior under you. You need baseline metrics before you can tell whether a deploy or a model swap made things better or worse.&lt;/p&gt;

&lt;p&gt;OpenTelemetry gives you a vendor-neutral way to capture that picture. SigNoz (or any OTLP-compatible backend) is where you visualize it. The demo proves the plumbing works; production agentic systems need you to extend that plumbing across every integration point.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Built (and Ran)
&lt;/h2&gt;

&lt;p&gt;The demo is intentionally small—a FastAPI app with one agent, one custom tool, one guardrail, and session-backed conversations. That restraint is a feature. It produces &lt;strong&gt;meaningfully agent-shaped telemetry&lt;/strong&gt; without hiding the signal in product complexity.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;FastAPI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;POST /agent/turn&lt;/code&gt; — one conversational turn per request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OpenAI Agents SDK&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Orchestrates the agent loop, tools, and guardrails&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;WebSearchTool&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;External retrieval (high latency, high cost if abused)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;calculate_win_percentage&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Deterministic custom tool for stats questions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Input guardrail&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Blocks off-topic queries (e.g., weather in Barcelona)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OpenAIConversationsSession&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Server-managed context across turns&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Running it is straightforward once environment variables are set:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;python/opentelemetry-llm-demo
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

&lt;span class="nv"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;your-key&amp;gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;OTEL_EXPORTER_OTLP_ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://ingest.&amp;lt;region&amp;gt;.signoz.cloud:443"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;OTEL_EXPORTER_OTLP_HEADERS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"signoz-ingestion-key=&amp;lt;your-key&amp;gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;OTEL_SERVICE_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"opentelemetry-llm-demo"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;OTEL_RESOURCE_ATTRIBUTES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"service.version=0.1.0,deployment.environment=dev"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;OTEL_EXPORTER_OTLP_PROTOCOL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"http/protobuf"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
opentelemetry-instrument fastapi run &lt;span class="nt"&gt;--port&lt;/span&gt; 8085 &lt;span class="nt"&gt;--workers&lt;/span&gt; 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I could not get a Signoz free trial account due to regional limitations on their site. But since this demo supports Open Telemetry vendors, i created a grafana labs free trial account for 15 days and use that to export the metrics.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;python/opentelemetry-llm-demo
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

&lt;span class="nv"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;your-key&amp;gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;OTEL_EXPORTER_OTLP_ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://otlp-gateway-prod-ap-south-1.grafana.net/otlp"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;OTEL_EXPORTER_OTLP_HEADERS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Authorization=Basic &amp;lt;GrafanaLabstoken&amp;gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;OTEL_SERVICE_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"opentelemetry-llm-demo"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;OTEL_RESOURCE_ATTRIBUTES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"service.version=0.1.0,deployment.environment=dev"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;OTEL_EXPORTER_OTLP_PROTOCOL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"http/protobuf"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
opentelemetry-instrument fastapi run &lt;span class="nt"&gt;--port&lt;/span&gt; 8085 &lt;span class="nt"&gt;--workers&lt;/span&gt; 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important detail: &lt;strong&gt;&lt;code&gt;opentelemetry-instrumentation-openai-agents-v2&lt;/code&gt; is pinned manually&lt;/strong&gt; in &lt;code&gt;requirements.txt&lt;/code&gt;. &lt;code&gt;opentelemetry-bootstrap&lt;/code&gt; does not yet discover the OpenAI Agents SDK. That’s a real-world snapshot of GenAI observability in 2026—powerful, but still maturing.&lt;/p&gt;




&lt;h2&gt;
  
  
  What a Good Trace Actually Looks Like
&lt;/h2&gt;

&lt;p&gt;After sending a few turns—initial question, follow-up with &lt;code&gt;session_id&lt;/code&gt;, a stats question that triggers the tool, and an off-topic question blocked by the guardrail—I knew what “healthy” vs “blocked” looked like in the trace UI.&lt;/p&gt;

&lt;h3&gt;
  
  
  A successful multi-turn flow
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;POST /agent/turn&lt;/code&gt;&lt;/strong&gt; — root HTTP span
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session I/O&lt;/strong&gt; — GET/POST spans for conversation fetch and persist
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;invoke_agent&lt;/code&gt;&lt;/strong&gt; — agent workflow container
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;guardrail_check&lt;/code&gt;&lt;/strong&gt; — with &lt;code&gt;gen_ai.guardrail.triggered=false&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model call span&lt;/strong&gt; — model name, token usage, optional message content attributes
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tool spans&lt;/strong&gt; — when the agent calls &lt;code&gt;calculate_win_percentage&lt;/code&gt; or web search
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Follow-up requests reuse &lt;code&gt;session_id&lt;/code&gt;; traces show the agent pulling prior context instead of starting cold. That context growth shows up in &lt;strong&gt;rising input token counts&lt;/strong&gt; over a session—something worth dashboarding.&lt;/p&gt;

&lt;h3&gt;
  
  
  A guardrail block
&lt;/h3&gt;

&lt;p&gt;Off-topic queries still produce useful telemetry:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;guardrail_check&lt;/code&gt; with &lt;strong&gt;&lt;code&gt;gen_ai.guardrail.triggered=true&lt;/code&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Workflow stops before expensive model/tool work&lt;/li&gt;
&lt;li&gt;FastAPI records an exception on the span via &lt;code&gt;span.record_exception(exc)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;API returns &lt;code&gt;400&lt;/code&gt; with a clear message&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s observability doing safety &lt;em&gt;and&lt;/em&gt; cost control: you can measure how often guardrails fire, by topic, without paying for a full agent loop.&lt;/p&gt;

&lt;h3&gt;
  
  
  Honest gaps I noticed
&lt;/h3&gt;

&lt;p&gt;Several spans appeared as &lt;strong&gt;&lt;code&gt;unknown&lt;/code&gt;&lt;/strong&gt;—internal SDK paths not yet mapped to semantic span names. Child spans (e.g., &lt;code&gt;guardrail_check&lt;/code&gt;) still tell the story. An open conformance effort for the Agents SDK should improve this. As an architect, plan for &lt;strong&gt;partial coverage today&lt;/strong&gt; and &lt;strong&gt;richer conventions tomorrow&lt;/strong&gt;—design dashboards around stable attributes (&lt;code&gt;gen_ai.*&lt;/code&gt;) where possible.&lt;/p&gt;




&lt;h2&gt;
  
  
  Observability Across the Full Agentic Ecosystem
&lt;/h2&gt;

&lt;p&gt;The NBA demo covers HTTP → agent → guardrail → LLM → tools → session store. Production agentic systems add more layers. Each needs the same treatment: &lt;strong&gt;trace context propagation&lt;/strong&gt;, &lt;strong&gt;consistent span naming&lt;/strong&gt;, and &lt;strong&gt;metrics that roll up to SLOs&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzw8xzgstwfzz54tdp388.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzw8xzgstwfzz54tdp388.png" alt="Interactions between components in Agentic Ecosystem" width="800" height="478"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  LLM interactions
&lt;/h3&gt;

&lt;p&gt;Every model call should emit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Operation name and provider (&lt;code&gt;gen_ai.system&lt;/code&gt;, &lt;code&gt;gen_ai.request.model&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Input / output / total tokens&lt;/strong&gt; (and cached tokens when available)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duration&lt;/strong&gt; (&lt;code&gt;gen_ai.client.operation.duration&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Finish reason, error type, retry count&lt;/li&gt;
&lt;li&gt;Optional: prompt/completion content (off in production via &lt;code&gt;OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=0&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The demo surfaces usage in the API response as well—useful for debugging, but &lt;strong&gt;metrics and traces are the source of truth&lt;/strong&gt; for trends:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"usage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"input_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"output_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"total_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;168&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Tool usage
&lt;/h3&gt;

&lt;p&gt;Tools are where agents become reliable—or expensive.&lt;/p&gt;

&lt;p&gt;Instrument each tool with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Span name: e.g., &lt;code&gt;tool.calculate_win_percentage&lt;/code&gt; or &lt;code&gt;tool.web_search&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Arguments (sanitized), result size, success/failure&lt;/li&gt;
&lt;li&gt;Latency and timeout events&lt;/li&gt;
&lt;li&gt;Parent link back to the agent span&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Compare &lt;strong&gt;tool latency vs model latency&lt;/strong&gt;. In many agents, a slow external API dominates p95 while the LLM looks fine in isolation.&lt;/p&gt;

&lt;h3&gt;
  
  
  API calls (REST, GraphQL, internal services)
&lt;/h3&gt;

&lt;p&gt;Web search in the demo is a stand-in for any external dependency. Treat them like microservice calls:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Propagate trace context in outbound headers&lt;/li&gt;
&lt;li&gt;Record status codes, payload sizes, and retry behavior&lt;/li&gt;
&lt;li&gt;Tag by dependency name for dependency maps&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  MCP server calls
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://modelcontextprotocol.io/" rel="noopener noreferrer"&gt;Model Context Protocol (MCP)&lt;/a&gt; servers are becoming the standard way agents reach databases, repos, ticketing systems, and internal APIs. They deserve first-class spans:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Why it matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;mcp.server.name&lt;/code&gt;, &lt;code&gt;mcp.tool.name&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Which integration failed or slowed down&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request/response size&lt;/td&gt;
&lt;td&gt;Context bloat and cost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Duration&lt;/td&gt;
&lt;td&gt;Often dominates agent turn time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Error taxonomy&lt;/td&gt;
&lt;td&gt;Auth vs timeout vs schema mismatch&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Propagate the same trace ID from your API through the agent into MCP client calls. Without that, “the agent was slow” is impossible to decompose.&lt;/p&gt;

&lt;h3&gt;
  
  
  Guardrails and policy layers
&lt;/h3&gt;

&lt;p&gt;The demo’s keyword guardrail is simple; production systems use classifiers, PII detectors, and budget enforcers. Track:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Trigger rate by guardrail name&lt;/li&gt;
&lt;li&gt;Time spent in guardrail evaluation&lt;/li&gt;
&lt;li&gt;Block vs warn vs redact outcomes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Correlate guardrail blocks with &lt;strong&gt;saved tokens&lt;/strong&gt; and &lt;strong&gt;avoided tool calls&lt;/strong&gt; to justify the complexity to leadership.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sessions and memory
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;OpenAIConversationsSession&lt;/code&gt; makes context explicit. For any memory layer (vector store, Redis, custom DB), observe:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read/write latency per turn&lt;/li&gt;
&lt;li&gt;Context size growth (tokens or bytes)&lt;/li&gt;
&lt;li&gt;Cache hit rate for retrieved chunks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Runaway context is a leading cause of &lt;strong&gt;latency and cost spikes&lt;/strong&gt; in long conversations.&lt;/p&gt;




&lt;h2&gt;
  
  
  Metrics Every AI Architect Should Track
&lt;/h2&gt;

&lt;p&gt;Traces explain &lt;em&gt;one&lt;/em&gt; request. Metrics tell you whether the system is healthy at scale. Below is a practical catalog—some exported automatically by GenAI instrumentation today, others you’ll derive or add custom instrumentation for.&lt;/p&gt;

&lt;h3&gt;
  
  
  Latency and streaming
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Definition&lt;/th&gt;
&lt;th&gt;Why it matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Time to First Token (TTFT)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Request start → first streamed token&lt;/td&gt;
&lt;td&gt;User-perceived responsiveness; spikes often indicate queueing or cold starts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Time per token / inter-token latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;(Total generation time − TTFT) / (output tokens − 1)&lt;/td&gt;
&lt;td&gt;Detects “stuttery” streaming and provider throttling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;End-to-end turn latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;HTTP request → final response&lt;/td&gt;
&lt;td&gt;Your real SLO; includes tools, MCP, and multiple model rounds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Agent loop depth&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Number of model invocations per turn&lt;/td&gt;
&lt;td&gt;High depth → runaway reasoning or tool retry loops&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;gen_ai.client.operation.duration&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Provider-reported operation time&lt;/td&gt;
&lt;td&gt;Compare across models and regions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Tokens and cost
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Definition&lt;/th&gt;
&lt;th&gt;Why it matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;&lt;code&gt;gen_ai.client.token.usage&lt;/code&gt;&lt;/strong&gt; (input / output / total)&lt;/td&gt;
&lt;td&gt;Tokens per operation&lt;/td&gt;
&lt;td&gt;Direct driver of cost and context pressure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cached input tokens&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tokens served from provider cache&lt;/td&gt;
&lt;td&gt;Cost optimization signal (when exposed)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost per turn / per user / per session&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;tokens × price table&lt;/td&gt;
&lt;td&gt;Finance and capacity planning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Token growth per session turn&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Δ input tokens turn-over-turn&lt;/td&gt;
&lt;td&gt;Early warning for context explosion&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Reliability and quality proxies
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Definition&lt;/th&gt;
&lt;th&gt;Why it matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Error rate by span kind&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;LLM vs tool vs MCP vs HTTP&lt;/td&gt;
&lt;td&gt;Target fixes where failures cluster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tool success rate &amp;amp; p95 latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Per tool name&lt;/td&gt;
&lt;td&gt;Fragile tools break agent reliability&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Guardrail trigger rate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Blocks / total requests&lt;/td&gt;
&lt;td&gt;Mis-tuned guardrails hurt UX; under-tuned ones hurt safety&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Empty or truncated response rate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;502s, zero output tokens&lt;/td&gt;
&lt;td&gt;Model or SDK integration issues&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Provider availability / timeout rate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;By model and region&lt;/td&gt;
&lt;td&gt;Brownout detection&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Agent-specific operational metrics
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Definition&lt;/th&gt;
&lt;th&gt;Why it matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tool calls per turn&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Count by tool&lt;/td&gt;
&lt;td&gt;Cost and latency multipliers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MCP calls per turn&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Count by server/tool&lt;/td&gt;
&lt;td&gt;Integration hot paths&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Parallel vs sequential tool time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sum of tool spans vs wall clock&lt;/td&gt;
&lt;td&gt;Opportunities to parallelize&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Retry count&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;On LLM and downstream calls&lt;/td&gt;
&lt;td&gt;Instability or aggressive client config&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Suggested dashboards
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Cost &amp;amp; usage&lt;/strong&gt; — tokens and estimated spend by model, topic, tenant
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latency&lt;/strong&gt; — p50/p95 TTFT, turn latency, tool latency
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reliability&lt;/strong&gt; — error rates, guardrail blocks, tool failures
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent behavior&lt;/strong&gt; — tool/MCP call mix, loop depth, session length
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;SigNoz’s OpenAI Python SDK dashboard template is a reasonable starting point; expect to extend it for Agents SDK, MCP, and custom tools.&lt;/p&gt;




&lt;h2&gt;
  
  
  GenAI Semantic Conventions: What I Internalized
&lt;/h2&gt;

&lt;p&gt;OpenTelemetry uses &lt;strong&gt;&lt;code&gt;gen_ai.*&lt;/code&gt;&lt;/strong&gt; attributes under evolving &lt;a href="https://github.com/open-telemetry/semantic-conventions-genai" rel="noopener noreferrer"&gt;GenAI semantic conventions&lt;/a&gt;. Treat them as a &lt;strong&gt;contract in motion&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;gen_ai.agent.name&lt;/code&gt;&lt;/strong&gt; — which agent ran
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;gen_ai.input.messages&lt;/code&gt; / &lt;code&gt;gen_ai.output.messages&lt;/code&gt;&lt;/strong&gt; — powerful for debug, risky for PII in production
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;gen_ai.guardrail.triggered&lt;/code&gt;&lt;/strong&gt; — policy outcomes
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;gen_ai.client.token.usage&lt;/code&gt;&lt;/strong&gt; — standardized token metrics
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;gen_ai.client.operation.duration&lt;/code&gt;&lt;/strong&gt; — standardized latency metrics
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Content capture:&lt;/strong&gt; With session-backed conversations, &lt;code&gt;gen_ai.input.messages&lt;/code&gt; may include &lt;strong&gt;prior turns&lt;/strong&gt;, not just the latest user message. Disable capture in production:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Instrumentation choice:&lt;/strong&gt; The plain OpenAI Python SDK instrumentation lagged the Responses API; the demo uses &lt;strong&gt;OpenAI Agents SDK + &lt;code&gt;opentelemetry-instrumentation-openai-agents-v2&lt;/code&gt;&lt;/strong&gt;. Pick instrumentation that matches your API surface, or you’ll fly blind on the code path you actually run.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lessons I’m Taking Into Production Designs
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Instrument at the agent boundary, not only at the LLM client.&lt;/strong&gt; The demo’s value is seeing guardrails, sessions, tools, and model calls in one trace—not three disconnected logs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Treat tools and MCP like microservices.&lt;/strong&gt; They need spans, timeouts, and RED metrics (rate, errors, duration). An agent is only as reliable as its slowest dependency.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Measure cost and latency together.&lt;/strong&gt; A “cheap” model that needs three extra tool rounds or a longer context window can cost more than a faster model with better cache behavior.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Guardrails are observability events.&lt;/strong&gt; Blocked requests should be visible, measurable, and cheap—exactly what the demo’s guardrail path demonstrates.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Plan for convention churn.&lt;/strong&gt; Manual dependency pins, occasional &lt;code&gt;unknown&lt;/code&gt; spans, and missing cache metrics today are normal. Abstract your dashboards around stable concepts (turn, tool, token, cost), not one vendor’s span titles.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Correlate logs and traces.&lt;/strong&gt; Enabling &lt;code&gt;OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true&lt;/code&gt; ties application logs to trace IDs—essential when debugging “the agent said something weird” incidents.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;If you’re evaluating observability for an agentic product:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Clone the &lt;a href="https://github.com/SigNoz/examples" rel="noopener noreferrer"&gt;SigNoz examples repo&lt;/a&gt; and run &lt;code&gt;python/opentelemetry-llm-demo&lt;/code&gt;.
&lt;/li&gt;
&lt;li&gt;Read the &lt;a href="https://signoz.io/blog/opentelemetry-llm/" rel="noopener noreferrer"&gt;original SigNoz walkthrough&lt;/a&gt; for setup screenshots and trace UI context.
&lt;/li&gt;
&lt;li&gt;Run four scenarios: first turn, follow-up with &lt;code&gt;session_id&lt;/code&gt;, a tool question (“Raptors went 46-36—what’s their win percentage?”), and an off-topic guardrail block.
&lt;/li&gt;
&lt;li&gt;In your backend, note span hierarchy, token metrics, and guardrail attributes.
&lt;/li&gt;
&lt;li&gt;List your production integrations (APIs, MCP servers, memory stores) and mark which ones &lt;strong&gt;lack&lt;/strong&gt; spans today—that’s your instrumentation backlog.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;Agentic AI isn’t “an LLM behind an API.” It’s a &lt;strong&gt;distributed system&lt;/strong&gt; where the planner is probabilistic and the dependencies are heterogeneous. OpenTelemetry gives you a shared language for that system—traces for debugging individual turns, metrics for TTFT, token cost, tool reliability, and guardrail behavior at scale.&lt;/p&gt;

&lt;p&gt;Running the SigNoz NBA agent demo didn’t just validate a tutorial. It clarified what I need to require from every agent service we design: &lt;strong&gt;propagated trace context&lt;/strong&gt;, &lt;strong&gt;GenAI semantic attributes&lt;/strong&gt;, &lt;strong&gt;tool and MCP spans&lt;/strong&gt;, and &lt;strong&gt;dashboards that connect latency to tokens to dollars&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The conventions and SDK instrumentation will keep improving. The architectural imperative won’t: &lt;strong&gt;if you can’t see the full agent loop, you can’t operate it in production.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have you instrumented MCP servers or multi-agent workflows yet? I’d be interested in what span attributes and metrics you’ve standardized on—drop a comment or reach out.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>observability</category>
      <category>agenticai</category>
      <category>llm</category>
    </item>
    <item>
      <title>Extend Bitnami Cassandra Image to customize the configuration in cassandra.yaml</title>
      <dc:creator>Saket</dc:creator>
      <pubDate>Mon, 29 Apr 2024 12:07:42 +0000</pubDate>
      <link>https://dev.to/archcode01/extend-bitnami-cassandra-image-to-customize-the-configuration-in-cassandrayaml-nma</link>
      <guid>https://dev.to/archcode01/extend-bitnami-cassandra-image-to-customize-the-configuration-in-cassandrayaml-nma</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fst12bpcw0r8uxm52sb9w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fst12bpcw0r8uxm52sb9w.png" alt="Cassandra on Kubernetes" width="800" height="266"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cassandra is a NoSQL Column Family based database. It is generally recommended in uses cases which need fast writes. Cassandra is deployed on Kubernetes as Statefulset due to its nature unlike stateless applications which are deployed as Deployments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bitnami Cassandra Image
&lt;/h2&gt;

&lt;p&gt;There are multiple benefits of using the images from &lt;a href="https://github.com/bitnami/containers/tree/main/bitnami/cassandra"&gt;Bitnami&lt;/a&gt;. We can refer to their github repo for additional details. &lt;br&gt;
The Bitnami image from cassandra provides us the option to override few of the configurations in the cassandra.yaml file by passing the values as environment variables.&lt;br&gt;
For eg: When we provide an environment variable - CASSANDRA_CLUSTER_NAME – to the container, the value of this variable gets updated in the cassandra.yaml -&amp;gt; cluster_name field.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#cassandra.yaml
..
...
# The name of the cluster. This is mainly used to prevent machines in
# one logical cluster from joining another.
cluster_name: ‘Dev Cassandra Cluster'
...
..
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The image executes container with a command which executes a shell script. This script when executes creates a cassandra.yaml file with default configurations and some parameters that can be provided to the container. Once the file is generated, it is placed in the appropriate location and then then the last step of the script is to start the Cassandra process.&lt;br&gt;
Like explained above regarding the cluster_name configuration in cassandra.yaml, there are various other configurations which can be updated by providing values through environment variables. For all such variables please go through the github page of Bitnami cassandra image. &lt;/p&gt;
&lt;h2&gt;
  
  
  Need for further customization
&lt;/h2&gt;

&lt;p&gt;The Bitnami image does provided custom configuration for some of the fields in cassandra.yaml. When we were working on a POC we encountered a problem related to queries failing due to tombstones failure threshold. A simple google search will help you explain what are tombstones in cassandra.&lt;br&gt;
The default limit for tombstone failure threshold is 100000 but our POC use case had nothing to worry even if this threshold was breached. So, we wanted a way to customize this number setting on the Bitnami Cassandra image that we were using. The nearest answer/solution that we got was to provide an entire cassandra.yaml file to the image. This way we could override which ever configuration we wanted and it worked in a simple single instance cluster. But when we scaled the cluster to 3 instances the custom configuration file did not help the new instances to join the cluster due to seed node address setting of the configuration. &lt;/p&gt;
&lt;h2&gt;
  
  
  Bitnami Image code on Github
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6f1rjzatin4lcccz6t3s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6f1rjzatin4lcccz6t3s.png" alt="Bitnami Cassandra Github" width="800" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we check the rootfs/opt/bitnami/scripts folder, it has few shell scripts.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F06zatur15o8m9gyn3stj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F06zatur15o8m9gyn3stj.png" alt="Bitnami Cassandra Scripts Github" width="800" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;u&gt;Libcassandra.sh&lt;/u&gt;&lt;/strong&gt; -&amp;gt; responsible for setting up the cassandra.yaml by reading the configurations from environment variables. &lt;br&gt;
&lt;strong&gt;&lt;u&gt;Cassandra-env.sh &lt;/u&gt;&lt;/strong&gt;-&amp;gt; this script is included in the libcassandra.sh and is responsible for injecting the environment variables into environment using export.&lt;/p&gt;
&lt;h2&gt;
  
  
  How to customize Bitnami Image to override configuration through environment variables.
&lt;/h2&gt;

&lt;p&gt;So in our case we wanted to override the tombstone failure threshold in cassandra.yaml. For this to achieve we introduced a new variable in Cassandra-env.sh file towards the bottom of this file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash
#
# Environment configuration for cassandra

# The values for all environment variables will be set in the below order of precedence
# 1. Custom environment variables defined below after Bitnami defaults
# 2. Constants defined in this file (environment variables with no default), i.e. BITNAMI_ROOT_DIR
# 3. Environment variables overridden via external files using *_FILE variables (see below)
# 4. Environment variables set externally (i.e. current Bash context/Dockerfile/userdata)

# Load logging library
# shellcheck disable=SC1090,SC1091
. /opt/bitnami/scripts/liblog.sh

export BITNAMI_ROOT_DIR="/opt/bitnami"
export BITNAMI_VOLUME_DIR="/bitnami"
…
…
..

# Custom environment variables may be defined below
export CASSANDRA_TOMBSTONE_WARN_THRESHOLD="${CASSANDRA_TOMBSTONE_WARN_THRESHOLD:-1000}"
export CASSANDRA_TOMBSTONE_FAILURE_THRESHOLD="${CASSANDRA_TOMBSTONE_FAILURE_THRESHOLD:-100000}"


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above code just exports the value if passed in as environment variable or default it to 100000 for failure threshold variable. Similarly for warn threshold variable.&lt;br&gt;
In libcassandra.sh file there is a function called cassandra_setup_cluster(). This function actually sets up the Cassandra.yaml file which has all the configuration for the instance.&lt;br&gt;
This function calls the below function to set each configuration in cassandra.yaml file that needs to be customized or overridden.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cassandra_yaml_set "listen_address" "$host"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So we can call the same function and set the tombstone related threshold values accordingly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        cassandra_yaml_set "tombstone_warn_threshold" "$CASSANDRA_TOMBSTONE_WARN_THRESHOLD"
        debug "setting tombstone_warn_threshold to $CASSANDRA_TOMBSTONE_WARN_THRESHOLD"
        cassandra_yaml_set "tombstone_failure_threshold" "$CASSANDRA_TOMBSTONE_FAILURE_THRESHOLD"
        debug "setting tombstone_failure_threshold to $CASSANDRA_TOMBSTONE_FAILURE_THRESHOLD"       

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the help of docker file we can now build the Cassandra image and push to image repository. &lt;br&gt;
When this image is used in docker cli or Kubernetes pod and if we want to override the default tombstone threshold specific values then we can pass the below environvent variable with our custom override value.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Docker run Cassandra –iimage mycustomcassandra:latest -e CASSANDRA_TOMBSTONE_FAILURE_THRESHOLD=200000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or in Kubernetes statefulset definition as follows:&lt;br&gt;
(&lt;em&gt;Please check the last of the env values passed to the statefulset&lt;/em&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kind: StatefulSet
apiVersion: apps/v1
metadata:
  name: cassandra
  namespace: cassandra
spec:
  replicas: 3
  selector:
    matchLabels:
      app.kubernetes.io/instance: cassandra
      app.kubernetes.io/name: cassandra
  template:
    metadata:
      creationTimestamp: null
      labels:
        app.kubernetes.io/instance: cassandra
        app.kubernetes.io/name: cassandra
    spec:
      containers:
        - name: cassandra
          image: my-custom-cassandra:latest
          command:
            - bash
            - '-ec'
            - &amp;gt;
              # Node 0 is the password seeder

              if [[ $POD_NAME =~ (.*)-0$ ]]; then
                  echo "Setting node as password seeder"
                  export CASSANDRA_PASSWORD_SEEDER=yes
              else
                  # Only node 0 will execute the startup initdb scripts
                  export CASSANDRA_IGNORE_INITDB_SCRIPTS=1
              fi

              /opt/bitnami/scripts/cassandra/entrypoint.sh
              /opt/bitnami/scripts/cassandra/run.sh
          ports:
            - name: intra
              containerPort: 7000
              protocol: TCP
            - name: tls
              containerPort: 7001
              protocol: TCP
            - name: jmx
              containerPort: 7199
              protocol: TCP
            - name: cql
              containerPort: 9042
              protocol: TCP
            - name: thrift
              containerPort: 9160
              protocol: TCP
          env:
            - name: BITNAMI_DEBUG
              value: 'false'
            - name: CASSANDRA_CLUSTER_NAME
              value: cassandra
            - name: CASSANDRA_SEEDS
              value: cassandra-0.cassandra-headless.cassandra.svc.cluster.local
            - name: CASSANDRA_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: cassandra
                  key: cassandra-password
            - name: POD_IP
              valueFrom:
                fieldRef:
                  apiVersion: v1
                  fieldPath: status.podIP
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  apiVersion: v1
                  fieldPath: metadata.name
            - name: CASSANDRA_USER
              value: cassandra
            - name: CASSANDRA_NUM_TOKENS
              value: '256'
            - name: CASSANDRA_DATACENTER
              value: dc1
            - name: CASSANDRA_ENDPOINT_SNITCH
              value: SimpleSnitch
            - name: CASSANDRA_KEYSTORE_LOCATION
              value: /opt/bitnami/cassandra/certs/keystore
            - name: CASSANDRA_TRUSTSTORE_LOCATION
              value: /opt/bitnami/cassandra/certs/truststore
            - name: CASSANDRA_CLIENT_ENCRYPTION
              value: 'true'
            - name: CASSANDRA_TRUSTSTORE_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: cassandra-tls-pass
                  key: truststore-password
            - name: CASSANDRA_KEYSTORE_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: cassandra-tls-pass
                  key: keystore-password
            - name: CASSANDRA_RACK
              value: rack1
            - name: CASSANDRA_ENABLE_RPC
              value: 'true'
            - name: CASSANDRA_TRANSPORT_PORT_NUMBER
              value: '7000'
            - name: CASSANDRA_JMX_PORT_NUMBER
              value: '7199'
            - name: CASSANDRA_CQL_PORT_NUMBER
              value: '9042'
            - name: CASSANDRA_TOMBSTONE_FAILURE_THRESHOLD
              value: 200000
            - name: CASSANDRA_TOMBSTONE_WARN_THRESHOLD
              value: 2000
          resources:
            limits:
              cpu: '3'
              memory: 16Gi
            requests:
              cpu: 1500m
              memory: 8Gi
          volumeMounts:
            - name: data
              mountPath: /bitnami/cassandra
            - name: certs-shared
              mountPath: /opt/bitnami/cassandra/certs
          livenessProbe:
            exec:
              command:
                - /bin/bash
                - '-ec'
                - |
                  nodetool status
            initialDelaySeconds: 60
            timeoutSeconds: 5
            periodSeconds: 30
            successThreshold: 1
            failureThreshold: 5
          readinessProbe:
            exec:
              command:
                - /bin/bash
                - '-ec'
                - |
                  nodetool status | grep -E "^UN\\s+${POD_IP}"
            initialDelaySeconds: 60
            timeoutSeconds: 5
            periodSeconds: 10
            successThreshold: 1
            failureThreshold: 5
          securityContext:
            runAsUser: 1001
            runAsNonRoot: true
            allowPrivilegeEscalation: false
  volumeClaimTemplates:
    - kind: PersistentVolumeClaim
      apiVersion: v1
      metadata:
        name: data
        creationTimestamp: null
        labels:
          app.kubernetes.io/instance: cassandra
          app.kubernetes.io/name: cassandra
      spec:
        accessModes:
          - ReadWriteOnce
        resources:
          requests:
            storage: 1Ti
        storageClassName: default
        volumeMode: Filesystem

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>cassandra</category>
      <category>kubernetes</category>
      <category>bitnami</category>
    </item>
    <item>
      <title>Add Custom Headers to Outgoing request from applications deployed on Kubernetes</title>
      <dc:creator>Saket</dc:creator>
      <pubDate>Fri, 26 Apr 2024 15:24:11 +0000</pubDate>
      <link>https://dev.to/archcode01/add-custom-headers-to-outgoing-request-from-applications-deployed-on-kubernetes-37n7</link>
      <guid>https://dev.to/archcode01/add-custom-headers-to-outgoing-request-from-applications-deployed-on-kubernetes-37n7</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Kubernetes in todays world is the defacto platform for deploying containerized applications including microservices because of the various benefits this platform offers including automated deployment, scaling security, resiliency, load balancing and self healing along with many others. &lt;br&gt;
This article will explain how we can automate the insertion of custom headers in outgoing requests from the applications that are deployed in Kubernetes with Istio Service Mesh.&lt;/p&gt;
&lt;h2&gt;
  
  
  Communication between Pods using Kubernetes Service
&lt;/h2&gt;

&lt;p&gt;Each Pod deployed gets its onw unique internal IP address in the cluster. When applications running inside pods wants to connect to other pods, they can connect using the podname or pod ipaddress. &lt;br&gt;
In case there are multiple replicas of an application running leading to multiple pods then kubernetes service is used to reach the destination. &lt;br&gt;
Below diagram shows the pod to pod communication through kubernetes service. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbx34i7iiplkw9xour0i3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbx34i7iiplkw9xour0i3.png" alt="Pod communication inside Kubernetes with K8S Service" width="751" height="411"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Istio Service Mesh
&lt;/h2&gt;

&lt;p&gt;Service Mesh is an infrastructure layer that handles communication and networking between services in an application. It handles traffic, monitors performance and enforces different policies. &lt;br&gt;
Some of the benefits of using service mesh includes Service Discovery, Traffic control, Observability, Security and Compliance&lt;br&gt;
&lt;a href="https://istio.io/latest/"&gt;Istio&lt;/a&gt; is an open source service mesh which offers all of the above mentioned features of a typical service mesh. You can read about it more on this link &lt;a href="https://istio.io/latest/docs/reference/config/networking/envoy-filter/"&gt;Istio / Architecture&lt;/a&gt;&lt;br&gt;
Istio uses an extended version of the Envoy proxy. Envoy is a high-performance proxy developed in C++ to mediate all inbound and outbound traffic for all services in the service mesh. Envoy proxies are the only Istio components that interact with data plane traffic. Istio deploys Envoy proxies as side car containers to workloads logically augmenting the services with many built in features of Envoy.&lt;/p&gt;

&lt;p&gt;Below diagram shows the communication across applications when Istio Service Mesh is used.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2l5j7pliy2nuif1gly8p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2l5j7pliy2nuif1gly8p.png" alt="Pod communication inside Kubernetes with Istio" width="751" height="351"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Insert Custom Headers to Outgoing Requests
&lt;/h2&gt;

&lt;p&gt;Envoy proxy intercepts all the incoming and outgoing requests  within the pod to/from all the containers that are running in the pod. This configuration can be overridden to implement various functionalities like logging, auditing, authentication, modifying requests etc. Envoy filters are additively applied to the proxy configuration. We can add multiple filters with different functionalities.&lt;br&gt;
Here we will see how can we extend this functionality to add custom headers to outgoing requests from the pods.&lt;/p&gt;

&lt;p&gt;We need to create the EnvoyFilter kubernetes Istio CRD and override the function which is invoked when a request is intercepted by the proxy.&lt;br&gt;
The following code enables Envoys Lua filter for all outbound HTTP calls on service port 8080 of the myapplication service pod  with labels "app: myapplication". The Lua filter adds a custom header key and value to the request handle headers object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: customheaders-filter
spec:
  workloadSelector:
    labels:
      app: myapplication
  configPatches:
  - applyTo: HTTP_FILTER
    match:
      context: SIDECAR_OUTBOUND
      listener:
        portNumber: 8080
        filterChain:
          filter:
            name: envoy.filters.network.http_connection_manager
            subFilter:
              name: envoy.filters.http.router
    patch:
      operation: INSERT_BEFORE
      value:
        name: envoy.lua
        typed_config:
          "@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua"
          inlineCode: |

              function envoy_on_request(request_handle)
                request_handle:headers():add( "my-custom-header", "my-custom-header-value )
              end

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can also extend this function to add headers only if the destination host is expected one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  function envoy_on_request(request_handle)
    local destinationHost = request_handle:headers():get(":authority")
    if destinationHost ~= nil and destinationHost ~= '' then
      request_handle:logInfo(" found authority  :"..destinationHost)
    else
      request_handle:logInfo(" no authority found")
      destinationHost = ''
    end

    request_handle:logInfo("destination host -&amp;gt; "..destinationHost) 
    request_handle:logInfo("configured host -&amp;gt; {{ $customHeadersByHost.host }}")

    if destinationHost:find({{ $customHeadersByHost.host | quote }}, 0, true) ~= nil then
      {{- range $header := $customHeadersByHost.headers }}
      request_handle:headers():add({{ $header.name | quote }} , {{ $header.value | quote }} )
      {{- end }}
      request_handle:logInfo(" added headers for host - {{ $customHeadersByHost.host }}")
    else
      request_handle:logInfo(" destination does not match configured host ")
    end

  end

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inorder to view the envoy-filter proxy logs from the pods we need to add the below annotations to the Pod definition if logging is restricted at istio system level.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  sidecar.istio.io/logLevel: info
spec:
  ..
  ..


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To view the logs that are added in the on request function above, we can view them using kubectl command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl logs -n mynamespace myappliation-pod -c istio-proxy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>Apache Airflow - For Beginners</title>
      <dc:creator>Saket</dc:creator>
      <pubDate>Mon, 18 Mar 2024 10:47:39 +0000</pubDate>
      <link>https://dev.to/archcode01/apache-airflow-for-beginners-2kn4</link>
      <guid>https://dev.to/archcode01/apache-airflow-for-beginners-2kn4</guid>
      <description>&lt;h2&gt;
  
  
  Airflow: A Beginner's Guide to Data Orchestration in Data Pipelines.
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqyinby4gycjftxir2zlp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqyinby4gycjftxir2zlp.png" alt="Image description" width="638" height="601"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Data pipelines are the lifeblood of modern data-driven applications. These data pipelines help automate the flow of data from various sources to its single or multiple destinations by transforming it. But managing these pipelines, especially complex ones, can be a challenge. Apache Airflow an open source solution helps in overcoming this challenge. This is a very basic introduction to Apache Airflow for absolute beginners.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Brief History of Airflow
&lt;/h3&gt;

&lt;p&gt;Airflow was originally created by engineers at Airbnb to internally manage their evergrowing data processing pipelines. Recognizing the potential it has, Airbnb open-sourced Airflow in 2016, making it available for the wider developer community. Today, Airflow is a mature and widely adopted tool, used by companies of all sizes to orchestrate and automate their data workflows.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Airflow Works
&lt;/h3&gt;

&lt;p&gt;At its core, Airflow is a workflow orchestration tool. It allows you to define complex data pipelines as Directed Acyclic Graphs (DAGs). A DAG is a series of tasks with dependencies, ensuring they run in the correct order. By Acyclic, it means the flow of data never returns the same node in graph which it had crossed once before. &lt;/p&gt;

&lt;p&gt;Here's a simplified breakdown of how Airflow works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Define Tasks:&lt;/strong&gt; You define individual tasks within your workflow using Python code. There is support for other languages also through plugins or using kubernetes pod operator to define these tasks. These tasks could involve data extraction, transformation, loading, analyzing, cleaning or any other operations needed in your pipeline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set Dependencies:&lt;/strong&gt; You specify dependencies between tasks. For example, a data transformation task might depend on a data extraction task completing successfully before it can run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schedule Workflows:&lt;/strong&gt; Airflow allows you to schedule your workflows to run at specific times (cron expressions) or at regular intervals.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring and Alerting:&lt;/strong&gt; Airflow provides a web interface to monitor the status of your workflows and tasks. You can also configure alerts to be sent if tasks fail or encounter issues.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpp7r1r8rjql56svhe2q2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpp7r1r8rjql56svhe2q2.png" alt="Image description" width="731" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploying Airflow in Production
&lt;/h3&gt;

&lt;p&gt;For production environments, proper deployment of Airflow is essential. Here are some considerations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Security:&lt;/strong&gt; Implement role-based access control (RBAC) to restrict access to workflows and resources.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High Availability:&lt;/strong&gt; Consider setting up a high-availability configuration with redundant Airflow servers and worker nodes to ensure uptime.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring and Logging:&lt;/strong&gt; Integrate Airflow with monitoring tools to track performance and identify potential issues.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Advantages of Apache Airflow
&lt;/h3&gt;

&lt;p&gt;Here's a breakdown of some key advantages Apache Airflow holds over other commonly available open-source data orchestration tools:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maturity and Large Community:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Extensive Resources:&lt;/strong&gt; Airflow, being a mature project, boasts a vast collection of documentation, tutorials, and readily available solutions for common challenges. This translates to easier learning, faster troubleshooting, and a wealth of resources to tap into.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strong Community Support:&lt;/strong&gt; The active and large Airflow community provides valuable support through forums, discussions, and contributions. This can be immensely helpful when encountering issues or seeking best practices.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Flexibility and Ease of Use:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Python-Based Approach:&lt;/strong&gt;  Airflow leverages Python for defining tasks, making it accessible to users with varying coding experience. Python is a widely used language familiar to many data professionals.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User-Friendly Interface:&lt;/strong&gt; The web interface provides a decent level of user-friendliness for monitoring and managing workflows. It allows for visualization of task dependencies and overall workflow status.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Scalability and Extensibility:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Horizontal Scaling:&lt;/strong&gt; Airflow scales horizontally by adding more worker nodes to distribute the workload. This ensures smooth operation even for complex workflows with numerous tasks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rich Plugin Ecosystem:&lt;/strong&gt; Airflow offers a vast plugin ecosystem. These plugins extend its functionality for various data sources, operators (specific actions within workflows), and integrations with other tools. This allows for customization and tailoring Airflow to fit your specific needs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;DAG Paradigm:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Clear Visualization:&lt;/strong&gt;  Airflow utilizes Directed Acyclic Graphs (DAGs) to define workflows. This allows for a clear visual representation of task dependencies and execution order. This visual approach promotes maintainability and simplifies debugging, especially for intricate pipelines.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Additional Advantages:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Robust Scheduling:&lt;/strong&gt; Airflow provides extensive scheduling capabilities. You can define how often tasks should run using cron expressions, periodic intervals, or custom triggers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring and Alerting:&lt;/strong&gt; Built-in monitoring and alerting functionalities offer visibility into your workflows and send notifications for potential issues or task failures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security:&lt;/strong&gt; Airflow supports role-based access control (RBAC). This allows you to define user permissions and secure access to workflows and resources within your data pipelines.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Open-Source Alternatives to Airflow
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Luigi:&lt;/strong&gt; While offering simplicity and Python-based workflows, Luigi might lack the same level of flexibility and scalability as Airflow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prefect:&lt;/strong&gt; Prefect excels in user-friendliness and visual design, but its in-process execution model might not be suitable for all scenarios compared to Airflow's worker-based approach.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dagster:&lt;/strong&gt; Prioritizing data lineage is valuable, but Dagster might have a steeper learning curve compared to Airflow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Argo Workflows/Kubeflow Pipelines:&lt;/strong&gt; For Kubernetes-centric environments, these tools offer tight integration, but they might not be as general-purpose as Airflow for broader data orchestration needs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Absolutely, here's an example of a simple "Hello World" DAG in Apache Airflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;airflow&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DAG&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;airflow.operators.python_operator&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PythonOperator&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;say_hello&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
  &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Prints a hello world message&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
  &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello World from Airflow!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Define the DAG
&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;DAG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;dag_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;hello_world_dag&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;start_date&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;# Set the start date for the DAG
&lt;/span&gt;    &lt;span class="n"&gt;schedule_interval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# Don't schedule this DAG to run automatically
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;dag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

  &lt;span class="c1"&gt;# Define a Python task to print the message
&lt;/span&gt;  &lt;span class="n"&gt;say_hello_task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PythonOperator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;task_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;say_hello_world&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;python_callable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;say_hello&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;*&lt;em&gt;Explanation for the above simple DAG *&lt;/em&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Import Libraries:&lt;/strong&gt; We import necessary libraries from &lt;code&gt;airflow&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Define Hello World Function:&lt;/strong&gt; The &lt;code&gt;say_hello&lt;/code&gt; function simply prints a message.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create DAG:&lt;/strong&gt; We define a DAG using &lt;code&gt;DAG&lt;/code&gt; object. Here, we specify:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;dag_id&lt;/code&gt;: Unique identifier for the DAG (here, "hello_world_dag").&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;start_date&lt;/code&gt;: The date on which the DAG can first be run (set to today's date).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;schedule_interval&lt;/code&gt;: Set to &lt;code&gt;None&lt;/code&gt; as we don't want this DAG to run automatically (you can trigger it manually in the Airflow UI).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Define Task:&lt;/strong&gt; We define a Python task using &lt;code&gt;PythonOperator&lt;/code&gt;. Here, we specify:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;task_id&lt;/code&gt;: Unique identifier for the task (here, "say_hello_world").&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;python_callable&lt;/code&gt;: The Python function to be executed (here, the &lt;code&gt;say_hello&lt;/code&gt; function).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Running the DAG:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Save this code as a Python file (e.g., &lt;code&gt;hello_world_dag.py&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Place this file in your Airflow DAGs directory (usually &lt;code&gt;$AIRFLOW_HOME/dags&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Go to the Airflow UI (usually at &lt;code&gt;http://localhost:8080&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Find your "hello_world_dag" DAG and click on it.&lt;/li&gt;
&lt;li&gt;You'll see the task "say_hello_world". Click on the play button next to it to run the task manually.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If everything is set up correctly and executes without error, you should see the "Hello World from Airflow!" message printed in the task logs. This is a simple example, but it demonstrates the basic structure of defining a DAG and tasks in Airflow. &lt;/p&gt;

</description>
      <category>airflow</category>
      <category>apache</category>
      <category>beginners</category>
      <category>learning</category>
    </item>
    <item>
      <title>Setting Up a Spark Cluster on Kubernetes Using Helm</title>
      <dc:creator>Saket</dc:creator>
      <pubDate>Fri, 15 Sep 2023 14:36:30 +0000</pubDate>
      <link>https://dev.to/archcode01/setting-up-a-spark-cluster-on-kubernetes-using-helm-30ib</link>
      <guid>https://dev.to/archcode01/setting-up-a-spark-cluster-on-kubernetes-using-helm-30ib</guid>
      <description>&lt;h2&gt;
  
  
  &lt;strong&gt;Introduction&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Apache Spark&lt;/strong&gt; is a powerful distributed data processing engine that can handle large-scale data processing tasks efficiently. Spark is a cluster computing framework which offers a complete solution to many of the common problems like ETL and warehousing, Stream data processing, common use case of supervised and unsupervised learning for data analytics and predictive modelling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kubernetes&lt;/strong&gt;, on the other hand, is a popular container orchestration platform that simplifies the deployment and management of containerized applications. Combining Spark and Kubernetes allows you to harness the benefits of both technologies for running Spark workloads in a scalable and flexible manner. With Kubernetes, the scalability aspect just becomes so easy for Spark, as you just scale the master or worker nodes by running a simple command. In this article, we'll guide you through the process of setting up a Spark cluster on Kubernetes using Helm, a package manager for Kubernetes.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Prerequisites:&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Before proceeding, make sure you have the following prerequisites:&lt;/p&gt;

&lt;p&gt;A Kubernetes cluster: Ensure you have a functioning Kubernetes cluster to deploy your Spark applications.&lt;/p&gt;

&lt;p&gt;Helm installed: Install Helm, the package manager for Kubernetes, on your local machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up the Spark Cluster on Kubernetes:
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Install the Spark Helm Chart:&lt;br&gt;
To deploy Spark on Kubernetes, we'll use the official Spark Helm chart. Open your terminal and add the Spark Helm repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; Customize Configuration (Optional):&lt;br&gt;
You can customize the Spark cluster configuration by creating a values.yaml file. This file allows you to set parameters like the number of master or worker nodes, cpu/memory allocation, Spark version, add additional supporting jars through init containers etc. You can refer to the official Spark Helm chart documentation for available configuration options.&lt;br&gt;
For eg: In values.yaml, you can update the image version to match the spark version according to your need as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
...
image:
  registry: docker.io
  repository: bitnami/spark
  tag: 3.2.0
  digest: ""
...
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; Deploy the Spark Cluster:&lt;br&gt;
With the customization (if any) done, it's time to deploy the Spark cluster using Helm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;helm install spark bitnami/spark -f path/to/your/values.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command will deploy the Spark Master and Worker nodes as specified in the configuration, along with all the required kubernetes components like services, secrets etc required for proper functioning of the spark cluster.&lt;br&gt;
This deploys a stateful set each for master and worker which can be scaled independently as per requirements. &lt;br&gt;
It also deploys a headless service to access master and worker nodes. &lt;br&gt;
It also deploys a regular service to access master and worker nodes.&lt;br&gt;
Other components to capture the help way of deployment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4:&lt;/strong&gt; Monitor the Spark Cluster:&lt;br&gt;
After the deployment is complete, you can monitor the Spark cluster by accessing the Spark Web UI. Find the service IP for the Spark Master by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl get svc spark-master-headless
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, access the Spark Web UI in your browser using the obtained IP and port 8080 (default Spark UI port).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5:&lt;/strong&gt; Submit Spark Applications:&lt;br&gt;
With the Spark cluster up and running, you can now submit your Spark applications for processing. Use the kubectl command we can exec into a pod either master or worker or any other pod which is available in the same namespace as the master and worker nodes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
# Running Spark application on Kubernetes cluster
./bin/spark-submit \
  --class org.apache.spark.examples.SparkPi \
  --master http://spark-master-headless:7077 \
  --deploy-mode cluster \
  --executor-memory 5G \
  --executor-cores 8 \
  /spark-home/examples/jars/spark-examples_versionxx.jar 80

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The spark-job.yaml file contains the specification of your Spark job, including the application jar, main class, input, and output paths, and any required configurations.&lt;/p&gt;

&lt;p&gt;Conclusion:&lt;/p&gt;

&lt;p&gt;Setting up a Spark cluster on Kubernetes using Helm brings together the power of Spark's distributed computing and Kubernetes' container orchestration capabilities. This combination allows you to scale your Spark workloads efficiently and take advantage of Kubernetes' resource management and fault tolerance features. With Helm's ease of use, you can quickly deploy and manage Spark clusters, making it a valuable approach for big data processing in cloud-native environments. &lt;/p&gt;

</description>
    </item>
    <item>
      <title>Software Application Telemetry and Open Telemetry Protocol</title>
      <dc:creator>Saket</dc:creator>
      <pubDate>Mon, 28 Aug 2023 10:57:54 +0000</pubDate>
      <link>https://dev.to/archcode01/software-application-telemetry-and-open-telemetry-protocol-3983</link>
      <guid>https://dev.to/archcode01/software-application-telemetry-and-open-telemetry-protocol-3983</guid>
      <description>&lt;h2&gt;
  
  
  &lt;strong&gt;What does Telemetry mean in software applications?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Telemetry in software applications refers to the collection and analysis of data from software systems. This data can be used to monitor the performance of the system, identify problems, and improve the system's design and implementation. This helps to gain knowledge of whats going inside the application.&lt;br&gt;
Telemetry data can be collected from a variety of sources, like for e.g.:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sensors: Collect data about the physical environment, such as temperature, humidity, and pressure.&lt;/li&gt;
&lt;li&gt;Logs: Collect data about the activities of the software system, such as errors, warnings, and performance metrics.&lt;/li&gt;
&lt;li&gt;Events: Collect data about specific events that occur in the software system, such as user logins, page views, and API calls.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Telemetry data can be analyzed using a variety of tools and techniques, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Statistical analysis: Used to identify trends and patterns in the data.&lt;/li&gt;
&lt;li&gt;Machine learning: Used to build models that can predict future behavior based on historical data.&lt;/li&gt;
&lt;li&gt;Visualization: Used to make the data easier to understand and interpret.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Telemetry data can be used to improve software applications in a variety of ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Monitoring the performance of the system: Monitor CPU usage, memory usage, and network traffic to identify problems and take corrective action.&lt;/li&gt;
&lt;li&gt;Identifying problems: Identify problems with the software system, such as errors, warnings, and performance bottlenecks. This data can be used to fix the problems and improve the system's reliability.&lt;/li&gt;
&lt;li&gt;Improving the system's design and implementation: Telemetry data can be to identify areas where the system can be improved, such as the performance, scalability, and security.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Telemetry is a powerful tool which helps to collect and analyse data which helps gain insights into the behaviour of applications and improve their overall quality.&lt;/p&gt;

&lt;p&gt;**&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Open Telemetry Protocol?
&lt;/h2&gt;

&lt;p&gt;**&lt;br&gt;
&lt;strong&gt;OpenTelemetry Protocol (OTLP)&lt;/strong&gt; is a standard way to collect and export telemetry data from software systems. It is a general-purpose protocol that can be used to collect data from a variety of sources, including applications, services, and infrastructure.&lt;br&gt;
OTLP is based on the Protocol Buffers language, which is a language-neutral, efficient way to serialize structured data. This makes it easy to transport telemetry data between different systems.&lt;br&gt;
OTLP defines two main types of data: traces and metrics. Traces represent the execution of a single request or transaction, while metrics represent measurements of the state of a system.&lt;br&gt;
OTLP can be used to collect telemetry data from a variety of sources, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Applications: OTLP can be used to instrument applications to collect data about their execution, such as the time it takes to respond to requests.&lt;/li&gt;
&lt;li&gt;Services: OTLP can be used to collect data about the performance of services, such as the number of requests they are handling and the amount of time they are taking to respond.&lt;/li&gt;
&lt;li&gt;Infrastructure: OTLP can be used to collect data about the performance of infrastructure components, such as servers, networks, and databases.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once telemetry data has been collected using OTLP, it can be exported to a variety of backends, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Observability platforms: Observability platforms, such as Prometheus and Grafana, can be used to visualize and analyze telemetry data.&lt;/li&gt;
&lt;li&gt;Logging systems: Logging systems, such as ELK and Splunk, can be used to store and search telemetry data.&lt;/li&gt;
&lt;li&gt;Data warehouses: Data warehouses, such as Snowflake and BigQuery, can be used to store telemetry data for long-term analysis.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;OTLP&lt;/strong&gt; is a powerful and versatile protocol that can be used to collect and export telemetry data from a variety of sources and in a variety of environments.&lt;/p&gt;

&lt;p&gt;Here are some of the benefits of using OTLP:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It is a standard protocol, so it can be used to collect data from a variety of systems.&lt;/li&gt;
&lt;li&gt;It is based on Protocol Buffers, which is a language-neutral, efficient way to serialize data.&lt;/li&gt;
&lt;li&gt;It is easy to use and implement.&lt;/li&gt;
&lt;li&gt;It is supported by a wide range of tools and platforms.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>telemetry</category>
      <category>otpl</category>
    </item>
    <item>
      <title>Kubernetes - Monitor custom application metrics using Prometheus and Istio.</title>
      <dc:creator>Saket</dc:creator>
      <pubDate>Thu, 10 Aug 2023 14:32:19 +0000</pubDate>
      <link>https://dev.to/archcode01/kubernetes-monitor-custom-application-metrics-using-prometheus-and-istio-1081</link>
      <guid>https://dev.to/archcode01/kubernetes-monitor-custom-application-metrics-using-prometheus-and-istio-1081</guid>
      <description>&lt;p&gt;Kubernetes pod level metrics are captured by out of the box combination of Istio, Prometheus and Grafana components. In order to get your application metrics (e.g.: jvm metrics, custom counter metrics etc) onto Grafana, we can configure the existing setup in such a way that Istio will capture the metrics from pod and applications and merge them before sending to prometheus.&lt;/p&gt;

&lt;p&gt;In order to do this, we need to have the application expose its metrics on some endpoint  (eg: springboot provides out of the box facility to expose jvm/app metrics on actuator/prometheus api. )&lt;/p&gt;

&lt;p&gt;Once this is setup, we can configure the deployment yaml to add annotations to the pod which help istio to scrape the metrics from the application, merge them with pod metrics and send to prometheus.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;annotations:&lt;br&gt;
        prometheus.io/path: "/actuator/prometheus"&lt;br&gt;
        prometheus.io/port: "80"&lt;br&gt;
        prometheus.io/scheme: "http"&lt;br&gt;
        prometheus.io/scrape: "true"&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
With this setup, the application metrics will be scraped from the pod and be available along with other metrics on Grafana dashboard.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Janusgraph OLAP Traversal not working with Cassandra backend with client SSL enabled.</title>
      <dc:creator>Saket</dc:creator>
      <pubDate>Fri, 23 Jun 2023 17:14:30 +0000</pubDate>
      <link>https://dev.to/archcode01/janusgraph-olap-traversal-not-working-with-cassandra-backend-with-client-ssl-enabled-3c4f</link>
      <guid>https://dev.to/archcode01/janusgraph-olap-traversal-not-working-with-cassandra-backend-with-client-ssl-enabled-3c4f</guid>
      <description>&lt;h2&gt;
  
  
  What is olap traversal in graph database?
&lt;/h2&gt;

&lt;p&gt;OLAP stands for OnLine Analytical Processing, is one of the ways to traverse graph database parallelly in batch operations.&lt;br&gt;
Janusgraph OLAP Traversal makes use of distributed graph processing by leveraging gremlin plugin for Apache Hadoop and Apache Spark.&lt;br&gt;
For more information on this topic please refer to below links:&lt;br&gt;
&lt;a href="https://docs.janusgraph.org/advanced-topics/hadoop/"&gt;JanusGraph with TinkerPop’s Hadoop-Gremlin - JanusGraph&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;We had a working setup of Janusgraph with version 0.5.2 where we were able to insert and query (OLTP) the data as per need. We were exploring JanusGraph OLAP traversal for some reporting and analytical requirements. However when we tried to follow the instructions provided on the JanusGraph documentation, we were not able connect to Cassandra with SSL enabled, when traversing the graph in OLAP mode through Gremlin queries. Cassandra database was setup on SSL connection with a Truststore expected with client connection requests. OLTP Queries or the regular way of working with the queries was working fine and inline with the official documentation available.&lt;/p&gt;

&lt;p&gt;Below is config for OLTP which works janusgraph-cql-oltp.properties:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gremlin.graph=org.janusgraph.core.JanusGraphFactory
storage.backend=cql
storage.hostname=cassandra.cassandra.svc.cluster.local
storage.username=cassandra
storage.password=cassandra123
storage.cql.keyspace=janusgraph
cache.db-cache = true
cache.db-cache-clean-wait = 20
cache.db-cache-time = 180000
cache.db-cache-size = 0.5
storage.lock.wait-time = 60000
storage.cql.ssl.enabled=true
storage.cql.ssl.truststore.location=/etc/config/tls/truststore
storage.cql.ssl.truststore.password=secretpasswd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we load this line in gremlin console to connect and traverse a simple query we were able to fetch the expected results.&lt;/p&gt;

&lt;p&gt;Below is the config for OLAP which is showing error for connection to Cassandra with ssl enabled:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gremlin.graph=org.apache.tinkerpop.gremlin.hadoop.structure.HadoopGraph
gremlin.hadoop.graphReader=org.janusgraph.hadoop.formats.cql.CqlInputFormat
gremlin.hadoop.graphWriter=org.apache.hadoop.mapreduce.lib.output.NullOutputFormat

gremlin.hadoop.jarsInDistributedCache=true
gremlin.hadoop.inputLocation=none
gremlin.hadoop.outputLocation=output
gremlin.spark.persistContext=true
# # JanusGraph Cassandra InputFormat configuration
# # These properties defines the connection properties which were used while write data to JanusGraph.
janusgraphmr.ioformat.conf.storage.backend=cql
# This specifies the hostname &amp;amp; port for Cassandra data store.
janusgraphmr.ioformat.conf.storage.hostname=cassandra.cassandra.svc.cluster.local
janusgraphmr.ioformat.conf.storage.port=9042
janusgraphmr.ioformat.conf.storage.username=cassandra
janusgraphmr.ioformat.conf.storage.password=cassandra123
janusgraphmr.ioformat.conf.storage.cql.keyspace=janusgraph
janusgraphmr.ioformat.conf.storage.lock.wait-time = 60000
janusgraphmr.ioformat.conf.storage.cql.ssl.enabled=true
janusgraphmr.ioformat.conf.storage.cql.ssl.truststore.location=/etc/config/tls/truststore
janusgraphmr.ioformat.conf.storage.cql.ssl.truststore.password=cassandra123

janusgraphmr.ioformat.conf.storage.ssl.enabled=true
janusgraphmr.ioformat.conf.storage.ssl.truststore.location=/etc/config/tls/truststore
janusgraphmr.ioformat.conf.storage.ssl.truststore.password=cassandra123

janusgraphmr.ioformat.conf.storage.cql.read-consistency-level=ONE

storage.lock.wait-time = 60000
storage.cql.ssl.enabled=true
storage.cql.ssl.client-authentication-enabled=true
storage.cql.ssl.truststore.location=/etc/config/tls/truststore
storage.cql.ssl.truststore.password=cassandra123

janusgraphmr.ioformat.conf.cache.db-cache = true
janusgraphmr.ioformat.conf.cache.db-cache-clean-wait = 20
janusgraphmr.ioformat.conf.cache.db-cache-time = 180000
janusgraphmr.ioformat.conf.cache.db-cache-size = 0.5

cassandra.input.partitioner.class=org.apache.cassandra.dht.Murmur3Partitioner
cassandra.input.widerows=true

# # SparkGraphComputer Configuration #
spark.master=local[*]
spark.executor.memory=1g
spark.serializer=org.apache.spark.serializer.KryoSerializer
spark.kryo.registrator=org.janusgraph.hadoop.serialize.JanusGraphKryoRegistrator


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we load the graph object in gremlin console, we can see properties are loaded correctly. But when we traverse the graph as mentioned in the documentation, we get cassandra connection error related to ssl config.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gremlin&amp;gt; graph=HadoopGraph.open('/janusgraph-full-0.5.2/conf/olap.properties')
==&amp;gt;hadoopgraph[cqlinputformat-&amp;gt;nulloutputformat]
gremlin&amp;gt; g=graph.traversal().withComputer(SparkGraphComputer)
==&amp;gt;graphtraversalsource[hadoopgraph[cqlinputformat-&amp;gt;nulloutputformat], sparkgraphcomputer]
gremlin&amp;gt; graph.configuration()
//// i can see all the properties from the file loaded here
gremlin&amp;gt; g.V().limit(1)
07:34:44 WARN  org.apache.tinkerpop.gremlin.spark.process.computer.SparkGraphComputer  - class org.apache.hadoop.mapreduce.lib.output.NullOutputFormat does not implement PersistResultGraphAware and thus, persistence options are unknown -- assuming all options are possible
com.datastax.driver.core.exceptions.NoHostAvailableException: All host(s) tried for query failed (tried: cassandra.cassandra.svc.cluster.local/10.0.165.158:9042 (com.datastax.driver.core.exceptions.TransportException: [cassandra.cassandra.svc.cluster.local/10.0.165.158:9042] Connection has been closed))
Type ':help' or ':h' for help.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We could verify from cassandra logs that a connection was attempted but request was rejected for ssl reasons. Below are the logs from cassandra instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INFO  [epollEventLoopGroup-2-4] 2023-05-02 07:34:58,809 Message.java:826 - Unexpected exception during request; channel = [id: 0xeb0e017f, L:/10.12.0.224:9042 ! R:/10.12.0.135:60316]
io.netty.handler.ssl.NotSslRecordException: not an SSL/TLS record: 0400000001000000500003000b43514c5f56455253494f4e0005332e302e30000e4452495645525f56455253494f4e0005332e392e30000b4452495645525f4e414d4500144461746153746178204a61766120447269766572
        at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1057) ~[netty-all-4.0.44.Final.jar:4.0.44.Final]
        at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:411) [netty-all-4.0.44.Final.jar:4.0.44.Final]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Finally found the missing piece
&lt;/h2&gt;

&lt;p&gt;After trying several combinations to pass the ssl info the connection configuration, we were still not able to establish connection with Cassandra and successfully execute an OLAP query.&lt;br&gt;
We posted this as a question on &lt;a href="https://stackoverflow.com/questions/76152740/janusgraph-olap-traversal-connection-with-cassandra-using-trusstore-config-not"&gt;stackoverflow&lt;/a&gt;, discord channel and google groups hoping to receive some help from community. Finally got a response from the discord community member and it worked out. The discord channel for Janusgraph and Gremlin users is quite active. The configuration parameters which were needed to be populated for ssl connection were not mentioned in the documentation. They are there in the code and below is the reference. These however work with latest versions of Janusgraph and we verified this with 0.6.0 and 1.0.0-rc2 versions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--P-rzn_V3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7fsbwqnhv92smewtz63i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--P-rzn_V3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7fsbwqnhv92smewtz63i.png" alt="Image description" width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The OLAP connection configuration was updated with below mentioned entries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cassandra.input.native.ssl.trust.store.password=cassandra123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally the updated OLAP traversal configuration looks like below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; gremlin.graph=org.apache.tinkerpop.gremlin.hadoop.structure.HadoopGraph
    gremlin.hadoop.graphReader=org.janusgraph.hadoop.formats.cql.CqlInputFormat
    gremlin.hadoop.graphWriter=org.apache.hadoop.mapreduce.lib.output.NullOutputFormat
    gremlin.hadoop.jarsInDistributedCache=true
    gremlin.hadoop.inputLocation=none
    gremlin.hadoop.outputLocation=output
    gremlin.spark.persistContext=true
    janusgraphmr.ioformat.conf.storage.backend=cql
    janusgraphmr.ioformat.conf.storage.hostname=cassandra-headless.cassandra.svc.cluster.local
    janusgraphmr.ioformat.conf.storage.port=9042
    janusgraphmr.ioformat.conf.storage.username=cassandra
    janusgraphmr.ioformat.conf.storage.password=cassa@2@2!
    janusgraphmr.ioformat.conf.storage.cql.keyspace=janusgraph
    janusgraphmr.ioformat.conf.storage.cql.read-consistency-level=ONE
    janusgraphmr.ioformat.conf.storage.cql.ssl.enabled=true
    janusgraphmr.ioformat.conf.storage.cql.ssl.truststore.location=/tmp/security/truststore
    janusgraphmr.ioformat.conf.storage.cql.ssl.truststore.password=cassandra123
    storage.cql.read-consistency-level=ONE
    janusgraphmr.ioformat.conf.cache.db-cache = true
    janusgraphmr.ioformat.conf.cache.db-cache-clean-wait = 20
    janusgraphmr.ioformat.conf.cache.db-cache-time = 180000
    janusgraphmr.ioformat.conf.cache.db-cache-size = 0.5
    cassandra.input.partitioner.class=org.apache.cassandra.dht.Murmur3Partitioner
    cassandra.input.native.keep.alive=true
    cassandra.input.native.ssl.trust.store.path=/tmp/security/truststore
    cassandra.input.native.ssl.trust.store.password=cassa@2@2!
    storage.cql.protocol-version=V4 
    spark.master=local[*]
    spark.executor.memory=3g
    spark.serializer=org.apache.spark.serializer.KryoSerializer
    spark.kryo.registrator=org.janusgraph.hadoop.serialize.JanusGraphKryoRegistrator
    spark.cassandra.input.fetch.size_in_rows=500

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the above configuration we were able to traverse the graph using OLAP traversal and achieve our objective.&lt;/p&gt;

</description>
      <category>graphdb</category>
      <category>olap</category>
      <category>cassandra</category>
      <category>ssl</category>
    </item>
  </channel>
</rss>
