Everybody keeps repeating the same headline numbers from the Veeam V13 release notes when it comes to the new Veeam V13 immutability model. 40% less storage, an 8x reduction in retention calls and more than 2x fewer object storage API calls in total. I recently presented this exact topic at our German Veeam Usergroup in Cologne, and the number one question afterwards was basically: “Falko, does it really work like that ?”
So instead of trusting the marketing slides, I built a lab, turned on S3 audit logging and counted every single API call myself. I guess most people already know that I love object storage In this blog post I want to walk you through what I did and what the logs actually revealed.
Object Storage has quietly become the most important repository type in the whole Veeam world, and the reworked immutability engine in Veeam V13 pushes this even further. With V13, every job type can write directly to object so there are no exceptions and no workarounds. That is a pretty big architectural shift, and it comes with a completely reworked immutability model which Veeam claims is way more efficient. However, claims like that are easy to make and hard to prove, so I wanted actual data and not a nice looking slide.
If you have followed my Object Storage for Veeam blog series or my post on why Immutable Storage is not enough, you already know I like to dig into these topics a bit deeper. So let’s do exactly that again !
Why Object Storage for Veeam ?
Before we get to the results, it helps to understand why Object Storage gets so much attention in the first place. In my opinion it is simply the best repository type you can run with Veeam nowadays, and V13 only makes that case stronger.
It is universal. With Veeam V13 every job type goes direct to object, one protocol for everything and no separate landing zone you need to build first.
It is secure by design. You get TLS / HTTPS natively on port 443, and Object Lock immutability kicks in the very moment data is written. There is no separate hardening step you have to perform, which is why I like to call it “inline immutability”.
It needs no repository servers. S3 compatible object storage is stateless, so basically you need Ethernet and nothing else. Performance scales automatically across the storage backend, which means there are no repository servers to buy, size, patch or multiply.
And it gives you one pool with total flexibility. A single target can serve all your jobs, with automatic bucket creation and a pool that expands dynamically. No volume resizing, no capacity planning per repository.
That combination being universal, immutable, serverless and elastic is exactly why so many admins are moving their backups to S3 compatible targets. And it is also why the efficiency of the immutability model matters so much once you run this at scale. Of course there is even more “Why’s” for Object Storage such as operational “easyness” but I wanted to keep it short here.
The Veeam V13 immutability model, what actually changed ?
The headline change in V13 is how immutability is enforced, and this is the part which drives both the storage savings and the reduction in API calls. The Veeam V13 immutability logic is a completely different approach compared to V12.
In Veeam V12 immutability is chain-based. The entire backup chain is treated as a single unit (checkpoints), so every new incremental backup recursively extends the lock on all previous files in the chain. The result is something I like to call “storage creep”: older blocks stay locked much longer than the retention policy actually requires, and Veeam has to fire off a lot of API calls to keep extending the Retain-Until-Date across the whole chain. On top of that, predicting the exact expiration date for a specific block becomes really difficult.
In Veeam V13 immutability is restore point-based, so essentially block-level. Each data block gets its lock calculated and set once, exactly when it is written, and after that the lock is rarely touched again. Data unlocks precisely when the retention policy expires, which frees up space sooner. Fewer lock extensions obviously mean fewer API calls, faster backup windows and a highly predictable expiration. The lock duration simply equals your immutability setting.
V12 and V13 Immutability model Comparison side by side
| Aspect | Veeam V12 (chain-based) | Veeam V13 (restore point-based) |
|---|---|---|
| Logic basis | Entire chain treated as one unit | Each restore point / block treated individually |
| Lock mechanism | Recursive extension across all prior files | set once when the block is written |
| Storage impact | “storage creep” keeps old blocks locked | Optimized data unlocks when retention expires |
| API costs | High & constant PutObjectRetention updates | Low locks are not repeatedly extended |
| Performance | Slower due to lock extension overhead | Faster and more efficient backup windows |
| Predictability | Hard to predict block expiration | Lock duration equals the immutability setting |
And you can even see this difference right in the Veeam console. With V13 every restore point carries its own Immutable Until date, exactly as the restore point-based model promises.
One important detail: this new architecture currently applies to Direct to Object backups only, so image-based backups written straight to object storage. On-premises object storage is much more impacted by storage creep because its capacity is strictly limited, and that is exactly where Veeam focused first. Once the model has proven itself stable, Veeam has stated they will bring it to the Capacity Tier of the scale-out backup repository as well.
What did Veeam actually promise ?
The official release notes phrase it like that. A 40% reduction in object storage space, an 8x reduction in PutObjectRetention calls, a 3x reduction in ListBucketcalls and a more than 2x reduction in total API calls. Either way, the promise is clear. The only question left is whether it holds up in a real lab
How I tested it
The environment looked like this:
- Veeam V12 (build 12.3.2.4465) writing to a bucket named v-12 to test Veeam V12 Immutability
- Veeam V13 (build 13.0.1.180) writing to a bucket named v-13 to test Veeam V13 Immutability
- One single Linux VM as the workload
- Active Full with 30 days retention
- 30 days immutability set on the bucket
- Storage Optimization set to 1 MB, everything else left at default
- Default block generation period of 10 days for S3 compatible storage
Here are the screenshots from the v-13 bucket, the same goes for the v-12 bucket.
And the method was easy. I enabled S3 audit logging on the object storage, so that every single API call hitting the member bucket was recorded to a dedicated log target. After each Veeam operation I pulled the logs (using the S3 Browser) and then cleared them, so every run stayed quite comparable.
Enabling S3 Audit Logging on my Object Storage System
I’m using an Everpure FlashBlade here for the object storage testing and the audit policy I created captures every API call against the member bucket and writes it to a dedicated log target bucket. This is the raw API data behin all the numbers you can see in this blog post.
While Object and Bucket access auditing was always there, the auditing of S3 API Calls came into the FlashBlade platform in March 2026. Based on filters you can define a scope to audit everyting or only specifc API calls, prefixed and more. In this case we just monitor everything.
The Python script to actually count and collect
A little Python script then parsed the log files and counted the API calls per operation type, and finally plotted how many PutObject, PutObjectRetention, ListBucketObjectVersions and other calls each version produced.
I published the script I used on Github so you can see it here: https://github.com/falkobanaszak/s3-api-calls
| import pandas as pd import matplotlib.pyplot as plt import json import glob import os
# Folder mit den Log Files log_directory = '/Users/fbanaszak/Downloads/veeamapi' # Suche nach .log, .json and .jsonl files file_patterns = ["*.log", "*.jsonl", "*.json"]
all_events = []
print("--- Starting Analysis ---")
# Alle Files im angegebenen Directory durchsuchen files_to_process = [] for pattern in file_patterns: files_to_process.extend(glob.glob(os.path.join(log_directory, pattern)))
if not files_to_process: print(f"Keine Files mit {file_patterns} in '{log_directory}' gefunden.") else: print(f"{len(files_to_process)} Files gefunden - jetzt lese Ich die mal ein....")
for file_path in files_to_process: with open(file_path, 'r', encoding='utf-8') as f: for line in f: line = line.strip() if not line: continue try: obj = json.loads(line) if 'eventName' in obj: all_events.append(obj['eventName']) except json.JSONDecodeError: # Falls JSON falsch formatiert ist wird hier ignoriert continue
# Ergebnisse und alles was eingelesen wird, wird jetzt verarbeitet in nen Chart if not all_events: print("No valid 'eventName' entries foud.") else: # Panda Series Konvertierung, braucht Python anscheinend um besser zu zählen, keine Ahnung wieviele Stunden ich hier verschwendet habe... series = pd.Series(all_events) counts = series.value_counts()
print(f"\nErgebnis der Zählung:") print(counts)
# Bar Chart generieren plt.figure(figsize=(12, 8))
# Bar Chart counts.plot(kind='bar', color='steelblue', edgecolor='black')
plt.title(f'Verteilung aller API-Calls ({len(all_events)} Events insgesamt)', fontsize=14) plt.xlabel('API Call (eventName)', fontsize=12) plt.ylabel('Häufigkeit', fontsize=12)
# Damit die Namen der API Calls nicht überlappen plt.xticks(rotation=45, ha='right')
# Gitter für bessere Lesbarkeit plt.grid(axis='y', linestyle='--', alpha=0.7)
plt.tight_layout()
# Speichern oder Anzeigen # plt.savefig('api_calls_chart.png') # Optional: als Bild speichern plt.show() |
The log files produced by the logging I activated on the Object Storage system (in this case an Everpure FlashBlade) look like that:
You can see the little red arrow, which are the “eventName” entries the Python script is collecting and plotting.
Every table in this blog post is based on the outputs from the python script, which prints the results to the console but also plots it into a chart. The charts look like this:
Active Full with Veeam V12
Active Full with Veeam V13
Test Sequence
The test sequence was: one Active Full with V12, pull and delete logs. Then a weekend of incremental runs with V12, pull and delete logs. And then exactly the same two steps again with V13. That gave me two nice, clean comparisons, a single Active Full and a full weekend of incrementals for storage consumption, total API calls and, most importantly, PutObjectRetention.
By the way, a quick look at a raw log line confirms the immutability metadata is sitting right there on every write. Each PutObject event carries x-amz-object-lock-mode: COMPLIANCEtogether with an x-amz-object-lock-retain-until-date ,which is exactly the Object Lock behaviour we want to see for immutable backups.
Results of the Active Full backup
The first comparison is a single Active Full of one VM. Here is what my Python counter returned.
| API operation | V12 (calls) | V13 (calls) | Difference | Change |
|---|---|---|---|---|
| PutObject (data upload) | 132,284 | 154,233 | + 21,949 | + 16% |
| ListBucketObjectVersions | 258 | 198 | – 60 | – 23% |
| GetObject (index / metadata reads) | 189 | 74 | – 115 | – 61% |
| HeadObject / HeadBucket | 72 | 43 | – 29 | – 40% |
| ListObjects / ListObjectsV2 | 39 | 5 | – 34 | – 87% |
| GetBucketLocation / Acl / Cors / etc. | 37 | 0 | – 37 | – 100% |
| PutObjectRetention | 15 | 12 | – 3 | – 20% |
PutObject went up by roughly 16%, which is expected, that is the actual data being written, and it is the cheap high-volume operation you never really worry about in object storage. Everything else went down, and some categories collapsed completely.
First findings after the Active Full
Three things jumped out at me immediately.
First, there are clearly far fewer “search” requests. In V12, Veeam constantly asks the bucket where it is (GetBucketLocation), how it is configured and which objects live in it. In V13 this behaviour is almost completely eliminated. ListObjects In particular dropped by nearly 90% and ListObjects is a so-called “Class A” request, meaning it is one of the more expensive call types.
Second, there is much better metadata handling. GetObject fell from 189 to 74 calls, which tells me V13 does not need to “look up” what is already sitting in the bucket nearly as often. It looks like Veeam optimized the local cache or the index management to require fewer read accesses against the S3 backend.
Third, there are no more redundant inits. V12 ran a whole batch of checks on every run (GetBucketAcl, GetBucketCors , GetPublicAccessBlock), and in V13 those simply do not appear anymore.
Ok let’s run the incrementals now and see what the differences are.
Incrementals with Veeam V12 Immutability
Incrementals with Veeam V13 Immutability
Results of a whole weekend of incrementals
The Active Full is of course only half the story. The immutability model really shows its value during the ongoing incremental runs, because that is exactly when V12 keeps extending locks across the chain. Here is the weekend comparison.
| API operation | V12 (calls) | V13 (calls) | Difference | Change |
|---|---|---|---|---|
| PutObject (data upload) | 1,919 | 181,905 | + 179,986 | see note |
| ListBucketObjectVersions | 14,501 | 6,813 | – 7,688 | – 53% |
| GetObject (reads) | 6,402 | 5,778 | – 624 | – 10% |
| HeadObject (metadata) | 5,196 | 3,207 | – 1,989 | – 38% |
| PutObjectRetention (lock) | 1,770 | 299 | – 1,471 | – 83% |
The PutObject line looks dramatic, and honestly I cannot fully explain the size of that jump tbh. But as I said above, PutObject is not the operation that hurts you in object storage. It is a cheap and expected data-write call, so I am really not losing any sleep over it. Furthermore, this is a lab and I possibly have done stupid things there.
The rows that matter tell exactly the story Veeam promised. ListBucketObjectVersions those expensive “what is in here ?” lookups dropped by more than 50 %
The Object Lock efficiency is the real headline
The single most convincing number for me is PutObjectRetention : 1,770 calls in V12 versus only 299 calls in V13, which is an 83% reduction. This is the API call Veeam uses to extend immutability locks, and it is precisely where the difference between chain-based and restore point-based becomes clear. V12 keeps re-stamping the retention dates across the chain, while V13 sets the lock once and moves on.
This maps almost perfectly onto Veeam’s claim of an 8x reduction in PutObjectRetention calls. In other words: Veeam actually isn’t lying ! And this was not a “best conditions” test either it was a straightforward lab test with default settings.
A quick word on S3 API call costs
Why do I keep obsessing over ListObjects and PutObjectRetention instead of that huge PutObject count ? Because S3 API calls are not priced equally at all. They fall into different cost classes.
| Class | Example operations | Cost factor |
|---|---|---|
| Class A | PutObject, ListObjects, ListBucketObjectVersions, CopyObject | Expensive (~$0.005 per 1,000 calls) |
| Class B | GetObject, HeadObject, HeadBucket | Cheap (~$0.0004 per 10,000 calls) |
| Free / Management | DeleteObject | Often free |
(These are public S3 pricing numbers , use them as a directional guide.. Source = Trust me bro (or AWS calculators) )
The expensive Class A operations especially the list and the retention calls are exactly the ones V13 slashes. And that is what turns “fewer API calls” from a nice sounding stat into a real line item on your bill.
A little TCO example
To make the cost impact a bit more tangible, let’s imagine these call counts running against AWS S3 pricing.
| Category | Example operations | V12 (count) | V12 cost | V13 (count) | V13 cost |
|---|---|---|---|---|---|
| Class A | PutObject, List, Retention | 189,567 | $0.9478 | 18,969 | $0.0948 |
| Class B | GetObject, HeadObject | 9,518 | $0.0004 | 12,531 | $0.0005 |
| Total | 199,085 | $0.9482 | 31,500 | $0.0953 |
The absolute numbers here are tiny, because this is one single VM. But now imagine these were petabytes across a full production estate ! The Class A cost alone drops by roughly 10x. So Veeam is effectively saving you money through a pure software optimization, without you having to change your storage or your process. Especially for Service Providers who bill on QoS and API consumption this difference compounds really quickly.
What I would still like to test
I want to be honest about the limits of this test. It is one single VM over one single weekend, not a full 30 day run at scale. To make the results even more meaningful you would actually want to let the whole thing run for the complete 30 days retention window (with the 10 day block generation period fully in play) and push far more data through it, so you can really see the effect on large datasets instead of a single workload. Storage consumption in particular is not very meaningful with only one VM.
So here are a couple of follow-up tests I would love to run:
- CPU and RAM utilization on the Veeam components during runtime
- Storage performance during runtime
- Consumption at scale, with a lot more workloads than just one
- Restores Veeam says nothing about how V12 and V13 behave differently on restores from object storage, and I think that would be genuinely interesting to measure
- Full TCO calculations, which could be especially spicy for Service Providers
If you want to benchmark your own object storage properly before you even get to this point, have a look at my Veeam Object Storage Benchmarking – Part 4 post where I cover exactly that with Warp from Minio.
Wrap up on Veeam V13 immutability
So, do the Veeam V13 immutability improvements really live up to the hype ? Based on my logs yes, they really do. In a clean lab with default settings, the shift from chain-based to restore point-based immutability produced exactly the pattern Veeam promised: a big drop in expensive Class A operations, a collapse of redundant metadata and init calls, and an 83% reduction in PutObjectRetention calls during the ongoing incrementals. The PutObject count did go up, but that is the cheap and expected data-write operation which never drives your bill anyway.
Ultimately, if you are running Veeam against on-premises S3 compatible object storage, the V13 immutability model is a meaningful efficiency upgrade ! Less storage creep, fewer API calls and lower cost while keeping the exact same immutability guarantees you already rely on. I would still love to validate this over a full 30 day cycle at scale, and I will definitely keep testing.
Did you run your own before-and-after on V13 already ? I would love to compare experiences, so feel free to drop a comment below or reach out to me. And if you want more on this topic, make sure to check out all my Veeam blog posts
The original post can be found here: https://www.virtualhome.blog/2026/07/22/veeam-v13-immutability/










