Since its launch in 2006, Amazon Simple Storage Service (Amazon S3) has experienced major growth, supporting multiple use cases such as hosting websites, creating data lakes, serving as object storage for consumer applications, storing logs, and archiving data. As the application portfolio grows, customers tend to store data from multiple application and different business functions in a single S3 bucket, which can grow the storage in S3 buckets to hundreds of TBs. The AWS Billing console provides a way to look at the total storage cost of data stored in Amazon S3, but sometimes IT organizations need to understand the breakdown of costs of a particular S3 bucket by various prefixes or objects corresponding to a particular user or application. There are various reasons to analyze the costs of S3 buckets, such as to identify the spend breakdown, do internal chargebacks, understand the cost breakdown by business unit and application, and many more. As of this writing, there is no easy way to do a cost breakdown of S3 buckets by objects and prefixes.
In this post, we discuss a solution using Amazon Athena to query AWS Cost and Usage Reports and Amazon S3 Inventory reports to analyze the cost by prefixes and objects in an S3 bucket.
The following figure shows the architecture for this solution. First, we enable the AWS Cost and Usage Reports (AWS CUR) and Amazon S3 Inventory features, which save the output into two separate pre-created S3 buckets. We then use Athena to query these S3 buckets for AWS CUR data and S3 object inventory data to correlate and allocate the cost breakdown at the object or prefix level.

To implement the solution, we complete the following steps:
For this walkthrough, you should have the following prerequisites:
Amazon S3 is an object storage service offering industry-leading scalability, data availability, security, and performance. Customers of all sizes and industries can store and protect any amount of data for virtually any use case, such as data lakes, cloud-native applications, and mobile apps. With cost-effective storage classes and easy-to-use management features, you can optimize costs, organize data, and configure fine-tuned access controls to meet specific business, organizational, and compliance requirements.
For this post, we use the S3 bucket s3-object-cost-allocation as the primary bucket for cost allocation. This S3 bucket is conveniently modeled to contain several prefixes and objects of different sizes for which cost allocation needs to be done based on the overall cost of the bucket. In a real-world scenario, you should use a bucket that has data for multiple teams and for which you need to allocate costs by prefix or object. Going forward, we refer to this bucket as the primary object bucket.
The following screenshot shows our S3 bucket and folders.

Now let’s create the three additional operational S3 buckets to store the datasets generated to calculate costs for the objects. You can create the following buckets or any existing buckets as needed:
cur-cost-usage-reports-<account\_number> – This bucket is used to save the Cost and Usage Reports for the account.S3-inventory-configurations-<account\_number> – This bucket is used to save the inventory configurations of our primary object bucket.athena-query-bucket-<account\_number> – This bucket is used to save the query results from Athena.Complete the following steps to create your S3 buckets:
cur-cost-usage-reports-<account\_number>).
s3-inventory-configurations-<account\_number> and athena-query-bucket-<account\_number>.The AWS Cost and Usage Reports (AWS CUR) contains the most comprehensive set of cost and usage data available. You can use Cost and Usage Reports to publish your AWS billing reports to an S3 bucket that you own. You can receive reports that break down your costs by the hour, day, or month; by product or product resource; or by tags that you define yourself.
Complete the following steps to enable Cost and Usage Reports for your account:
account-cur-s3.cur-cost-usage-reports-<account\_number>) and choose Next.cur-data/account-cur-daily.
The Cost and Usage reports will be delivered to the S3 buckets within 24 hours.
The following sample CUR in CSV format shows different columns of the Cost and Usage Report, including bill\_invoice\_id, bill\_invoicing\_entity, bill\_payer\_account\_id, and line\_item\_product\_code, to name a few.

Amazon S3 Inventory is one of the tools Amazon S3 provides to help manage your storage. You can use it to audit and report on the replication and encryption status of your objects for business, compliance, and regulatory needs. Amazon S3 Inventory provides comma-separated values (CSV), Apache Optimized Row Columnar (ORC), or Apache Parquet output files that list your objects and their corresponding metadata on a daily or weekly basis for an S3 bucket or a shared prefix (objects that have names that begin with a common string).
Complete the following steps to enable Amazon S3 Inventory on the primary object bucket:
s3-object-cost-allocation with some sample data and folder structure. s3-object-cost-allocation.s3-inventory-configurations-<account\_number>).

It may take up to 48 hours to deliver the first report.
Wait for up to 48 hours for the previous step to generate the reports. In this section, we use Athena to create and define AWS Glue Data Catalog tables for the data that has been created using Cost and Usage Reports and Amazon S3 Inventory reports.
Athena is a serverless, interactive analytics service built on open-source frameworks, supporting open-table and file formats. Athena provides a simplified, flexible way to analyze petabytes of data where it lives.
Complete the following steps to create the tables using Athena:

athena-query-bucket-<account\_number>).
account\_cur. CREATE EXTERNAL TABLE `account\_cur`(`identity\_line\_item\_id` string,`identity\_time\_interval` string,`bill\_invoice\_id` string,`bill\_billing\_entity` string,`bill\_bill\_type` string,`bill\_payer\_account\_id` string,`bill\_billing\_period\_start\_date` timestamp,`bill\_billing\_period\_end\_date` timestamp,`line\_item\_usage\_account\_id` string,`line\_item\_line\_item\_type` string,`line\_item\_usage\_start\_date` timestamp,`line\_item\_usage\_end\_date` timestamp,`line\_item\_product\_code` string,`line\_item\_usage\_type` string,`line\_item\_operation` string,`line\_item\_availability\_zone` string,`line\_item\_resource\_id` string,`line\_item\_usage\_amount` double,`line\_item\_normalization\_factor` double,`line\_item\_normalized\_usage\_amount` double,`line\_item\_currency\_code` string,`line\_item\_unblended\_rate` string,`line\_item\_unblended\_cost` double,`line\_item\_blended\_rate` string,`line\_item\_blended\_cost` double,`line\_item\_line\_item\_description` string,`line\_item\_tax\_type` string,`line\_item\_legal\_entity` string,`product\_product\_name` string,`product\_availability` string,`product\_description` string,`product\_durability` string,`product\_event\_type` string,`product\_fee\_code` string,`product\_fee\_description` string,`product\_free\_query\_types` string,`product\_from\_location` string,`product\_from\_location\_type` string,`product\_from\_region\_code` string,`product\_group` string,`product\_group\_description` string,`product\_location` string,`product\_location\_type` string,`product\_message\_delivery\_frequency` string,`product\_message\_delivery\_order` string,`product\_operation` string,`product\_platopricingtype` string,`product\_product\_family` string,`product\_queue\_type` string,`product\_region` string,`product\_region\_code` string,`product\_servicecode` string,`product\_servicename` string,`product\_sku` string,`product\_storage\_class` string,`product\_storage\_media` string,`product\_to\_location` string,`product\_to\_location\_type` string,`product\_to\_region\_code` string,`product\_transfer\_type` string,`product\_usagetype` string,`product\_version` string,`product\_volume\_type` string,`pricing\_rate\_code` string,`pricing\_rate\_id` string,`pricing\_currency` string,`pricing\_public\_on\_demand\_cost` double,`pricing\_public\_on\_demand\_rate` string,`pricing\_term` string,`pricing\_unit` string,`reservation\_amortized\_upfront\_cost\_for\_usage` double,`reservation\_amortized\_upfront\_fee\_for\_billing\_period` double,`reservation\_effective\_cost` double,`reservation\_end\_time` string,`reservation\_modification\_status` string,`reservation\_normalized\_units\_per\_reservation` string,`reservation\_number\_of\_reservations` string,`reservation\_recurring\_fee\_for\_usage` double,`reservation\_start\_time` string,`reservation\_subscription\_id` string,`reservation\_total\_reserved\_normalized\_units` string,`reservation\_total\_reserved\_units` string,`reservation\_units\_per\_reservation` string,`reservation\_unused\_amortized\_upfront\_fee\_for\_billing\_period` double,`reservation\_unused\_normalized\_unit\_quantity` double,`reservation\_unused\_quantity` double,`reservation\_unused\_recurring\_fee` double,`reservation\_upfront\_value` double,`savings\_plan\_total\_commitment\_to\_date` double,`savings\_plan\_savings\_plan\_a\_r\_n` string,`savings\_plan\_savings\_plan\_rate` double,`savings\_plan\_used\_commitment` double,`savings\_plan\_savings\_plan\_effective\_cost` double,`savings\_plan\_amortized\_upfront\_commitment\_for\_billing\_period` double,`savings\_plan\_recurring\_commitment\_for\_billing\_period` double,`resource\_tags\_user\_bucket\_name` string,`resource\_tags\_user\_cost\_tracking` string)PARTITIONED BY (`year` string,`month` string)ROW FORMAT SERDE'org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe'STORED AS INPUTFORMAT'org.apache.hadoop.mapred.TextInputFormat'OUTPUTFORMAT'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'LOCATION'<<LOCATION>>' Hive . Use the S3 URI to replace <<LOCATION>> in the query.
CREATE EXTERNAL TABLE s3\_object\_inventory( bucket string, key string, version\_id string, is\_latest boolean, is\_delete\_marker boolean, size bigint, last\_modified\_date bigint, storage\_class string, is\_multipart\_uploaded boolean, replication\_status string, encryption\_status string, intelligent\_tiering\_access\_tier string, bucket\_key\_status string) PARTITIONED BY ( dt string)ROW FORMAT SERDE 'org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe' STORED AS INPUTFORMAT 'org.apache.hadoop.hive.ql.io.SymlinkTextInputFormat' OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.IgnoreKeyTextOutputFormat' LOCATION '<<LOCATION>>'; MSCK REPAIR TABLE `account\_cur`;MSCK REPAIR TABLE s3\_object\_inventory; Now we can query the data we have available to get a cost allocation breakdown at the prefix level.
We need to provide some information in the following queries:
We use the following part of the query to calculate the size of storage being used by the target prefix that we want to calculate the cost for:
select date\_parse(dt,'%Y-%m-%d-%H-%i') dt, cast (sum(size) as double) targetPrefixBytesfrom s3\_object\_inventorywhere date\_parse(dt,'%Y-%m-%d-%H-%i') = cast('<<YYYY-MM-DD>>' as timestamp)and key like '<<prefix>>/%'group by dt
Next, we calculate the total size of the bucket on that particular date:
select date\_parse(dt,'%Y-%m-%d-%H-%i') dt, cast (sum(size) as double) totalBytesfrom s3\_object\_inventorywhere date\_parse(dt,'%Y-%m-%d-%H-%i') = cast('<<YYYY-MM-DD>>' as timestamp)group by dt
We query the CUR table to get the cost of a particular bucket on a particular date:
select line\_item\_usage\_start\_date as dt, sum(line\_item\_blended\_cost) as line\_item\_blended\_costfrom "account\_cur"where line\_item\_product\_code = 'AmazonS3'and product\_servicecode = 'AmazonS3'and line\_item\_operation = 'StandardStorage'and line\_item\_resource\_id = '<<bucket\_name>>'and line\_item\_usage\_start\_date = cast('<<YYYY-MM-DD>>' as timestamp)group by line\_item\_usage\_start\_date
Putting all of this together, we can calculate the cost of a particular prefix (folder or a file) on a specific date. The complete query is as follows:
withcost as (select line\_item\_usage\_start\_date as dt, sum(line\_item\_blended\_cost) as line\_item\_blended\_costfrom "account\_cur"where line\_item\_product\_code = 'AmazonS3'and product\_servicecode = 'AmazonS3'and line\_item\_operation = 'StandardStorage'and line\_item\_resource\_id = '<<bucket\_name>>'and line\_item\_usage\_start\_date = cast('<<YYYY-MM-DD>>' as timestamp)group by line\_item\_usage\_start\_date),total as (select date\_parse(dt,'%Y-%m-%d-%H-%i') dt, cast (sum(size) as double) totalBytesfrom s3\_object\_inventorywhere date\_parse(dt,'%Y-%m-%d-%H-%i') = cast('<<YYYY-MM-DD>>' as timestamp)group by dt),target as (select date\_parse(dt,'%Y-%m-%d-%H-%i') dt, cast (sum(size) as double) targetPrefixBytesfrom s3\_object\_inventorywhere date\_parse(dt,'%Y-%m-%d-%H-%i') = cast('<<YYYY-MM-DD>>' as timestamp)and key like '<<prefix>>/%'group by dt)select target.dt,(target.targetPrefixBytes/ total.totalBytes * 100) percentUsed,cost.line\_item\_blended\_cost totalCost,cost.line\_item\_blended\_cost*(target.targetPrefixBytes/ total.totalBytes) as prefixCostfrom target, total, costwhere target.dt = total.dtand target.dt = cost.dt
The following screenshot shows the results table for the sample data we used in this post. We get the following information:

To stop incurring costs, be sure to disable Amazon S3 Inventory and Cost and Usage Reports when you’re done.
Delete the S3 buckets created for the Amazon S3 Inventory reports and Cost and Usage Reports to avoid storage charges.
Amazon S3 Storage Lens can provide a single view of object storage usage and activity across your entire Amazon S3 storage. With S3 Storage Lens, you can understand, analyze, and optimize storage with over 29 usage and activity metrics and interactive dashboards to aggregate data for your entire organization, specific accounts, Regions, buckets, or prefixes. All of this data is accessible on the Amazon S3 console or as raw data in an S3 bucket.
S3 Storage Lens doesn’t provide cost analysis based on an object or prefix in a single bucket. If you want visibility of storage usage and trends across the entire storage footprint along with recommendations on cost efficiency and data protection best practices, S3 Storage Lens is the right option. But if you want a cost analysis of specific S3 buckets and looking for ways to get cost allocation of S3 objects at the object or prefix level, the solution in this post would be the best fit.
In this post, we detailed how to create a cost breakdown model at the object or prefix level for S3 buckets that contains data for multiple business units and applications. We used Athena to query the reports and datasets produced by the AWS CUR and Amazon S3 Inventory features that, when correlated, give us the cost allocation at the object and prefix level. This solution gives you an easy way to calculate costs for independent objects and prefixes, which can be used for internal chargebacks or just to know the per-object or per-prefix spending in a shared S3 bucket.
Dagar Katyal is a Senior Solutions Architect at AWS, based in Chicago, Illinois. He works with customers and provides guidance for key strategic initiatives important for their business. Dagar has an MBA and has spent years over 15 years working with customers on projects on analytics strategy, roadmap, and using data as a key differentiator. When not working with customers, Dagar spends time with his family and doing home improvement projects.
Saiteja Pudi is a Solutions Architect at AWS, based in Dallas, Tx. He has been with AWS for more than 3 years now, helping customers derive the true potential of AWS by being their trusted advisor. He comes from an application development background, interested in Data Science and Machine Learning.