Skip to content

Query the size of files per year of an S3 using Athena

0

Hi,

I am looking to compute the total size of my files per year inside a given S3 bucket. I have been trying multiple methods:

  • I tried to use a goto3 script but I constantly ran into issues or inconsistencies with the expected size vs actual size returned by the script. I expected 66 TB but only got 40 TB returned. And when I tried with a bigger bucket that has 460 TB, the code ran into synchronization issues. Here is the code I tried:
def get_bucket_size_per_year(bucket_name):
    s3 = session.resource('s3')
    bucket = s3.Bucket(bucket_name)

    size_per_year = defaultdict(int)

    for obj in bucket.objects.all():
        year = obj.last_modified.year
        size_per_year[year] += obj.size

    return size_per_year

bucket_name = '[redacted]'
size_per_year = get_bucket_size_per_year(bucket_name)

for year, size in size_per_year.items():
    print(f"Year: {year}, Size: {size} bytes")

Then I tried this, thinking it would give me all the 66 TB instead of only 40 TB:

def get_bucket_size_per_year(bucket_name):
    s3 = session.resource('s3')
    bucket = s3.Bucket(bucket_name)

    size_per_year = defaultdict(int)

    for obj in bucket.object_versions.all(): # <---------- This is what changed
        year = obj.last_modified.year
        size_per_year[year] += obj.size

    return size_per_year

bucket_name = [redacted]
size_per_year = get_bucket_size_per_year(bucket_name)

sorted_size_per_year = dict(sorted(size_per_year.items()))

for year, size in sorted_size_per_year.items():
    print(f"Year: {year}, Size: {size} bytes")

def bytes_to_terabytes(bytes_value):
    return bytes_value / (1024 ** 4)

sizes_in_terabytes = {year: bytes_to_terabytes(size) for year, size in sorted_size_per_year.items()}
total_size_tb = sum(sizes_in_terabytes.values())

print(f"The total size across all years is {total_size_tb:.2f} TB")

I quickly gave up on that and decided to directly query in Athena the metadata. However, now I run into issues about the path not matching the key:

SELECT
   EXTRACT(YEAR FROM "$file_modified_time") AS last_modified,
    SUM("$file_size") as size
FROM 
    s3_bucket_objects
    group by EXTRACT(YEAR FROM "$file_modified_time"), "$file_size" limit 100;

The error:

HIVE_CANNOT_OPEN_SPLIT: Error opening Hive split s3://[redacted] (offset=0, length=446567): com.amazonaws.trino.exceptions.UnrecoverableS3OperationException: com.amazonaws.services.s3.model.AmazonS3Exception: The specified key does not exist. (Service: Amazon S3; Status Code: 404; Error Code: NoSuchKey; Request ID: 01Z766JH580JX2D1; S3 Extended Request ID: vFUf8sixbDmEKdY2ouZEIfLJztaWM50fE8DltuUB+WWYX7CswdyyHyZEb8HpKBfdgkwSZpX0vzCWqKESEsB77Lk8b2SXXNom; Proxy: null), S3 Extended Request ID: vFUf8sixbDmEKdY2ouZEIfLJztaWM50fE8DltuUB+WWYX7CswdyyHyZEb8HpKBfdgkwSZpX0vzCWqKESEsB77Lk8b2SXXNom (Bucket: [redacted]/.json)

I found out the issue was because the key, uri path has two "//" back after the other. So athena is looking for the path [redacted]/.json, but in reality in the s3 bucket it is: [redacted]//.json. How can I fix my query? Any suggestion on how to deal with this? Should I stick to the goto3 code instead?

2 Answers
-1

I think you have differences from your code output and actual most likely because your code is not catering for versioning of objects. That is, it's calculating storage based on the most recent version. You can try the following code to calculate total storage including versioning.

def get_bucket_size_per_year_including_versions(bucket_name):
    s3 = session.client('s3')
    paginator = s3.get_paginator('list_object_versions')

    size_per_year = defaultdict(int)

    for page in paginator.paginate(Bucket=bucket_name):
        versions = page.get('Versions', [])
        for obj in versions:
            year = obj['LastModified'].year
            size_per_year[year] += obj['Size']

    return size_per_year

Also, I'm wondering why you calculating storage in this way instead of either checking metrics in cloudwatch or taking advantage of S3 inventory. With S3 inventory, you can get a csv file listing all the objects including versioning, filename, date, and bytes. You can then use that csv and code it to get the required data.

Note, with your current approach, you are most likely incurring HEAD request charges. With 460TB of data and therefore there are many files, the HEAD request charges could be high, where inventory charges would be lower.

EXPERT

answered a year ago

AWS
EXPERT

reviewed 9 months ago

  • The reason I am using this current approach is because, Cloudwatch metrics only go as far as 15 months back. My bucket is older than that. It has been created in 2020, hence why I wanted to go through it programatically as tools offered by aws dont seem to give me exactly what I want. From what I understand about Amazon S3 Inventory, it doesn't automatically provide historical data from four years ago. It generates reports based on current configuration, either daily or weekly, which is not what I am looking for as I already have s3 access logs enabled. I just somehow can't get to do a proper athena query to compute the data.

  • Hi. Are you trying to get information about existing storage and files in S3 or are you looking for historic data e.g. a month ago or 6 month ago?

-1

To query the size of files per year in an S3 bucket using Athena, you can use S3 Inventory reports combined with Athena queries. Here's a step-by-step approach to achieve this:

  1. First, set up S3 Inventory for your bucket. This will generate daily or weekly reports of your objects and their metadata.

  2. Once you have S3 Inventory configured, you can use Athena to query these inventory reports.

  3. Create an external table in Athena that points to your S3 Inventory data. The exact schema will depend on the fields you've included in your inventory report, but it should include at least the object size and last modified date.

  4. After creating the table, you can run a query like this to get the total size of files per year:

SELECT 
    YEAR(from_iso8601_timestamp(last_modified)) AS year,
    SUM(size) AS total_size_bytes
FROM 
    your_inventory_table
GROUP BY 
    YEAR(from_iso8601_timestamp(last_modified))
ORDER BY 
    year

This query assumes your inventory table has columns named 'last_modified' and 'size'. Adjust these column names if they're different in your inventory setup.

To convert the result to terabytes, you can modify the query like this:

SELECT 
    YEAR(from_iso8601_timestamp(last_modified)) AS year,
    SUM(size) / (1024.0 * 1024 * 1024 * 1024) AS total_size_tb
FROM 
    your_inventory_table
GROUP BY 
    YEAR(from_iso8601_timestamp(last_modified))
ORDER BY 
    year

This approach using S3 Inventory and Athena should give you accurate results and work well even for large buckets. It avoids the issues you encountered with the boto3 script and the problems with file paths in your previous Athena query.

Regarding the double slash issue you encountered, this is likely due to how the S3 paths are being interpreted. Using S3 Inventory as suggested above should avoid this problem entirely, as the inventory reports will contain the correct object information without you needing to construct the paths manually.
Sources
Calculate Amazon S3 bucket size and number of objects | AWS re:Post
Unlocking efficient data lifecycle management for Amazon S3 with last access times | AWS re:Post

answered a year ago

AWS
EXPERT

reviewed 9 months ago

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.