All posts

Claude Sonnet vs. DeepSeek V4 Flash: An S3 Storage Showdown

One simple automation task, two very different AI coding experiences — and a 10x price gap.

S3 storage is great. Reliable, convenient, and cheap. Except when it isn't.

That's the trap, really. S3 is so easy to use that it becomes way too tempting to dump huge amounts of "temporary" data into a bucket and then just… forget about it. Nobody sets out to waste money. It happens gradually: a test export here, a debug dump there, a "just for now" backup that quietly becomes permanent. Usage creeps up, and so does the monthly bill, one forgotten object at a time.

To keep that creep under control, I wanted something simple: a script that checks the storage used across every bucket we own and sends me a report on a schedule, so I actually notice when things start piling up. Nothing fancy. The kind of task that's perfect for handing off to an LLM.

So, just for fun, I asked two different models to write it: Claude Sonnet, and the new kid on the block, DeepSeek V4 Flash. I don't have the hardware to run DeepSeek locally, so I used their cloud offering instead. The results were… interesting.

claude-deppseek-1

Round One: Claude Sonnet

Claude was a real champ. It quickly and effectively produced the script — no fuss, no frills. Within a few minutes, and without any hand-holding, I had a working first version.

From there I went back and forth with it a couple of times, but only on cosmetic stuff: logging so I could see what was happening during a run, a delta comparison against the previous week to track whether usage was trending up or down, and sorting by size so the biggest offenders jump out immediately.

That was it. Reliable and fast — exactly what you want from a tool you're going to trust with a recurring job.

Round Two: DeepSeek V4 Flash

Then it was DeepSeek's turn. Same goal, but this time I front-loaded the prompt with everything I'd learned from the Claude round — logging and the delta calculation requested from the start instead of added later.

Boy, this one is a thinker. It meditated on the problem for a good while before producing anything — minutes, not the tens of seconds Claude took. At least an order of magnitude slower just to get a first draft.

And here's the fun part: the first version didn't work. Every single bucket reported zero usage, with a handful of error messages scattered through the logs — clearly some API calls were going wrong.

I reported the problem, but kept it deliberately vague: usage shows as zero, there are some errors in the log. No pasted error messages, no pointing at specific lines. I wanted to see if it could figure it out on its own.

It chewed on that for a couple more minutes and came back with a second version. And this one worked. Not just worked — it correctly diagnosed the root cause from a vague description alone, fixed it, and the resulting script actually ran faster than Claude's.

You can still see the scar tissue from that bug in the final code. The problem was in how the CloudWatch BucketSizeBytes metric gets queried — get the StorageType dimension wrong and CloudWatch silently returns no datapoints, which the script dutifully reports as zero. DeepSeek's fix wasn't just correcting the one query; it built a three-stage fallback chain so the script degrades gracefully instead of lying:

def fetch_bucket_sizes(cw, bucket, now, old_time):
    """Query CloudWatch for a bucket's current and historical size in bytes."""
    start = old_time - timedelta(hours=12)

    # 1) Documented total across all storage classes.
    datapoints = _query_metric(cw, bucket, ALL_STORAGE_TYPES, start, now)
    # 2) Plain BucketName query (works when CloudWatch aggregates the omitted dimension).
    if not datapoints:
        datapoints = _query_metric(cw, bucket, None, start, now)
    # 3) Sum of the per-storage-class metrics.
    if not datapoints:
        datapoints = _merge_types(
            [_query_metric(cw, bucket, st, start, now) for st in STORAGE_TYPES]
        )
    ...

That's a model that got burned once and decided it would never trust that API again.

Two Very Different Coding Styles

Once both scripts were working, I sat down and read the code. The contrast turned out to be just as interesting as the back-and-forth that produced it.

Claude's script was fairly monolithic: a couple of big functions doing most of the heavy lifting, start to finish. Not messy — Claude is tidy even when it goes big — but the kind of code where you need to read a good chunk of a function to understand everything it does. Here's the heart of its main(), walking the buckets one by one:

for idx, b in enumerate(buckets, start=1):
    bucket_name = b["Name"]
    logger.info("[%d/%d] %s: locating region...", idx, total, bucket_name)

    region = get_bucket_region(s3_client, bucket_name)
    cw_client = session.client("cloudwatch", region_name=region)

    logger.info("[%d/%d] %s: fetching current size (region=%s)...",
                idx, total, bucket_name, region)
    current_bytes = get_size_at(cw_client, bucket_name, now)
    past_bytes = get_size_at(cw_client, bucket_name, past)

    if current_bytes == 0 and past_bytes == 0:
        logger.info("[%d/%d] %s: empty on both dates, skipping", ...)
        continue
    ...

Clear, readable, sequential — and everything happens inline in the loop.

DeepSeek went the opposite direction. Its script was broken into small, focused functions, each doing one clear thing: fetch the bucket list, compute a single bucket's size, format a log line, calculate the delta. Even the helpers have helpers — human_size(), format_delta(), pick_sizes(), bucket_region(), each a few lines with a docstring. It reads like something a careful engineer would write for a codebase meant to be maintained long-term, rather than a one-off script.

That structural difference also explains the speed gap. Because DeepSeek had already isolated the per-bucket logic in one self-contained function, wiring in concurrency was almost trivial — the whole parallelization is just this:

def process(bucket):
    region = bucket_region(s3, bucket, default_region)
    cw = get_cw_client(cw_clients, lock, session, region)
    current, old = fetch_bucket_sizes(cw, bucket, now, old_time)
    return bucket, region, current, old

with ThreadPoolExecutor(max_workers=args.workers) as pool:
    futures = {pool.submit(process, name): name for name in bucket_names}
    for future in as_completed(futures):
        ...

Sixteen buckets in flight at once (the worker count is even a CLI flag), with a thread-safe cache for the per-region CloudWatch clients. Claude's more monolithic version checked buckets sequentially. The parallelism is the real reason DeepSeek's final script outran Claude's — not a smarter algorithm, just a structure that made concurrency easy to bolt on.

claude-deppseek-2

So Who Wins?

Here's where it gets nuanced. DeepSeek can get to the right answer, and once it does, the result is solid — better structured, more maintainable, and thanks to that modularity, faster at runtime. But Claude got there first, with fewer detours, and without ever handing me a broken script along the way.

First-attempt precision goes to Claude; problem-solving grit under a vague bug report and better software engineering instincts go to DeepSeek.

But there's one more variable that tips the scale: cost.

The Price Tag

Once both scripts were working, I asked each model the same question: "Estimate the cost of this conversation: how much did getting to the final script cost me in dollars?"

Claude's estimate: roughly $0.50–$0.70.

DeepSeek's estimate: 2 to 4 cents.

That's not a small gap. That's an order of magnitude — maybe more — and it's no joke when you're running this kind of thing across a team, or repeating similar tasks dozens of times a month. DeepSeek took longer, stumbled once, and needed a second pass, but even accounting for the extra round trip, it came out dramatically cheaper.

Takeaways

So, which one should you reach for? It depends on what you're optimizing for.

If you want something fast, reliable, and right on the first try — the kind of tool you can hand a task to and walk away from — Claude Sonnet delivered exactly that. If you're cost-sensitive, don't mind some back-and-forth, and are willing to trade speed and a rocky first attempt for a much smaller bill, DeepSeek V4 Flash deserves serious consideration.

Either way, both models eventually got me exactly what I needed: a script that watches our S3 buckets so those "temporary" files don't quietly turn into a permanent line item on the AWS invoice. And that's the whole point of automating this kind of housekeeping — set it up once, let it run, and stop finding surprises at the end of the month.