I Tried Checking S3 Delivery Costs with the New ALB Logs Feature "CloudWatch Logs Integration"
This page has been translated by machine translation. View original
Introduction
On July 23, 2026, an update was released that integrates ALB log recording with the CloudWatch Logs delivery feature (Vended Logs).
The previous article introduced log output to CloudWatch Logs.
In this article, I checked the cost impact of delivering ALB logs to S3 via the CloudWatch Logs integration.
In this article, the conventional S3 output configured through ALB attributes (such as access_logs.s3.*) is referred to as the "legacy configuration."
What I Tested
This time, I targeted the ALB health check logs. Because they are not easily affected by the presence of actual access and are output at regular intervals, I thought they would make it easy to compare file sizes and output counts for each format.
I delivered the same health check logs to S3 in parallel in three formats (JSON, plain, and Parquet), and checked the file sizes and the cost impact of S3 delivery via the CloudWatch Logs integration.
Test Environment
| Item | Value |
|---|---|
| Region | ap-northeast-1 |
| Load balancer | 1 Application Load Balancer (<ALB_NAME>) |
| Target log type | ALB_HEALTH_CHECK_LOGS |
| Number of log fields | 10 fields |
| Health check interval | 30 seconds |
| Log file output interval | 5 minutes (per ALB node) |
| Output formats | JSON / plain (space-delimited) / Parquet |
| Measurement period | 2026-07-22 to 2026-07-27 (S3 delivery was configured on 07-24) |
In this comparison, to avoid the influence of differences in destinations, I delivered all three formats to the same S3 bucket <LOG_BUCKET>, separating them only by prefix.
All size and cost figures below are based on health check logs.
Comparison by Format
File Size
I extracted and compared one set of files containing 10 records, output at the same time from the same ALB node.
| Format | File size | Per-record equivalent | Ratio to plain (%) |
|---|---|---|---|
| plain (gz) | 283 B | 28.3 B | 100% |
| JSON (gz) | 393 B | 39.3 B | 139% |
| Parquet (internal GZIP) | 4,042 B | 404.2 B | 1,429% |
The "per-record equivalent" is a reference value obtained by dividing the compressed file size by 10 records. Compression efficiency varies with the number of records, so it does not scale proportionally as the record count increases.
plain (space-delimited)
Like the legacy configuration, plain is a format in which values are arranged separated by spaces.
http 2026-07-27T00:25:02.444270Z 0.022732728 <TARGET_IP>:3000 <TARGET_GROUP> PASS 200 - app/<ALB_NAME>/<ALB_ID> <ALB_NODE_IP>
JSON
In JSON, each record is output as a one-line object.
{"type":"http","time":"2026-07-27T00:25:02.444270Z","latency":"0.022732728","target_addr":"<TARGET_IP>:3000","target_group_id":"<TARGET_GROUP>","status":"PASS","status_code":"200","reason_code":"-","elb":"app/<ALB_NAME>/<ALB_ID>","ip_address":"<ALB_NODE_IP>"}
If the number of fields differs, the ratio will also change (in the access log measurement in the previous article, JSON was 2.53 times plain).
Because JSON maps keys to values, it can be handled without depending on the position of values as with plain. If you are preparing for field additions, JSON is easier to work with.
Parquet
When I read the Parquet file with pyarrow, I was able to check the proportion of metadata.
- rows=10, row_groups=1, columns=10
- Compression method is GZIP
- The data portion of the row group is 1,070 B
- Footer and schema metadata are about 2,972 B (about 73% of the entire file)
Parquet is not suitable for output on the scale of this test, with 5-minute intervals and 10 records. Metadata accounts for about 73% of the whole, and the large file size stood out more than the benefits of columnar format such as compression and narrowing the scan range. In addition, the official announcement states that Parquet conversion incurs a separate charge, and the unit price shown in the official blog for N. Virginia is $0.035/GB. Even in the Tokyo region, I see no reason to choose it as an S3 storage format at this scale.
Although it is outside the scope of this test, if you want to accumulate logs in Parquet, there is also a method of converting logs delivered in JSON or plain in bulk using Athena CTAS or Glue ETL. Since this lets you increase the number of records per file, you can keep the metadata ratio lower than when saving small files as Parquet as-is.
Measured Costs
In Cost Explorer, I checked the daily usage by usage type for ap-northeast-1. I compare 2026-07-22 and 07-23 before the configuration with 2026-07-25, the day after the configuration.
| Date | S3 recording | APN1-VendedLog-Bytes usage (MB equivalent) |
|---|---|---|
| 2026-07-22 | No | 10.57 |
| 2026-07-23 | No | 10.75 |
| 2026-07-25 | Yes | 10.73 |
I converted APN1-VendedLog-Bytes in Cost Explorer to MB for comparison. Even after configuring the CloudWatch Logs integration, this usage type stayed within the range of 10.57 to 10.75 MB observed before the configuration.
The roughly 10 MB that was being recorded before the configuration is attributable to VPC flow logs running in the same account. I set a cost allocation tag on the flow log's log group and confirmed that this usage type is generated only by the flow logs. Other than the ALB logs in this test, I have not configured any deliveries targeting Vended Logs.
The official announcement states that delivery is free when ALB logs are delivered to Amazon S3. Delivery to CloudWatch Logs and Amazon Data Firehose is charged as Vended Logs. The comparison result of APN1-VendedLog-Bytes this time is also consistent with this pricing structure.
Configuration Steps (Reference)
Here, I summarize the configuration for delivering the same logs to S3 in multiple formats in parallel, as in this test.
Deliver in 3 formats in parallel via CLI
Create one Delivery Source, and create a Delivery Destination and a Delivery for each of JSON, plain, and Parquet.
When switching formats in actual operation, separate the S3 buckets by format if possible to avoid mixing with existing logs. If you use the same bucket, at least separate the prefixes.
First, create the Delivery Source that serves as the origin of the logs.
aws logs put-delivery-source \
--name alb-health-check-logs \
--resource-arn arn:aws:elasticloadbalancing:ap-northeast-1:<ACCOUNT_ID>:loadbalancer/app/<ALB_NAME>/<ALB_ID> \
--log-type ALB_HEALTH_CHECK_LOGS
In the test, I created a Delivery Destination for each format, all pointing to the same S3 bucket. Only --output-format is changed.
aws logs put-delivery-destination \
--name alb-vended-logs-s3-json \
--output-format json \
--delivery-destination-configuration destinationResourceArn=arn:aws:s3:::<LOG_BUCKET>
aws logs put-delivery-destination \
--name alb-vended-logs-s3-plain \
--output-format plain \
--delivery-destination-configuration destinationResourceArn=arn:aws:s3:::<LOG_BUCKET>
aws logs put-delivery-destination \
--name alb-vended-logs-s3-parquet \
--output-format parquet \
--delivery-destination-configuration destinationResourceArn=arn:aws:s3:::<LOG_BUCKET>
Finally, link them with Delivery. Change the destination ARN and suffixPath for each format so that outputs do not get mixed within the same bucket.
aws logs create-delivery \
--delivery-source-name alb-health-check-logs \
--delivery-destination-arn arn:aws:logs:ap-northeast-1:<ACCOUNT_ID>:delivery-destination:alb-vended-logs-s3-json \
--s3-delivery-configuration 'suffixPath=health-check-logs/json/{region}/{yyyy}/{MM}/{dd}/'
For plain and Parquet as well, run create-delivery in the same way, changing the destination ARN and suffixPath.
S3 bucket policy
When using S3 as the destination, grant the following permissions to delivery.logs.amazonaws.com.
- Object write (
s3:PutObject) - Get bucket ACL (
s3:GetBucketAcl)
{
"Effect": "Allow",
"Principal": {
"Service": "delivery.logs.amazonaws.com"
},
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::<LOG_BUCKET>/*",
"Condition": {
"StringEquals": {
"aws:SourceAccount": "<ACCOUNT_ID>"
}
}
}
{
"Effect": "Allow",
"Principal": {
"Service": "delivery.logs.amazonaws.com"
},
"Action": "s3:GetBucketAcl",
"Resource": "arn:aws:s3:::<LOG_BUCKET>",
"Condition": {
"StringEquals": {
"aws:SourceAccount": "<ACCOUNT_ID>"
}
}
}
To simplify the test, both statements use only aws:SourceAccount as the condition and do not specify aws:SourceArn. In actual operation, we recommend also specifying aws:SourceArn to restrict the delivery source.
Reasons to Migrate from the Legacy Configuration to the CloudWatch Logs Integration
As mentioned above, when delivering ALB logs to S3 via the CloudWatch Logs integration, no Vended Logs delivery charge applies. S3 storage charges and request charges are incurred as before. Note that when delivering in multiple formats in parallel as in this test, the number of PutObject requests increases in proportion to the number of formats.
You can separate the log delivery definition from the ALB attributes. In this test as well, all of the ALB's log-related attributes remained disabled, yet logs were delivered to S3 through the Delivery defined on the CloudWatch Logs side.
Even in a configuration that limits the log destination to S3 with cost as the priority, the new method lets you add delivery to a CloudWatch Logs log group as needed. Since Logs Insights searches and near-real-time checks become available, this is useful during events and for incident investigation.
Additional delivery to a log group incurs CloudWatch Logs charges. You can also use it in a way where logs are normally stored only in S3, and delivery to a log group is added only before an event or during the period of reproducing an incident. Please consider it as needed.
Summary
For new builds that store ALB logs in S3, I consider the CloudWatch Logs integration the first choice. For delivery to S3, no Vended Logs delivery charge applies, you can choose the output format, and if needed you can add delivery to a log group and investigate with Logs Insights.
As for the format, plain is easier to handle if you prioritize capacity, and JSON is easier to handle if you anticipate field additions or programmatic handling.
For environments using the existing legacy configuration as well, I recommend reviewing it at a time when you can maintain the log analysis environment. Keep the legacy configuration enabled and set up new log delivery to a separate destination. If you disable the legacy configuration after confirming that the log analysis and log processing pipelines work correctly, you can switch over safely.
