Creating Composite CloudWatch Alarms with Condition Logic and Missing Data Treatment

Composite CloudWatch alarms aggregate multiple metric alarms using Boolean operators like AND, OR, and AT_LEAST, while explicit treatMissingData settings prevent false INSUFFICIENT_DATA states during gaps in telemetry.

The Agent Toolkit for AWS provides comprehensive guidance for building robust observability stacks, including detailed patterns for creating composite CloudWatch alarms with proper condition logic and missing data treatment. Located in the aws/agent-toolkit-for-aws repository, the toolkit's reference documentation and TypeScript assets demonstrate how to combine metric states with Boolean rules while handling data gaps explicitly.

How Composite Alarms Work

Composite alarms evaluate the states of underlying metric alarms using rule expressions. According to the reference documentation in skills/core-skills/aws-observability/references/alarms.md, these alarms enable complex service-level health checks by combining multiple individual metrics into a single actionable state.

Boolean Operators and Syntax

The toolkit supports four logical operators for alarm aggregation:

  • AND – All referenced alarms must be in the ALARM state.
  • OR – At least one referenced alarm must be ALARM.
  • NOT – The referenced alarm must NOT be ALARM (must be OK or INSUFFICIENT_DATA).
  • AT_LEAST(M, STATE, (a1, a2, …)) – At least M of the listed alarms must be in the specified state. Percentages are valid, such as AT_LEAST(50%, ALARM, (a1, a2, a3)).

In CDK, the CompositeAlarm construct uses the AlarmRule helper to express these relationships. The AlarmRule.anyOf, AlarmRule.allOf, and AlarmRule.not methods map directly to the Boolean operators above.

Account and Region Constraints

All underlying metric alarms and the composite alarm must reside in the same AWS account and Region. Cross-account or cross-region alarm aggregation is not supported in the current implementation.

Missing Data Treatment Options

When metrics fail to publish datapoints during an evaluation period, CloudWatch requires explicit guidance on interpreting the gap. The treatMissingData parameter controls this behavior, and the toolkit strongly recommends explicitly setting this value for every alarm to avoid ambiguous states.

The Four TreatMissingData Behaviors

Value Behavior Use Case
missing (default) Missing data triggers INSUFFICIENT_DATA EC2 stop/terminate actions
notBreaching Missing data treated as OK (within threshold) Error-count metrics where "no data = no errors"
breaching Missing data treated as ALARM (violates threshold) Heartbeat or health-check metrics
ignore Current state remains unchanged DynamoDB alarms (console overrides default)

Choosing the Right Policy

The default missing setting often leads to unexpected INSUFFICIENT_DATA states that can mask genuine issues. For error-rate metrics, use notBreaching to assume success when no data arrives. For heartbeat checks, use breaching to treat silence as a failure. According to the source analysis, lines 60-62 of alarms.md emphasize that explicit configuration prevents operational ambiguity.

Implementation Patterns

The Agent Toolkit provides both conceptual documentation and executable code assets. The plugins/aws-core/skills/aws-observability/assets/alarm-template.ts file contains production-ready TypeScript implementations demonstrating the CompositeAlarm construct with AlarmRule helpers.

CDK Implementation with AlarmRule

The following pattern creates individual metric alarms with explicit missing data handling, then combines them into a composite alarm:

import {
  Alarm,
  ComparisonOperator,
  MathExpression,
  TreatMissingData,
  CompositeAlarm,
  AlarmRule,
  AlarmState,
} from 'aws-cdk-lib/aws-cloudwatch';
import { Duration } from 'aws-cdk-lib';

// Individual metric alarm with explicit missing data treatment
const errorRateAlarm = new Alarm(this, 'ErrorRateAlarm', {
  metric: new MathExpression({
    expression: 'IF(invocations > 0, errors * 100 / invocations, 0)',
    usingMetrics: {
      errors: fn.metricErrors({ period: Duration.minutes(1) }),
      invocations: fn.metricInvocations({ period: Duration.minutes(1) }),
    },
  }),
  threshold: 5,
  evaluationPeriods: 3,
  datapointsToAlarm: 2,
  comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
  treatMissingData: TreatMissingData.NOT_BREACHING,
});

const latencyAlarm = new Alarm(this, 'LatencyAlarm', {
  metric: fn.metricDuration({ statistic: 'p99', period: Duration.minutes(1) }),
  threshold: 3000,
  evaluationPeriods: 3,
  datapointsToAlarm: 2,
  comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD,
  treatMissingData: TreatMissingData.NOT_BREACHING,
});

// Composite alarm firing when either condition is unhealthy
const serviceHealthAlarm = new CompositeAlarm(this, 'ServiceHealth', {
  alarmRule: AlarmRule.anyOf(
    AlarmRule.fromAlarm(errorRateAlarm, AlarmState.ALARM),
    AlarmRule.fromAlarm(latencyAlarm, AlarmState.ALARM),
  ),
});

CLI Configuration

Create metric alarms with explicit missing data handling via the AWS CLI:

aws cloudwatch put-metric-alarm \
  --alarm-name MyFunc-ErrorRate \
  --metrics '[
    {"Id":"errors","MetricStat":{"Metric":{"Namespace":"AWS/Lambda","MetricName":"Errors","Dimensions":[{"Name":"FunctionName","Value":"MyFunc"}]},"Period":60,"Stat":"Sum"},"ReturnData":false},
    {"Id":"invocations","MetricStat":{"Metric":{"Namespace":"AWS/Lambda","MetricName":"Invocations","Dimensions":[{"Name":"FunctionName","Value":"MyFunc"}]},"Period":60,"Stat":"Sum"},"ReturnData":false},
    {"Id":"error_rate","Expression":"IF(invocations > 0, errors * 100 / invocations, 0)","Label":"Error Rate %"}
  ]' \
  --threshold 5 \
  --comparison-operator GreaterThanThreshold \
  --evaluation-periods 3 \
  --datapoints-to-alarm 2 \
  --treat-missing-data notBreaching

Configure latency monitoring with the same missing data policy:

aws cloudwatch put-metric-alarm \
  --alarm-name MyFunc-Latency \
  --namespace AWS/Lambda \
  --metric-name Duration \
  --dimensions Name=FunctionName,Value=MyFunc \
  --extended-statistic p99 \
  --period 60 \
  --evaluation-periods 3 \
  --datapoints-to-alarm 2 \
  --threshold 3000 \
  --comparison-operator GreaterThanThreshold \
  --treat-missing-data notBreaching

CloudFormation YAML

Define composite alarms in CloudFormation using the AWS::CloudWatch::CompositeAlarm resource type:

Resources:
  ServiceHealthAlarm:
    Type: AWS::CloudWatch::CompositeAlarm
    Properties:
      AlarmName: ServiceHealth
      AlarmRule: |
        ALARM("ErrorRateAlarm") OR ALARM("LatencyAlarm")

Limitations and Architecture

Understanding the constraints of composite alarms ensures proper operational design.

Action Constraints

Composite alarms cannot trigger EC2 or Auto Scaling actions. Any remediation requiring instance termination, scaling, or recovery must be attached directly to the underlying metric alarms, not the composite alarm. This limitation is documented in alarms.md at lines 33-34.

Source Code Structure

The Agent Toolkit organizes alarm functionality across two primary locations:

These resources are consumed by the Agent Toolkit's LLM-driven agents to generate correct CloudFormation or CDK deployments on demand.

Summary

  • Composite alarms use Boolean operators (AND, OR, NOT, AT_LEAST) to aggregate multiple metric alarm states into a single health indicator.
  • Explicit treatMissingData configuration is required to prevent false INSUFFICIENT_DATA states; choose notBreaching for error metrics, breaching for heartbeats, and missing only when EC2 actions are required.
  • Regional constraints require all underlying alarms and the composite alarm to exist in the same AWS account and Region.
  • Action limitations prevent composite alarms from triggering EC2 or Auto Scaling actions; attach these actions to individual metric alarms instead.
  • Implementation follows the patterns in alarm-template.ts using AlarmRule helpers for CDK, or direct rule expressions for CloudFormation.

Frequently Asked Questions

How do I prevent a composite CloudWatch alarm from entering INSUFFICIENT_DATA when one metric stops reporting?

Set the treatMissingData parameter to notBreaching on the individual metric alarm. This treats missing datapoints as "within threshold" (OK), preventing the composite alarm from transitioning to INSUFFICIENT_DATA when telemetry gaps occur. Use this for error-rate or latency metrics where no data implies healthy behavior.

Can composite CloudWatch alarms trigger EC2 Auto Scaling actions?

No. Composite alarms cannot trigger EC2 or Auto Scaling actions directly. You must attach scaling policies to the underlying metric alarms. The composite alarm is suitable for notifications and overall service health dashboards, while individual metric alarms handle infrastructure remediation.

What is the difference between AlarmRule.anyOf and AlarmRule.allOf in CDK?

AlarmRule.anyOf implements the OR operator, triggering the composite alarm when at least one referenced alarm is in the ALARM state. AlarmRule.allOf implements the AND operator, requiring all referenced alarms to be in ALARM before the composite alarm fires. Both are available in the aws-cdk-lib/aws-cloudwatch module.

How do I require at least 50% of my alarms to fire before triggering a composite alarm?

Use the AT_LEAST operator in your alarm rule expression: AT_LEAST(50%, ALARM, (alarm1, alarm2, alarm3)). In CloudFormation YAML, write the rule as AT_LEAST(50%, ALARM("alarm1"), ALARM("alarm2"), ALARM("alarm3")). This creates a threshold-based composite alarm that tolerates partial failures without requiring complex CDK logic.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →