Database Design

191 posts

datadog3 min readCurated summary

Squeezing every millisecond: How we rebuilt the Datadog Lambda Extension in Rust

Datadog rewrote its AWS Lambda extension from Go into Rust to overcome the performance limits of adapting its large, host-oriented Datadog Agent to Lambda’s constrained environment. The redesign reduced cold-start latency by 82%, memory usage by 40%, and binary size from 55 MB to 7 MB. The project succeeded by narrowing the problem, enforcing performance budgets from the beginning, and designing specifically for Lambda’s execution model. ## Why the Original Extension Needed to Change - The Lambda extension runs as a sidecar process, collecting logs, metrics, traces, profiles, and process data asynchronously. - It was originally based on the Datadog Agent, which is designed for hosts, containers, and clusters. - The Agent’s fairness, buffering, caching, and high-throughput features introduced unnecessary overhead in Lambda. - Optimization attempts included: - Removing dependencies with build tags - Compressing binaries with UPX - Eliminating unnecessary `init` methods - Exploring Go plugins for lazy loading - These changes could not reduce additional cold-start latency below roughly 450–500 milliseconds. ## Why a Rewrite—and Why Rust - Rewrites are risky because they can lose undocumented invariants, reproduce subtle bugs, and create the burden of supporting two systems. - The team concluded that Lambda represented a fundamentally different scale and workload from the general-purpose Datadog Agent. - Rust was well suited because: - Memory safety reduces the risk of crashes and data races. - Extension crashes also terminate the Lambda function and trigger another cold start. - Rust produces small binaries with limited runtime overhead. - Lambda targets a narrow platform set: Amazon Linux on x86 and Arm. - Compile-time concurrency guarantees support reliable multithreaded code. - A hackathon prototype demonstrated enough potential to begin the full rewrite, named Project Bottlecap. ## Project Bottlecap’s Design Constraints - The extension had to minimize interference with the function handler, especially because many Lambda functions serve latency-sensitive APIs. - Telemetry work should occur after the handler returns whenever possible. - The team also minimized post-runtime duration—the CPU time added after normal function execution. - Performance was monitored from the start: - Dashboards and alerts tracked cold-start overhead. - Every pull request was benchmarked. - Regressions were investigated before merging. - The team accepted targeted tradeoffs for speed, including manually implementing AWS API calls and request signing instead of using SDKs that added too much overhead. - The design emphasized optionality because Lambda workloads range from small API functions to large asynchronous batch jobs. - Planned flush strategies included: - Flushing at the end of an invocation for infrequently called or CPU-constrained functions - Periodic or in-invocation flushing for workloads needing different latency and resource tradeoffs The practical lesson is that software optimized for large, long-running systems may be fundamentally unsuitable for serverless runtimes. When optimization reaches a hard performance floor, a focused rewrite—constrained by the target environment and measured continuously—can deliver major gains.

Read original(opens in new tab)
datadog3 min readCurated summary

Achieving relentless Kafka reliability at scale with the Streaming Platform

Datadog built a Streaming Platform to make Kafka resilient and dynamically manageable across hundreds of clusters, thousands of topics, and millions of partitions. By decoupling applications from physical Kafka resources, the platform can reroute traffic, rebalance workloads, and fail over in seconds without redeployments. Its design trades strict processing order for parallelism and durability, while adding mechanisms to prevent poisoned events and backlogs from blocking entire partitions. ## A Fluid Control Plane for Multi-Cluster Orchestration - Kafka infrastructure is treated as interchangeable commodity hardware rather than a fixed application dependency. - Workloads can shift between clusters, availability zones, and Kubernetes environments automatically. - The control plane continuously replaces unhealthy components and maintains uninterrupted data flow. ### Resilient Pipelines with Streams - Streams abstract away Kafka topics and clusters behind stable identifiers. - A single Stream can span multiple Kafka clusters, availability zones, and Kubernetes clusters. - Producers and consumers do not need to know the underlying Kafka topology. - Infrastructure can be reconfigured in real time without application changes or redeployments. ### Live Traffic Failovers - When a cluster becomes unhealthy, the platform creates a replacement topic and redirects new traffic to it. - Consumers drain the backlog from the original topic before transitioning to the replacement. - The same mechanism supports proactive traffic redistribution, cluster decommissioning, capacity changes, and partition adjustments. - Operations that traditionally take hours can be completed in seconds. ### Consumer Semantics for Scale and Durability - Datadog uses at-least-once delivery rather than strict processing order. - Events can be processed independently and in parallel. - Ordering is restored later in the event store. - This trade-off enables efficient processing of petabytes of data across distributed infrastructure. ### The Assigner Coordinator - Kafka’s default group coordinator depends on session timeouts, making failover detection take tens of seconds or minutes. - Datadog’s Assigner monitors cluster health, resource usage, and workload distribution continuously. - It reacts in seconds to failures, traffic spikes, and capacity changes. - Workloads are balanced using real metrics such as CPU load and available resources, allowing heterogeneous environments to be used efficiently. ## Preventing Head-of-Line Blocking Kafka’s strict per-partition ordering means a single unprocessable event can block all later events. Datadog addresses this reliability problem through independent Stream lanes and a more flexible commit log. ### Stream Lanes and Quality of Service - Streams contain separate lanes for different priority levels and traffic requirements. - High-priority real-time traffic can be isolated from slower bursts or late-arriving data. - A dedicated dead-letter queue lane receives poison pills that cannot be processed. - Consumers can commit progress after moving failed events to the DLQ, preventing one bad event from blocking the partition without losing data. ### Advanced Commit Logging - Kafka traditionally maintains one committed pointer per partition. - That model prevents consumers from advancing past older events that are delayed or still being processed. - Datadog uses Kafka’s commit metadata to record multiple offsets or offset ranges simultaneously. - This allows consumers to continue processing live traffic while older events are handled separately, reducing backlog-related blocking. ## Overall Design Philosophy - The Streaming Platform combines stable logical Streams, real-time orchestration, flexible consumer semantics, QoS isolation, and enhanced offset tracking. - Together, these components make Kafka more self-healing and suitable for Datadog’s extreme scale. - The approach prioritizes uninterrupted processing, rapid recovery, and operational flexibility over Kafka’s default assumptions of fixed topology and strict ordering. Datadog’s design demonstrates that Kafka at very large scale requires a control layer around the broker infrastructure. Abstracting resources, automating failovers, and allowing independent progress through partitions can provide substantially better reliability and throughput than relying solely on Kafka’s native coordination model.

Read original(opens in new tab)
datadog3 min readCurated summary

Husky: Efficient compaction at Datadog scale

Husky is a distributed event store built on object storage for observability workloads reaching trillions of events per day. Because data is written continuously, rarely updated, and queried both recently and historically, its storage layer must minimize object-store fetches while still supporting high query parallelism. The central design challenge is choosing a compaction and layout strategy that keeps fragments manageable without sacrificing query speed. ## Husky’s Query Execution Model - Ingested events are grouped into files called **fragments** and stored in systems such as Amazon S3, Google Cloud Storage, or Azure Blob Storage. - Metadata for each fragment is stored separately in FoundationDB. - For each query: - Metadata is scanned to identify relevant fragments. - Fragments are distributed among query workers. - Workers scan their assigned data. - Results are merged. - Query cost depends mainly on: - The number of fragments fetched from object storage. - The number of events scanned within those fragments. - Husky therefore focuses on: - Reducing the total number of files through efficient compaction. - Organizing data so queries scan as few irrelevant events as possible. ## The Compaction “Goldilocks” Problem - Compaction combines many small fragments into a larger fragment containing the same data. - FoundationDB transactions atomically replace the old fragments with the compacted one, ensuring queries see a consistent state either before or after compaction. - Ingestion writers buffer events per tenant to avoid producing extremely small files, but they flush periodically to keep newly ingested data queryable quickly. - These flushed fragments may contain only a few thousand events, making queries inefficient when they must fetch thousands of objects and metadata records. ## Balancing Fragment Size Husky must find a fragment size that balances several competing concerns: - **Object storage and metadata overhead** - Fewer, larger fragments reduce the number of fetches and metadata entries. - **Compaction cost** - Larger or more aggressively reorganized fragments require more CPU and more object-storage GET and PUT operations. - **Query parallelism** - Smaller fragments allow more workers to operate concurrently. - Larger fragments reduce distribution overhead but can limit parallelism for large analytical queries. - **Scan efficiency** - Query workers use vectorized execution, which is most effective when scanning sufficiently large batches of rows. - **Data locality and compression** - Compaction can place events with similar timestamps or tags near one another. - This improves compression and allows queries to skip irrelevant data, but requires additional processing and query-pattern analysis. Fragments that are too small create excessive fetch and scheduling overhead. Fragments that are too large reduce parallelism and can make broad queries slower. The goal is a “just right” size suited to typical query patterns. ## Storage Layout and Query Selectivity - Husky organizes events along both: - The time dimension. - Spatial dimensions such as tags. - Keeping commonly queried events close together reduces the amount of data that must be scanned. - Similar data also compresses more effectively. - Achieving this layout increases compaction work, creating a tradeoff between lower query cost and lower maintenance cost. ## Scalable Compaction - Husky’s storage system depends on compaction being efficient enough to run continuously at very large scale. - The design must account not only for the final fragment size, but also for the CPU and object-storage costs required to produce it. - Atomic metadata updates ensure that compaction can occur without exposing partial results or inconsistent table states. Husky’s approach treats compaction as a core part of query performance rather than simple file maintenance. A practical design must tune fragment sizes, merge frequency, and data layout together to minimize total system cost while preserving fast access to both recent events and large historical datasets.

Read original(opens in new tab)
datadog2 min readCurated summary

Unraveling a Postgres segfault that uncovered an Arm64 JIT compiler bug

Postgres was crashing with segmentation faults when executing certain expensive queries on an Arm64 Kubernetes cluster. Investigators reduced the failure to a simple table scan and discovered that disabling JIT compilation prevented the crash. Assembly-level debugging ultimately traced the problem to a bug in LLVM’s Arm64 JIT support. ## Isolating the Crash - The failures occurred across multiple EC2 nodes, ruling out faulty hardware. - Query logs showed that the crashes consistently followed a small number of query patterns. - The simplest reproducer was: ```sql SELECT repo_id FROM repository; ``` - Core dumps had badly corrupted stacks, but surviving frames pointed to `ExecRunCompiledExpr`, suggesting a failure during JIT execution. - The unusually short backtraces reinforced the suspicion that the stack itself had been corrupted. ## How PostgreSQL JIT Works - PostgreSQL normally evaluates SQL expressions through a general-purpose interpreter. - JIT compilation converts expressions such as `1+1` into native machine code, reducing interpreter overhead for large workloads. - JIT can also optimize tuple deforming by converting disk tuples into in-memory values more efficiently. - PostgreSQL uses LLVM to generate the compiled code. - Because compilation adds overhead and compiled functions are not reused between queries, PostgreSQL enables JIT primarily for expensive queries based on cost thresholds. ## The Query of Death - The affected query scanned a partitioned `repository` table with: - 64 partitions - More than 1.6 million rows - 128 JIT-generated functions - Its query plan enabled expression compilation and tuple deforming: ```text JIT: Functions: 128 Expressions: true Deforming: true Inlining: false Optimization: false ``` - Running the query with: ```sql SET jit = off; ``` completed successfully. - Disabling JIT cluster-wide immediately stopped the crashes without noticeable query-latency effects. ## Root Cause Direction - The release build of PostgreSQL offered limited debugging flexibility, so the team planned to reproduce the failure in a dedicated test environment. - Further investigation eventually isolated the defect to JIT compilation on Arm64 systems. - The underlying issue was identified as an LLVM bug rather than a PostgreSQL query or hardware problem. - The investigation continued down to generated assembly and resulted in an upstream fix. The immediate mitigation was to disable PostgreSQL JIT, while the durable solution was to adopt the LLVM fix addressing the Arm64 code-generation bug.

Read original(opens in new tab)
datadog3 min readCurated summary

Timeseries indexing at scale

Datadog’s metrics volume grew 30× from 2017 to 2022, while customers began running increasingly complex queries. This growth exposed limitations in the Timeseries Index service, whose original indexing approach became a performance and maintenance bottleneck. The post introduces Datadog’s metrics architecture and explains how its indexing strategy evolved to handle large-scale workloads more reliably. ## Metrics Platform Architecture - **Intake** - Datadog Agents send data points through a load balancer to metrics intake. - Each point contains a metric name, timestamp, numerical value, and optional tags. - Tags such as `env`, `host`, and `service` provide dimensions for filtering, aggregation, and comparison. - Data is written to Kafka, allowing multiple consumers to process it for storage, indexing, analysis, and archiving. - **Storage** - The short-term storage layer has two services: - The Timeseries Database stores tuples of `<timeseries_id, timestamp, float64>`. - The Timeseries Index stores `<timeseries_id, tags>` mappings. - The custom Timeseries Index database is built on RocksDB and supports filtering and grouping during queries. - **Query Processing** - The distributed query layer contacts index nodes, retrieves intermediate results from the timeseries database, and combines them. - Filters such as `env:prod AND service:event-consumer` restrict results to matching data points. - Grouping by tags, such as `service`, produces separate timeseries for each group. - Aggregators such as `avg` combine values within each group. ## Why Timeseries Indexing Matters - Indexes prevent queries from scanning every timeseries associated with a metric, much like database indexes avoid full table scans. - Poorly designed or insufficient indexes can make queries slow and consume excessive CPU and memory. - As Datadog’s data volume and query complexity increased, the indexing system became a critical scalability concern. ## Automatically Generated Indexes - The original system generated indexes from live query behavior. - Slow or resource-intensive queries were recorded in a query log and analyzed periodically. - Index selection considered: - Query frequency - Execution time - Number of input timeseries identifiers scanned - Number of output identifiers returned - Highly selective queries—with a high input-to-output ratio—received indexes. - Obsolete indexes that no longer received queries were removed. - These indexes acted as materialized views, replacing expensive scans with efficient key-value lookups. ## Original Indexing Service Design - The service was written in Go and used embedded SQLite and RocksDB databases. - SQLite stored metadata, including: - Index definitions - Query logs - Query counts and timestamps - Input and output cardinalities - Query durations - Index definitions were read frequently, updated rarely, and cached entirely in memory. - Query logs were bulk-written in the background, keeping them out of the ingestion and query paths. - SQLite’s SQL interface made the metadata easy to inspect and modify manually. - RocksDB handled the high-volume write workload required to index trillions of events per day. Datadog’s experience shows that indexing strategies that work at smaller scale can become bottlenecks as data volume and query sophistication grow. Effective timeseries systems therefore need adaptive indexing, careful separation of query and ingestion workloads, and storage technologies suited to extremely high write rates.

Read original(opens in new tab)
datadog3 min readCurated summary

How we migrated our static analyzer from Java to Rust

Datadog migrated its static analyzer from Java to Rust after finding that ANTLR-based parsing was too slow and language support was incomplete. Rust’s strong integration with Tree-sitter enabled broader language coverage, faster scans, and lower memory usage. The migration preserved behavioral parity while tripling performance and reducing memory consumption tenfold. ## Why Performance Became a Priority - Datadog runs analysis directly in customers’ CI environments, often on resource-constrained runners. - On a two-core, 7 GB GitHub Actions runner, medium repositories took about five minutes to scan instead of the target of under three minutes. - Codiga’s previous hosted environment used large, tuned servers, which masked some performance problems. - Java also required customers to use JVM 17+, potentially conflicting with JVM versions already installed in their CI environments. - Improving Java offered limited upside, so the team considered a rewrite despite its cost and risk. ## Static Analyzer Architecture - The analyzer consists primarily of: - A parsing layer that builds an abstract syntax tree (AST). - An execution layer that analyzes the AST, reports violations, and offers fixes. - Tree-sitter generates the AST. - The existing Java binding lacked important functionality, including Tree-sitter pattern matching. - Tree-sitter’s core libraries are implemented in Rust, where support was more complete. - Analysis rules are written in JavaScript and were originally executed through GraalVM’s polyglot capabilities. - Fast parsing, pattern matching, and rule execution were central to meeting the desired CI performance. ## Migrating from Java to Rust - Rust was selected because it is a first-class part of the Tree-sitter ecosystem and provided better access to its features. - The migration required: - Feature parity with the Java implementation. - Identical analysis results and reported violations. - No execution-time regressions. - Migrating the parser was relatively straightforward because Rust support came directly from Tree-sitter. - The Rust implementation: - Tripled analyzer performance. - Reduced memory usage by a factor of ten. - JavaScript execution moved from GraalVM to `deno-core`, a Rust-based V8 integration. - Only the core JavaScript functionality was included. - Disk and network capabilities were excluded because analysis rules do not need them, improving security. ## Migration Strategy and Rust Adoption - The team treated automated equivalence and performance tests as requirements for a successful rewrite. - Rust allowed the analyzer to integrate more directly with its key dependencies rather than maintaining a separate Java binding. - The broader migration also required replacing supporting Java components with corresponding Rust libraries; the article indicates that these mappings were documented as part of the transition. Overall, the move to Rust was justified by the analyzer’s deployment model: faster execution and lower resource consumption directly improved the experience of customers running scans in constrained CI environments.

Read original(opens in new tab)
datadog3 min readCurated summary

Engineering VP spotlight: Ivo Dimitrov

Ivo Dimitrov’s career evolved from low-level systems programming into engineering leadership focused on large-scale distributed storage. His experience at Microsoft and LinkedIn shaped his approach to building scalable data platforms, while Datadog attracted him with its talented people, modern technology, and culture of experimentation. Today, he leads Datadog’s Distributed Data Systems organization, supporting the company’s metrics, events, query, alerting, and analytics infrastructure. ## From Electrical Engineering to Systems Programming - Dimitrov initially studied electrical engineering and became interested in software while working on digital control systems. - His early work included contributing to a real-time operating system kernel. - He spent roughly a decade as an individual contributor, specializing in: - High-performance systems - Low-level programming - C and C++ - System software ## Transition from Individual Contributor to Manager - At Microsoft, Dimitrov worked on an early version of Azure Blob Storage. - Following a reorganization, he accepted an opportunity to lead his team despite having no prior management experience. - Microsoft supported the transition through: - Leadership mentorship - Formal management training - Guidance on communication, conflict resolution, and interpersonal leadership - He discovered that management allowed him to expand his ownership beyond individual projects and influence broader organizational outcomes. - The role combined his technical background with responsibilities such as cross-functional coordination, team development, and engineering strategy. ## Building Internet-Scale Storage at Microsoft and LinkedIn - At Microsoft, Dimitrov worked on storage systems supporting Hotmail. - After joining LinkedIn in 2014, he adapted to a technology environment centered on open source tools such as MySQL and Java. - He led development of Espresso, LinkedIn’s proprietary key-value storage platform. - The platform matured into a core system supporting approximately 95 percent of LinkedIn’s data sets. - He also helped oversee several other large-scale storage projects: - **Venice**, an open source platform for serving derived data - **Ambry**, an open source blob storage system - **Helix**, an open source cluster manager - These systems supported critical parts of LinkedIn’s internet-scale infrastructure. ## Why Datadog Was Appealing - Dimitrov was drawn to Datadog by three main factors: - Highly capable engineers and leaders - Interesting, modern technology - The opportunity to contribute to a rapidly growing company - Compared with the legacy systems and processes that had accumulated at LinkedIn, Datadog offered less bureaucracy and more freedom to: - Take thoughtful risks - Experiment - Deliver quickly - Fail fast and learn - Iterate and innovate - He was particularly interested in Datadog’s Kubernetes-based Metrics and Events platforms and the challenge of building best-in-class infrastructure during the company’s growth. ## Distributed Data Systems at Datadog - Dimitrov leads the Distributed Data Systems organization, which owns a portfolio of storage and data technologies. - Its responsibilities include: - **Metrics**, supporting metrics and time-series data - **Events**, handling semi-structured data such as logs, profiles, and traces - **Driveline**, a main-memory database optimized for online analytics - The **Cross-Platform Queries** team provides a unified query interface across systems that historically exposed separate, domain-specific APIs. - This reduces the learning curve for engineers and customers. - It abstracts the underlying data stores behind a common API. - The organization also operates Datadog’s Alerts platform, which generates a large share of the queries sent to the Metrics and Events systems. Dimitrov’s experience demonstrates how deep systems expertise can translate into effective engineering leadership. His recommendation by example is to remain technically engaged while expanding one’s scope—from writing individual components to shaping teams, platforms, and long-term engineering direction.

Read original(opens in new tab)
datadog3 min readCurated summary

How we built the Datadog heatmap to visualize distributions over time at arbitrary scale

Datadog uses DDSketch-powered distribution metrics and heatmaps to reveal performance patterns that percentile lines can hide. Heatmaps preserve the full shape of latency distributions across hosts and time, making distinct behavioral modes, seasonality, and outliers visible. The visualization is designed to remain scalable and readable even with hundreds of trillions of underlying datapoints. ## Why Unaggregated Distributions Matter - Line graphs reduce billions of events to a single value, such as p50, p99, or max. - Multiple percentile lines provide more context, but the selected percentiles remain arbitrary and can obscure important behavior. - Aggregated percentile changes may suggest that all requests are slowing when only one subset of traffic is changing. - Heatmaps expose separate “modes”—distinct groups of measurements with different behavior. - For example, periodic latency spikes may come from a low-latency benchmarking service rather than from a general degradation in the endpoint. - Filtering out an identified mode can reveal other patterns, such as daily seasonality in the remaining traffic. ## Building Heatmaps with DDSketch - DDSketch sacrifices a small amount of precision to represent extremely large numbers of observations efficiently. - Datadog sends histogram bins and counts to the frontend instead of transmitting every individual datapoint. - Limiting the number of bins keeps the payload size constant as traffic volume grows. - Counts use `float32`, supporting values up to approximately `3 × 10^38` per bin—far beyond practical monitoring volumes. - This allows heatmaps to represent massive datasets, including hundreds of trillions of datapoints. ## Preserving Resolution and Avoiding Aliasing - Heatmap requests contain time buckets, distribution bins, and counts. - Since bucket boundaries are shared across a request, Datadog stores those boundaries only once. - Boundaries must be explicit because distributions may use logarithmic rather than linear scales. - Time buckets need to align with the source data intervals. - Misaligned intervals create aliasing artifacts: for example, grouping 10-second data into 7-second buckets produces repeating count patterns such as `[1, 1, 2, 1, 1, 2, …]`. - Careful discretization preserves the resolution available in the original DDSketch data. ## Designing the Color Scale - The default palette begins with light blue, consistent with other single-series Datadog visualizations. - It transitions toward purple to match Datadog’s visual identity. - The scale avoids lingering on red, which can imply negative alerts, and ends in orange for the hottest values. - Color choices must communicate both the volume and structure of the distribution. ## Maintaining Dynamic Range - A few high-count bins can dominate a linear color scale, leaving most of the heatmap visually indistinguishable. - This is especially problematic for power-law distributions with a dense central mode and a long tail. - A linear scale may clearly show the main mode around 20 ms while hiding a smaller mode near 1 second. - Human brightness perception is nonlinear, approximately following a power law described by Stevens’ law. - Applying nonlinear color interpolation improves the visibility of meaningful differences across both dense regions and long tails. - This helps preserve distribution details that would otherwise be lost when the color range is dominated by outliers or highly concentrated buckets. Datadog’s heatmap approach combines DDSketch compression, aligned high-resolution buckets, and perceptually informed color scaling. For systems where averages or a handful of percentiles conceal important subpopulations, distribution heatmaps provide a more reliable way to investigate performance at scale.

Read original(opens in new tab)
datadog4 min readCurated summary

.NET Continuous Profiler: Under the hood

Datadog’s .NET profiler is designed for continuous, low-overhead production monitoring rather than occasional diagnostic runs. It collects CPU, wall time, exceptions, lock contention, and allocation data, aggregates it into compact `.pprof` files, and links profiles to traces and services through runtime metadata. The post introduces the architecture and emphasizes preserving application performance as a central design requirement. ## What a Continuous Profiler Does - Profiling analyzes runtime performance and method call stacks. - It complements APM, which focuses on request latency, throughput, and errors. - The profiler also measures: - CPU usage - Wall time and method duration - Exceptions - Lock contention - Memory allocations and potential leaks - Unlike tools such as PerfView, dotTrace, dotMemory, and Visual Studio profilers, Datadog’s profiler is intended to run continuously in production with negligible overhead. - Continuous profiling avoids the need to recreate production traffic, security settings, hardware, and load in a separate environment. ## Datadog’s .NET Profiler Architecture - The profiler is composed of specialized profilers for different resource types. - Each profiler includes: - A sampler that collects raw data - A provider that exposes the collected samples - An aggregator combines samples from all profilers. - An exporter serializes the data into Google’s `.pprof` format and uploads it through the Datadog Agent. - Datadog’s backend processes the profiles for visualization and analysis. ## Sample Aggregation and Storage Each sample contains: - A call stack made up of method frames - Key-value labels, such as thread identifiers - A numeric value vector representing measurements like CPU consumption or wall time Samples with identical call stacks and labels are merged, and their numeric values are added together. This reduces duplication and produces smaller profile files—for example, repeated exceptions from the same code path and thread can be stored as one aggregated sample. The aggregation and `.pprof` serialization code is implemented in Rust and shared across Datadog’s Ruby, PHP, and other runtime profilers. ## Connecting Profiles to Traces and Services - Each uploaded profile includes process ID, host name, and runtime ID metadata. - The runtime ID uniquely identifies a .NET service running within a process. - This is important because a single .NET process can host multiple services, such as separate IIS applications running in different AppDomains. - The tracer communicates the mapping between runtime IDs, AppDomains, and service names. - Service names come from `DD_SERVICE`; if it is unset, the process name is used. - Datadog sends one profile per runtime ID every minute, so multiple profiles from one process may share a timestamp while representing different services. - Runtime IDs allow the backend to associate profiles with the correct traces and spans. ## Making .NET Call Stacks Easier to Read The .NET profiling API can expose compiler- and runtime-generated names that differ from the original source code. Datadog rewrites these frames to make visualized call stacks more understandable. - Constructors named `.ctor` are displayed using the class name. - Compiler-generated anonymous methods are rendered as the enclosing method followed by `_AnonymousMethod`. - Lambdas and local methods use an enclosing-method name with the `_Lambda` suffix. - Nested named methods such as `<DefiningMethodName>g__InnerMethodName|yyy_zzz` are displayed as `DefiningMethodName.InnerMethodName`. - Compiler-generated state-machine methods such as `MoveNext` are mapped back to the original source-level type and method names. ## Native and Managed Implementation Considerations - The team considered using Microsoft’s `TraceEvent` NuGet package to receive and parse CLR events in C#. - That approach would execute managed profiling code on the same CLR as the application being profiled. - Allocations made by the profiler could therefore increase garbage-collector pressure. - The post begins discussing how this performance concern influenced the implementation, but the provided excerpt ends before that design is explained. A production profiler must not only collect useful data but also minimize the memory and CPU costs of collecting it. Datadog’s architecture addresses this through specialized samplers, aggregation, compact serialization, runtime-aware trace association, and source-oriented call-stack cleanup.

Read original(opens in new tab)
datadog3 min readCurated summary

Engineering spotlight: Jeromy Carriere

Jeromy Carriere, Datadog’s SVP of Product Engineering, describes engineering leadership as balancing strategy, execution, people development, and organizational processes. His career across Google, Facebook, and Datadog shaped his passion for observability and taught him to lead through mistakes, autonomy, and accountability. He argues that sustained innovation requires both intentional direction and space for teams and individuals to grow. ## The Responsibilities of Engineering Leadership - Carriere’s work shifts with organizational cycles: - Quarterly planning focused on connecting initiatives and increasing collaboration. - Execution periods focused on removing resource and decision-making blockers. - Ongoing performance management across the broader engineering organization. - He works on how Engineering operates, including: - Process definition and improvement. - Hiring and performance management. - Reviewing design documents and code. - Observing incidents and postmortems. - His central challenge is balancing attention across strategy, execution, people, and technical quality. ## From Cloud Monitoring to Datadog - At Google in 2014, Carriere helped create a cloud monitoring offering because Google Cloud lacked capabilities comparable to Datadog. - He later worked on observability at Facebook and developed a strong interest in improving developer and engineer productivity. - He returned to Datadog after seeing its ability to innovate and deliver products with sustained velocity. - He emphasizes that velocity means more than moving quickly: it requires direction, strategy, and consistency over time. ## Learning Through Mistakes - Carriere believes the most valuable lessons come from making and owning mistakes. - Earlier in his career, he was sometimes too directive, limiting team creativity and ownership. - At other times, he was too distant and failed to provide enough support. - His current leadership approach aims to: - Give teams substantial autonomy. - Provide support when needed. - Hold teams accountable for agreed-upon outcomes. - He also learned that people may not have a clear five-year career plan. Leaders should help them identify the work that provides satisfaction and enables them to perform at their best. ## Creating Space for Career Decisions - People often become focused on the immediate task and overlook other possibilities. - Carriere recommends deliberately stepping back to observe: - What activities feel satisfying. - What opportunities exist nearby. - What kinds of work could better match an individual’s strengths and interests. - This reflection requires intentional time and freedom rather than waiting for clarity to emerge automatically. ## The Value of Co-op and Internship Programs - Carriere credits the University of Waterloo’s co-op program with giving him early experience as a professional software developer. - The combination of strong academic training and repeated, high-quality industry placements helped connect theory with real work. - He sees a similar benefit in Datadog’s internship program, where interns are trusted with meaningful projects and often produce some of the company’s strongest work. Datadog’s engineering approach, as described by Carriere, combines strategic product velocity with thoughtful organizational support. For both leaders and individual contributors, the practical recommendation is to learn from mistakes, create room for reflection, and build environments where people have autonomy, meaningful work, and accountability.

Read original(opens in new tab)
datadogOriginal article

2023-03-08 incident: A deep dive into the platform-level recovery | Datadog (opens in new tab)

Following a massive system-wide outage in March 2023, Datadog successfully restored its EU1 region by identifying that a simple node reboot could resolve network connectivity issues caused by a faulty system patch. While the team managed to restore 100 percent of compute capacity within hours, the recovery effort was subsequently hindered by cloud provider infrastructure limits and IP address exhaustion. This post-mortem highlights the complexities of scaling hierarchical Kubernetes environments under extreme pressure and the importance of accounting for "black swan" capacity requirements. ## Hierarchical Kubernetes Recovery Datadog utilizes a strict hierarchy of Kubernetes clusters to manage its infrastructure, which necessitated a granular, three-tiered recovery approach. Because the outage affected network connectivity via `systemd-networkd`, the team had to restore components in a specific order to regain control of the environment. * **Parent Control Planes:** Engineers first rebooted the virtual machines hosting the parent clusters, which manage the control planes for all other clusters. * **Child Control Planes:** Once parent clusters were stable, the team restored the control planes for application clusters, which run as pods within the parent infrastructure. * **Application Worker Nodes:** Thousands of worker nodes across dozens of clusters were restarted progressively to avoid overwhelming the control planes, reaching full capacity by 12:05 UTC. ## Scaling Bottlenecks and Cloud Quotas Once the infrastructure was online, the team attempted to scale out rapidly to process a massive backlog of buffered data. This surge in demand triggered previously unencountered limitations within the Google Cloud environment. * **VPC Peering Limits:** At 14:18 UTC, the platform hit a documented but overlooked limit of 15,500 VM instances within a single network peering group, blocking all further scaling. * **Provider Intervention:** Datadog worked directly with Google Cloud support to manually raise the peering group limit, which allowed scaling to resume after a nearly four-hour delay. ## IP Address and Subnet Capacity Even after cloud-level instance quotas were lifted, specific high-traffic clusters processing logs and traces hit a secondary bottleneck related to internal networking. * **Subnet Exhaustion:** These clusters attempted to scale to more than twice their normal size, quickly exhausting all available IP addresses in their assigned subnets. * **Capacity Planning Gaps:** While Datadog typically targets a 66% maximum IP usage to allow for a 50% scale-out, the extreme demands of the recovery backlog exceeded these safety margins. * **Impact on Backlog:** For six hours, the lack of available IPs forced these clusters to process data significantly slower than the rest of the recovered infrastructure. ## Recovery Summary The EU1 recovery demonstrates that even when hardware is functional, software-defined limits can create cascading delays. Organizations should not only monitor their own resource usage but also maintain visibility into cloud provider quotas and ensure that subnet allocations account for extreme recovery scenarios where workloads may need to double or triple in size momentarily.

datadog3 min readCurated summary

Not just another network latency issue: How we unraveled a series of hidden bottlenecks

Repeated high-startup-latency pages in Datadog’s usage estimation service were caused by several independent bottlenecks rather than application changes. The investigation eventually identified four issues: CPU-throttled Envoy sidecars, a Linux kernel bug affecting ENA transmit queues, insufficient EC2 network bandwidth, and requests routed to terminating cache pods. Fixing each layer progressively reduced remote-cache p99 latency from roughly one second to its normal level of about 100 ms. ## Service Architecture and the Original Symptoms - The service consists of router, counter, and aggregator applications. - At startup, `counter` loads data from a remote cache into a local cache. - While the local cache is populating, request processing is slower and backlog grows. - Normal p99 remote-cache latency was approximately 100 ms, but it exceeded one second during deployments. - Scaling the remote cache did not help, indicating that the cache itself was not underprovisioned. ## CPU-Throttled Envoy Sidecars - Requests to the remote cache passed through an Envoy sidecar that batched queries into packets. - When `counter` restarted, Envoy reached its two-core CPU limit and was throttled. - Delayed request and response processing caused retries, TCP retransmits, and increased remote-cache latency. - Increasing Envoy’s CPU allocation eliminated the issue in staging and reduced production latency, but did not fully resolve rollout spikes. ## Linux Kernel and ENA Transmit-Queue Bug - Investigation of system and network metrics revealed a Linux kernel bug affecting AWS Elastic Network Adapter traffic. - The kernel mapped all outbound traffic to the first transmit queue instead of distributing it across eight queues. - This limited throughput and caused retransmits during high-traffic periods such as deployments. - A hotfix distributed traffic across all eight queues. - The change removed non-rollout latency spikes but rollout latency still fluctuated between 200 and 600 ms. ## EC2 Network Bandwidth Limits - ENA metrics showed that instances exceeded AWS inbound and outbound bandwidth allowances. - AWS dropped packets at the hypervisor when those limits were exceeded, causing retransmissions and slower cache requests. - Migrating to network-optimized EC2 instance types with higher bandwidth allowances largely restored p99 latency to around 100 ms. - Occasional one-second spikes continued despite the improvement. ## Requests Sent to Terminating Cache Pods - Remaining spikes correlated with remote-cache pods that were shutting down. - Clients continued sending requests to terminating pods, leading to one-second timeouts and retries. - The cache’s graceful-shutdown behavior did not adequately wait for Envoy clients’ in-flight requests. - The team added a `preStop` hook that sets an `XXX_MAINTENANCE_MODE` key to notify clients before termination and began coordinating shutdown with outstanding requests. The incident demonstrates the importance of tracing latency across the entire request path, from application startup through proxies, kernel networking, hardware interfaces, cloud bandwidth limits, and pod lifecycle behavior. Layered system metrics and component-level investigation were necessary to eliminate alert fatigue and restore reliable deployment behavior.

Read original(opens in new tab)
datadog3 min readCurated summary

Making fetch happen: Building a general-purpose query and render scheduler

Datadog rebuilt its dashboard scheduler to improve responsiveness while distributing network and rendering work more efficiently. The legacy system helped, but had grown into a complex set of roughly 20 interdependent heuristics that were difficult to maintain and poorly separated query scheduling from rendering. A simpler, general-purpose approach reduced request spikes, improved fetching performance, and created a foundation for browser-aware task scheduling. ## Limitations of the Original Scheduler - A periodic updater determined when widgets should request fresh data based on factors such as time range and browser focus. - Query tasks for visible widgets ran immediately; offscreen queries were delayed using heuristics such as pending-query counts and historical fetch durations. - Render tasks similarly prioritized visible widgets and delayed offscreen work. - The system improved performance over an unscheduled baseline by reducing main-thread work and flattening query traffic. - Over time, it accumulated around 20 parameters and interlinked rules. - Query and render concerns were mixed together: - Queries could be delayed because too many render tasks were pending. - Renders could be delayed based on data size even when browser resources were available. - The dashboard-specific implementation could not easily be reused across Datadog’s increasingly generalized widget framework. ## A General-Purpose Scheduling Strategy - Datadog separated query scheduling from render scheduling so each could be developed, tested, and rolled out independently. - The team evaluated existing heuristics across dashboards of different sizes and under different browser conditions. - Several rules were removed without harming performance: - Unfocused or occluded tabs did not need special delays because the periodic updater and browser already throttle them. - The redesign aimed to preserve two goals: - Keep query execution distributed over time. - Prioritize widgets visible to the user. - The new system was progressively deployed, first to dashboards and then to the shared data-fetching framework used across Datadog. ## Simpler Query Scheduling The new query algorithm uses a small set of straightforward rules: - Fetches for visible widgets run immediately. - Non-visible queries are ranked and executed in fixed time windows, subject to a task limit. - Query execution pauses when the number of pending fetches becomes too high. - The chosen configuration uses: - A 2,000-millisecond time window. - A maximum of 10 tasks per window. - FIFO-style ranking for offscreen queries, favoring earlier requests. - The scheduler uses only about six parameters instead of the legacy system’s roughly 20. - The simplified algorithm produced a better task distribution than the old scheduler. - “429 Too many requests” errors dropped significantly, reducing retries and helping data arrive sooner. ## Browser-Aware Render Scheduling - The old render scheduler did not account for the browser’s available CPU and memory resources. - Datadog adopted the Browser Scheduling API to create prioritized tasks that the browser can schedule natively. - Tasks can receive priorities such as: - `user-blocking` - `user-visible` - `background` - A `TaskController` assigns a priority signal to scheduled work. - Priorities can later be changed for all tasks controlled by the same controller, and tasks can be aborted. - The API was supported in Chromium and Firefox Nightly, with a polyfill for other browsers. Datadog’s experience suggests that performance schedulers benefit from simple, independently testable rules: prioritize visible work, smooth network activity, and let the browser manage expensive rendering when possible.

Read original(opens in new tab)
datadog3 min readCurated summary

Husky: Exactly-once ingestion and multi-tenancy at scale

Husky, Datadog’s distributed, time-series-oriented event store, is optimized for large scans and aggregations rather than high-volume, low-latency point lookups. This makes exactly-once ingestion challenging, especially at Datadog’s multi-tenant scale. Datadog addresses the problem with deterministic, locality-aware routing that limits deduplication scope, improves storage efficiency, and supports autoscaling ingestion pipelines. ## Husky’s Ingestion Challenge - Husky separates storage and compute, allowing each to scale independently. - Its storage engine is designed primarily for large analytical scans and aggregations. - It is not optimized for massive numbers of low-latency point lookups, complicating duplicate detection during ingestion. - The ingestion system must guarantee that every event is stored exactly once while maintaining: - Multi-tenant scalability - Reasonable ingestion latency - Controlled infrastructure and storage costs ## Routing Events to Storage Shards - Datadog uses an upstream **Shard Router** to introduce locality into Kafka pipelines. - Events are deterministically assigned to shards based on their tenant, timestamp, and event ID. - Each tenant receives a list of shards rather than being permanently assigned to one shard. - A deterministic choice from that list distributes the tenant’s events while keeping the number of active shards as small as practical. - Downstream workers consume one or more shards and perform exactly-once ingestion into Husky. ## Benefits of Data Locality - **Simpler deduplication** - An event with the same timestamp and ID always reaches the same shard. - Deduplication only needs to occur within that shard. - Workers handle smaller sets of event IDs, making in-memory deduplication more efficient. - **Lower storage costs and better performance** - Each shard processes a relatively small set of tenants. - Husky stores each tenant in a separate table and does not mix tenants within files. - More tenants per writer produce more output files, increasing blob-storage costs and compaction work. - Restricting tenant cardinality reduces file creation and improves writer and compactor efficiency. ## Challenges in Deterministic Routing - **Changing shard assignments** - Tenant traffic can increase by one or two orders of magnitude. - Assignments may change when scaling a tenant across more shards or rebalancing traffic among existing shards. - **Distributed router consensus** - Every Shard Router node must make the same routing decision for a given event. - Inconsistent decisions could send duplicates to different shards and undermine exactly-once ingestion. - **Load balancing** - Shards must receive roughly equal traffic so downstream ingestion workers remain balanced. ## Time-Bounded Shard Placements - A simple deterministic mapping can select a shard using a hash of the event ID: ```text shard = shards[hash(event_id) % num_shards] ``` - This approach is cheap and stateless when all routers know the same shard list. - However, changing the shard list can cause the same event ID to map to a different shard, so assignment changes require additional coordination. - The article introduces **time-bounded Shard Placements** to preserve consistent routing while allowing tenant assignments to evolve, though the supplied excerpt ends before explaining the mechanism in detail. Datadog’s core recommendation is to combine deterministic, tenant-aware routing with carefully coordinated assignment changes. This narrows the scope of deduplication while reducing storage overhead and enabling balanced, scalable exactly-once ingestion.

Read original(opens in new tab)
datadogOriginal article

Performance improvements in the Datadog Agent metrics pipeline | Datadog (opens in new tab)

Datadog engineers recently optimized the Datadog Agent's metric processing pipeline to achieve higher throughput and lower CPU overhead. By identifying that metric context generation—the process of creating unique keys for metrics—was a primary bottleneck, they implemented a series of algorithmic changes and Go runtime optimizations. These improvements allow the Agent to process significantly more metrics using the same computational resources. ### Identifying Bottlenecks via CPU Profiling * Developers utilized Go’s native profiling tools to capture CPU usage during high-volume metric ingestion via DogStatsD. * Flamegraph analysis revealed that the `addSample` and `trackContext` functions were the most CPU-intensive components of the pipeline. * The profiling data specifically pointed to tag sorting and deduplication as the underlying operations consuming the most processing time. ### The Challenges of Metric Context Generation * The Agent must generate a unique hash (context) for every metric received to address it within a hash table in RAM. * To ensure the same metric always generates the same key, the original algorithm required sorting all tags and ensuring their uniqueness. * The computational cost of sorting lists repeatedly for every incoming message created a performance ceiling for the entire metrics pipeline. ### Specialization and Runtime Optimization * **Algorithmic Specialization:** The team implemented specialized sorting logic that adjusts based on the number of tags, optimizing the "hot path" for the most common metric structures. * **Hashing Efficiency:** Micro-benchmarks identified Murmur3 as the most efficient hash implementation for balancing speed and collision resistance in this use case. * **Leveraging Go Runtime:** The team transitioned from 128-bit hashes to 64-bit metric contexts. This change allowed the Agent to utilize Go's internal `mapassign_fast64` and `mapaccess2_fast64` functions, which provide optimized map operations for 64-bit keys. ### Redesigning for Performance * The original design followed a rigid "hash metric name -> sort tags -> deduplicate tags -> iterative hash" workflow. * Recognizing that sorting was the primary architectural bottleneck, the team moved toward a new design intended to minimize or eliminate the overhead of traditional list sorting during context generation. To achieve similar performance gains in high-throughput Go applications, developers should profile their applications under realistic load and look for opportunities to leverage runtime-specific optimizations, such as using 64-bit map keys to trigger specialized compiler paths.