<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Yoshiki Fujiwara(藤原 善基)@AWS Community Builder</title>
    <description>The latest articles on DEV Community by Yoshiki Fujiwara(藤原 善基)@AWS Community Builder (@yoshikifujiwara).</description>
    <link>https://dev.to/yoshikifujiwara</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1143688%2F2e0886ff-292c-4e8f-a588-bc7629c2321b.jpeg</url>
      <title>DEV Community: Yoshiki Fujiwara(藤原 善基)@AWS Community Builder</title>
      <link>https://dev.to/yoshikifujiwara</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yoshikifujiwara"/>
    <language>en</language>
    <item>
      <title>Why Amazon FSx for NetApp ONTAP block performance differed by about 2x on the same specified values, isolated by measurement</title>
      <dc:creator>Yoshiki Fujiwara(藤原 善基)@AWS Community Builder</dc:creator>
      <pubDate>Wed, 30 Sep 2026 05:43:42 +0000</pubDate>
      <link>https://dev.to/aws-builders/why-amazon-fsx-for-netapp-ontap-block-performance-differed-by-about-2x-on-the-same-specified-35pi</link>
      <guid>https://dev.to/aws-builders/why-amazon-fsx-for-netapp-ontap-block-performance-differed-by-about-2x-on-the-same-specified-35pi</guid>
      <description>&lt;h1&gt;
  
  
  Why Amazon FSx for NetApp ONTAP block performance differed by about 2x on the same specified values, isolated by measurement
&lt;/h1&gt;

&lt;p&gt;While measuring, I hit a case where the templates and the specified values matched, yet the number&lt;br&gt;
moved every time I measured. Rather than let it go, I wanted to isolate whether it was a&lt;br&gt;
measurement mistake or something about the environment, so I eliminated the causes one at a time.&lt;/p&gt;

&lt;p&gt;The same thing shows up when a post-migration performance test does not agree, and when you want to&lt;br&gt;
settle on one representative value while sizing an initial build. It is a question that eats a lot of&lt;br&gt;
time, so this article is the record of that isolation.&lt;/p&gt;

&lt;p&gt;What this article does and does not cover: it covers investigating the cause of the performance&lt;br&gt;
swing, and how to isolate the bottleneck using four CloudWatch metrics. It does not cover mount&lt;br&gt;
procedures, how to confirm multipathing, or figures usable for sizing (details under "What this&lt;br&gt;
article does not cover" below). All figures are from a Single-AZ configuration; Multi-AZ is&lt;br&gt;
unverified.&lt;/p&gt;

&lt;p&gt;dev.to series: &lt;code&gt;FSx for ONTAP Block Protocols, Measured&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A 1 MiB sequential read on Amazon FSx for NetApp ONTAP (hereafter FSx for ONTAP), measured with the&lt;br&gt;
same template, the same specified values and the same workload, &lt;strong&gt;varied by 2.64x across&lt;br&gt;
environments.&lt;/strong&gt; Each one was stable within its own run. This is not measurement scatter.&lt;/p&gt;

&lt;p&gt;This is the record of eliminating the causes one at a time. It was not the path, not how the&lt;br&gt;
connection was made, not the queue count, not the distribution, and not the iopolicy (Linux's&lt;br&gt;
multipath setting for distributing I/O across paths to an NVMe device). &lt;strong&gt;What was&lt;br&gt;
acting on it was the layout on disk, which breaks after 300 seconds of 4 KiB random writes and&lt;br&gt;
comes back when the data is rewritten sequentially.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Three times along the way I nearly settled on the wrong explanation. First: "if it exceeds the&lt;br&gt;
specified value it must be served from memory." Second: "the layout must also be setting the&lt;br&gt;
amplification factor for 4 KiB random reads." Third: "the amplification factor must be decided by&lt;br&gt;
whether 4 KiB random writes ever ran on that device." &lt;strong&gt;All three were disproved once I aimed a&lt;br&gt;
measurement that could decide them.&lt;/strong&gt; All three are in here. The third had five observations&lt;br&gt;
lined up cleanly behind it and survived until a control experiment killed it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What was measured
&lt;/h2&gt;

&lt;p&gt;Assuming no prior knowledge of FSx for ONTAP, here's what varies between the deployments.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deployment 1&lt;/strong&gt;: AWS Cloud, single VPC, single AZ. One EC2 client -&amp;gt; (NVMe/TCP, 1 MiB
sequential read) -&amp;gt; Amazon FSx for NetApp ONTAP (same template, same specified values)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment 2&lt;/strong&gt;: same AWS Cloud, single VPC, single AZ. One EC2 client -&amp;gt; (same connection,
same workload) -&amp;gt; Amazon FSx for NetApp ONTAP (same template, same specified values)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The invisible difference between Deployment 1 and 2&lt;/strong&gt;: layout on disk. It never appears in
the template or the list of specified values&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FYoshiki0705%2Fs3-burst-on-ontap-files%2Fb6b83cd3d6bc516f12478138cc36bb30a3ae0ec3%2Fdocs%2F_assets%2Fimages%2Fs3burst-block-c-layout-en%402x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FYoshiki0705%2Fs3-burst-on-ontap-files%2Fb6b83cd3d6bc516f12478138cc36bb30a3ae0ec3%2Fdocs%2F_assets%2Fimages%2Fs3burst-block-c-layout-en%402x.png" alt="Two FSx for ONTAP file systems built from the same template and the same specified values&lt;br&gt;
(Deployment 1, Deployment 2), each measured from one EC2 client with the same connection and the&lt;br&gt;
same workload. Shows what can be held identical (path, connection setup, queue count,&lt;br&gt;
distribution, iopolicy) against what cannot (layout on disk)" width="800" height="726"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure: **matching the CloudFormation template and the specified values doesn't make the layout&lt;br&gt;
on disk match across a redeploy.&lt;/em&gt;* Path, how the connection is made, queue count, distribution,&lt;br&gt;
and iopolicy can all be held to the same value across deployments. Layout is the one condition&lt;br&gt;
that changes every deployment and never appears in the list of specified values.*&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Role in this article&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Deployment 1 / Deployment 2&lt;/td&gt;
&lt;td&gt;Separate FSx for ONTAP file systems, built from the same template and the same specified values&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Conditions that can be matched&lt;/td&gt;
&lt;td&gt;Path, how the connection is made, queue count, distribution, iopolicy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Condition that can't be matched&lt;/td&gt;
&lt;td&gt;Layout on disk. Breaks after 300 seconds of 4 KiB random writes, comes back when rewritten sequentially&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;What this article does not cover. Mount procedure is out of scope&lt;br&gt;
(&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/ja/verification/block-measurement-runbook.md" rel="noopener noreferrer"&gt;repository record, Japanese&lt;/a&gt;).&lt;br&gt;
Confirming whether multipathing is usable is also out of scope (&lt;a href="https://dev.to/aws-builders/amazon-fsx-for-netapp-ontap-nvmetcp-assumes-rhel-93-why-multipathing-fails-on-amazon-linux-2023-1afl"&gt;separate article&lt;/a&gt;).&lt;br&gt;
&lt;strong&gt;The figures in this article cannot be used for sizing either.&lt;/strong&gt; Why they cannot is the subject of&lt;br&gt;
the article. All figures are from a Single-AZ configuration; Multi-AZ is unverified.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;One of three, but there is no reading order.&lt;/strong&gt; The block-protocol measurements are split by&lt;br&gt;
reader.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Article&lt;/th&gt;
&lt;th&gt;Reader&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;A&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/aws-builders/moving-on-premises-block-storage-to-aws-the-divide-by-625-mbps-sizing-approach-comes-out-wrong-31pc"&gt;Sessions and queues&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;People about to mount&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;B&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/aws-builders/amazon-fsx-for-netapp-ontap-nvmetcp-assumes-rhel-93-why-multipathing-fails-on-amazon-linux-2023-1afl"&gt;Confirming whether multipathing is usable&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;People who followed the procedure and got stuck&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;C&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;What moves the numbers&lt;/strong&gt; (this article)&lt;/td&gt;
&lt;td&gt;People who measure the numbers, and people who cite them&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;None of the three presupposes the other two.&lt;/strong&gt; Read only the part you need.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The same property on the file-protocol side (NFS / SMB / S3 API) is covered in&lt;br&gt;
&lt;a href="https://dev.to/aws-builders/amazon-s3-files-vs-amazon-fsx-for-netapp-ontap-two-designs-for-using-s3-as-a-file-s3-burst-part-4p9m"&gt;S3 Burst Part 2&lt;/a&gt;&lt;/strong&gt;, with the primary record of those figures in&lt;br&gt;
&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/ja/verification/throughput-iops-concurrency.md" rel="noopener noreferrer"&gt;throughput, IOPS and concurrency (Japanese)&lt;/a&gt;.&lt;br&gt;
Those figures are also &lt;code&gt;iorate=max&lt;/code&gt; values from this environment, so &lt;strong&gt;the way of measuring&lt;br&gt;
described here applies to them unchanged.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The environment measured
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;File system&lt;/td&gt;
&lt;td&gt;FSx for ONTAP second generation SINGLE_AZ_2, 6,144 MBps x 1 HA pair, SSD 4,096 GiB, provisioned 200,000 IOPS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Region&lt;/td&gt;
&lt;td&gt;ap-northeast-1, single AZ&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ONTAP&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Not one version.&lt;/strong&gt; It spans 9.18.1P5 (the early deployments), 9.18.1P6 (the per-path measurements) and 9.18.1 (the last three deployments). &lt;strong&gt;This is exactly the unresolved candidate left at the end of this article&lt;/strong&gt; (the cross-article version mapping is in the &lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/en/reference/block-protocol-ontap-version-matrix.md" rel="noopener noreferrer"&gt;ONTAP version matrix&lt;/a&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NVMe read cache&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Disabled&lt;/strong&gt; (confirmed on both nodes)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LUN / namespace&lt;/td&gt;
&lt;td&gt;600 GiB, on a 900 GiB volume&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Before measuring&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;All 600 GiB was written once.&lt;/strong&gt; Unwritten blocks on a thin-provisioned volume return zeros, so reading without writing first measures nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client&lt;/td&gt;
&lt;td&gt;c5n.9xlarge; 50 Gbps network is the guaranteed figure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Instrument&lt;/td&gt;
&lt;td&gt;VDBENCH 5.04.07 directly, &lt;strong&gt;&lt;code&gt;iorate=max&lt;/code&gt; (unlimited)&lt;/strong&gt;, 512 threads, 60 s warm-up + 300 s measurement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test data / efficiency settings&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Not recorded whether the VDBENCH payload was compressible, or whether volume inline efficiency (&lt;code&gt;StorageEfficiencyEnabled&lt;/code&gt;) was enabled.&lt;/strong&gt; This article's subject is on-disk layout, so whether efficiency settings affected results is an unconfirmed variable&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The window is 300 seconds, so the figures in this article include burst. They cannot be cited as a&lt;br&gt;
baseline.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Every figure in this article is an &lt;code&gt;iorate=max&lt;/code&gt; saturation point, not an operating point.&lt;/strong&gt;&lt;br&gt;
No target IOPS was given; it is what the path would take. &lt;strong&gt;This distinction bears on the subject&lt;br&gt;
of the article itself.&lt;/strong&gt; A saturation point answers "where does it jam", and does not answer "at&lt;br&gt;
what response time can it be used".&lt;/p&gt;

&lt;p&gt;The file-protocol side of the same repository measured that gap. With a target of 4,400 IOPS the&lt;br&gt;
result was 4,406 MB/s at &lt;strong&gt;8.86 ms&lt;/strong&gt;; unlimited, it was 4,204 MB/s at &lt;strong&gt;121.79 ms&lt;/strong&gt;.&lt;br&gt;
&lt;strong&gt;Unlimited is 5% lower in throughput and 14x worse in response time.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Recording one &lt;code&gt;iorate=max&lt;/code&gt; point as "the ceiling" leaves behind a figure that is lower than what&lt;br&gt;
is reachable and an order of magnitude worse in response time.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Response time was recorded as measured in only the two series under "not concurrency" below.&lt;/strong&gt;&lt;br&gt;
Elsewhere the values VDBENCH printed were not kept, and the environments are torn down. But with&lt;br&gt;
512 threads fixed and no rate limit, response time is the identity &lt;code&gt;512 / IOPS&lt;/code&gt; (derived 216.6 ms&lt;br&gt;
against measured 216.588 ms, confirmed to agree). &lt;strong&gt;The figures below can be read through that&lt;br&gt;
identity&lt;/strong&gt;: 1,288.87 MB/s is about &lt;strong&gt;397 ms&lt;/strong&gt;, 3,407.60 MB/s about &lt;strong&gt;150 ms&lt;/strong&gt;, 546.02 MB/s about&lt;br&gt;
&lt;strong&gt;937 ms&lt;/strong&gt;. &lt;strong&gt;All three are derived, not measured.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The response-time-versus-load curve was not measured.&lt;/strong&gt; The file-protocol side produced a curve&lt;br&gt;
across target IOPS values and a cutoff (the IOPS at which a given latency is reached) with&lt;br&gt;
&lt;a href="https://github.com/shuichi-taketani/auto_vdbench" rel="noopener noreferrer"&gt;auto_vdbench&lt;/a&gt;, but &lt;strong&gt;the same thing could not be&lt;br&gt;
produced on the block side.&lt;/strong&gt; auto_vdbench creates test files on a mount path and cannot drive a&lt;br&gt;
raw device or an NVMe namespace. &lt;strong&gt;Open work.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The same configuration varied widely
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;This is the part of the article I would most like read carefully.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As F-3, the same shape was measured three times within one connection.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Run&lt;/th&gt;
&lt;th&gt;1 MiB sequential read&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1st&lt;/td&gt;
&lt;td&gt;3,401.49 MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2nd&lt;/td&gt;
&lt;td&gt;3,407.60 MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3rd&lt;/td&gt;
&lt;td&gt;3,407.59 MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Within one connection it reproduces to 0.18%.&lt;/strong&gt; The second and third differ by 0.01 MB/s.&lt;/p&gt;

&lt;p&gt;But &lt;strong&gt;three environments built with the same procedure, the same template and the same negotiated&lt;br&gt;
result (4 queues)&lt;/strong&gt; produced different values.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Environment&lt;/th&gt;
&lt;th&gt;1 MiB sequential read&lt;/th&gt;
&lt;th&gt;Stability within the window&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;First&lt;/td&gt;
&lt;td&gt;1,288.87 MB/s&lt;/td&gt;
&lt;td&gt;stable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Second&lt;/td&gt;
&lt;td&gt;2,366.49 MB/s&lt;/td&gt;
&lt;td&gt;stable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Third&lt;/td&gt;
&lt;td&gt;3,401-3,408 MB/s&lt;/td&gt;
&lt;td&gt;stable (0.18%)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The spread is 2.64x.&lt;/strong&gt; Each was stable within its run, so this is not measurement scatter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So you cannot size from a single figure of the form "NVMe/TCP throughput on this configuration is&lt;br&gt;
N MB/s".&lt;/strong&gt; At the point where I had measured the second environment, I was about to write 2,366.49&lt;br&gt;
as the representative value. I had not followed my own rule of repeating three times, so I noticed&lt;br&gt;
one environment later than I should have.&lt;/p&gt;

&lt;p&gt;I had put forward one candidate at the time: Asymmetric Namespace Access (the mechanism that&lt;br&gt;
tells the host which of several paths to the same namespace is the shortest one, referred to as&lt;br&gt;
multipathing throughout this article) is unusable, so&lt;br&gt;
which of the two controllers the device under measurement sits behind can change per deployment.&lt;br&gt;
&lt;strong&gt;Measurement said otherwise.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The path candidate is gone
&lt;/h3&gt;

&lt;p&gt;I brought in a kernel with multipathing enabled (Rocky Linux 9.7) and measured &lt;strong&gt;within one deployment, on&lt;br&gt;
one namespace, with the paths narrowed to one at a time.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;1 MiB sequential read&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;optimized only (the node holding the namespace)&lt;/td&gt;
&lt;td&gt;1,278.11 MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;non-optimized only&lt;/td&gt;
&lt;td&gt;1,279.07 MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;0.08% apart. Either controller gives the same figure, so &lt;strong&gt;the path cannot explain that spread.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;How the connection is made was eliminated too. Connecting from optimized first, from non-optimized&lt;br&gt;
first, and using &lt;code&gt;connect-all&lt;/code&gt; as the AWS procedure has it, gave 1,276.60 / 1,277.38 / 1,277.28&lt;br&gt;
MB/s. &lt;strong&gt;A spread of 0.06%.&lt;/strong&gt; The queue count was the same in every configuration&lt;br&gt;
(&lt;code&gt;queue_count=5&lt;/code&gt;, 10 established sockets to &lt;code&gt;:4420&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  What was acting on it was when the data being read was last written
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Within one deployment, one device, one path and one policy, rewriting the 600 GiB brings it back.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;th&gt;1 MiB sequential read&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Immediately before the rewrite (control)&lt;/td&gt;
&lt;td&gt;1,239.15 MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Immediately after the rewrite&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2,298.54 MB/s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Once more, right after that&lt;/td&gt;
&lt;td&gt;2,296.12 MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;1.85x. &lt;strong&gt;And the second read does not fall back. This is not a property that is consumed by one&lt;br&gt;
read.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is the only place in this article where a ratio is written.&lt;/strong&gt; The numerator and denominator&lt;br&gt;
come from the same file system, the same device, the same path and the same policy: the reason a&lt;br&gt;
ratio can be written here is the same reason none is written in the other sections.&lt;/p&gt;

&lt;p&gt;CloudWatch answers what the cold side is running into.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;th&gt;&lt;code&gt;DiskIopsUtilization&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Disk throughput utilization&lt;/th&gt;
&lt;th&gt;Network utilization&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cold read&lt;/td&gt;
&lt;td&gt;avg 57-60 / &lt;strong&gt;max 95.0-95.4&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;30 / 42&lt;/td&gt;
&lt;td&gt;8.7 / 11.6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Warm read&lt;/td&gt;
&lt;td&gt;avg &lt;strong&gt;13.6-14.4&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;40 / 41&lt;/td&gt;
&lt;td&gt;19.2 / 19.5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The ceiling on the cold side is SSD IOPS. It is not the 6,144 MBps of throughput capacity that&lt;br&gt;
was specified (utilization 42% or below). It is not the network either (11% or below).&lt;br&gt;
&lt;strong&gt;And at 2,298 MB/s on the warm side, none of the utilization figures FSx for ONTAP publishes is at&lt;br&gt;
its ceiling.&lt;/strong&gt; What is acting on it there is not known.&lt;br&gt;
(What is being read here is &lt;code&gt;DiskIopsUtilization&lt;/code&gt; / disk throughput utilization / network&lt;br&gt;
utilization from the&lt;br&gt;
&lt;a href="https://docs.aws.amazon.com/fsx/latest/ONTAPGuide/so-file-system-metrics.html" rel="noopener noreferrer"&gt;second-generation file system metrics&lt;/a&gt;.&lt;br&gt;
The utilization metrics emit one point per HA pair and per aggregate.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Not reads, and not elapsed time
&lt;/h3&gt;

&lt;p&gt;Since it moves with "when it was written", I aimed separate measurements at &lt;strong&gt;what brings it down.&lt;/strong&gt;&lt;br&gt;
On top of one fill, reads, elapsed time and writes were applied in turn.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Immediately after&lt;/th&gt;
&lt;th&gt;1 MiB sequential read&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The fill&lt;/td&gt;
&lt;td&gt;2,263.8 MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A second read straight after&lt;/td&gt;
&lt;td&gt;2,267.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A third read straight after&lt;/td&gt;
&lt;td&gt;2,268.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;30 minutes idle&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2,258.5&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;300 seconds of 4 KiB random writes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1,272.8&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Refilling&lt;/td&gt;
&lt;td&gt;2,235.4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reads do not bring it down.&lt;/strong&gt; Three full passes over 600 GiB, a spread of 0.2%&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time does not bring it down.&lt;/strong&gt; 30 minutes with nothing running, within 0.4%&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;4 KiB random writes bring it down.&lt;/strong&gt; 2,258.5 to 1,272.8, a fall of 1.77x&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not generalize this to "writes bring it down". What was aimed was one point: 300 seconds of&lt;br&gt;
4 KiB random writes. &lt;strong&gt;The result of separating shape from duration and aiming again is in&lt;br&gt;
the section below, and the generalization&lt;br&gt;
did not hold.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What falls is not cache but the layout on disk
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;I first explained this as "cache retention". That was wrong.&lt;/strong&gt; Read CloudWatch interval by&lt;br&gt;
interval and the mechanism comes out plainly.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;th&gt;Client&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;One read on the disk side&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;Disk IOPS&lt;/th&gt;
&lt;th&gt;Against the specified 200,000&lt;/th&gt;
&lt;th&gt;&lt;code&gt;DiskRead / DataRead&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Right after the fill (5 intervals)&lt;/td&gt;
&lt;td&gt;2,219-2,263 MB/s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;87.3-89.6 KiB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;25,743-27,404&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;13%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;97-105%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;After 4 KiB random writes (6 intervals)&lt;/td&gt;
&lt;td&gt;1,151-1,267 MB/s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;11.7-11.8 KiB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;177,527-189,629&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;89-95%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;169-188%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;DiskRead / DataRead&lt;/code&gt; is about 100%, so it is reading from disk right after the fill too.&lt;/strong&gt;&lt;br&gt;
It is not being served from memory. What changed is the size of one read on the disk side, by a&lt;br&gt;
factor of 7.5. Disk IOPS rises 7.3x as a result, and saturates at 89-95% of the specified value.&lt;/p&gt;

&lt;p&gt;So: 4 KiB random writes break the layout on disk, and the same 1 MiB read becomes many small&lt;br&gt;
reads. Reads do not change the layout, so they have no effect, and neither does time. Rewriting&lt;br&gt;
sequentially restores a contiguous layout. &lt;strong&gt;This is also why only 4 KiB random reads are&lt;br&gt;
unaffected&lt;/strong&gt;: a random 4 KiB is one read on the disk side whatever the layout is.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;DiskIopsUtilization&lt;/code&gt; alone does not get you here.&lt;/strong&gt; It tells you whether something is&lt;br&gt;
saturated; it does not tell you &lt;strong&gt;why&lt;/strong&gt; it saturated (because each read is small).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And &lt;strong&gt;it goes back and forth within a single run.&lt;/strong&gt; I think this is the strongest evidence.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What was running&lt;/th&gt;
&lt;th&gt;One write&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;One disk read&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Fill, then 3 reads&lt;/td&gt;
&lt;td&gt;1,024 KiB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;89.1 / 89.6 / 88.3 / 87.4 KiB&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The read &lt;strong&gt;after 30 minutes idle&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;87.9 KiB&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;4 KiB random writes&lt;/strong&gt;, then read&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4.0 KiB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;58.7 to 11.8 to 11.6 KiB&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Sequential refill&lt;/strong&gt;, then read&lt;/td&gt;
&lt;td&gt;1,024 KiB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;88.2 / 87.7 KiB&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;30 minutes idle does not move it, a 4 KiB write brings it down, a sequential write brings it back.&lt;/strong&gt;&lt;br&gt;
This is neither a difference between deployments nor measurement scatter.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note: this &lt;code&gt;d_io&lt;/code&gt; depends on the workload too.&lt;/strong&gt; In 1 MiB sequential read intervals it is 88 or&lt;br&gt;
11.7 KiB; in 4 KiB random read intervals it is 4-5 KiB. &lt;strong&gt;When comparing, match the client-side&lt;br&gt;
size of one operation.&lt;/strong&gt; Without that, the layout difference and the workload difference mix.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As a benchmarking practice, I think this is the most portable thing here.&lt;br&gt;
If you publish a sequential read figure, &lt;strong&gt;record whether small random writes were interposed&lt;br&gt;
between writing and reading.&lt;/strong&gt; Without them, the value does not move however many times you read or&lt;br&gt;
however long you wait. With them, it changes by nearly 1.8x. As the next section shows, 300 seconds&lt;br&gt;
of sequential writes does not move it.&lt;/p&gt;

&lt;h3&gt;
  
  
  What breaks it is not writes but small random writes
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;This section was measured later.&lt;/strong&gt; The wording above reads as "writes break it", and that was&lt;br&gt;
too broad. On the same device, I separated the shape of the write from its duration.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;1 MiB sequential read&lt;/th&gt;
&lt;th&gt;Against the reference&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Reference (not broken)&lt;/td&gt;
&lt;td&gt;2,364.78 MB/s&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4 KiB random writes for &lt;strong&gt;30 seconds&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;2,343.63 MB/s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-0.9% (not broken)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4 KiB random writes for &lt;strong&gt;300 seconds&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;546.02 MB/s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-77% (broken)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;After a sequential refill&lt;/td&gt;
&lt;td&gt;2,364.18 MB/s&lt;/td&gt;
&lt;td&gt;-0.03% (fully restored)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Sequential writes for 300 seconds&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2,365.59 MB/s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+0.03% (not broken)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sequential writes do not break it.&lt;/strong&gt; 300 seconds of writing, 0.03%. What breaks it is small
random writes only, not "writes" in general&lt;/li&gt;
&lt;li&gt;30 seconds does not break it, 300 seconds does. The threshold is somewhere in between, and
where is not known&lt;/li&gt;
&lt;li&gt;The unbroken side does not depend on the specified IOPS. 2,364.78 at 100,000, 2,366.23 at
200,000&lt;/li&gt;
&lt;li&gt;For the broken side, proportionality cannot be written. The 546.02 MB/s here (specified
100,000) and 1,272.8 MB/s on another deployment (specified 200,000) give a ratio of 2.33 —
&lt;strong&gt;it moves in the direction of the specified IOPS but not by 2.00, and the two are not from the
same deployment.&lt;/strong&gt; No run varied the specified IOPS while in the broken state, so this is not a
control experiment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;In practice, record whether you "read after small random writes", not whether you "read after&lt;br&gt;
writes".&lt;/strong&gt; A workload that writes sequentially and reads sequentially is not subject to this.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The conclusion that you cannot size from a single point is unchanged. What changed is the&lt;br&gt;
reason.&lt;/strong&gt; Not "an unexplained 2.64x", but &lt;strong&gt;"even on the same configuration it moves 1.8x&lt;br&gt;
depending on whether small random writes were interposed after the data being read was last&lt;br&gt;
written".&lt;/strong&gt;&lt;br&gt;
Why the 3,401-3,408 MB/s environment came out higher still &lt;strong&gt;remains unexplained.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The warm-side ceiling sits on the client side
&lt;/h3&gt;

&lt;p&gt;In the warm state, none of the&lt;br&gt;
&lt;a href="https://docs.aws.amazon.com/fsx/latest/ONTAPGuide/so-file-system-metrics.html" rel="noopener noreferrer"&gt;published utilization metrics&lt;/a&gt;&lt;br&gt;
was at its ceiling. &lt;strong&gt;I used two EC2 clients to decide which side it stops on.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What was measured&lt;/th&gt;
&lt;th&gt;1 MiB sequential read&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Rocky 9.7 (concurrent)&lt;/td&gt;
&lt;td&gt;2,040.69 MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RHEL 9.7 (concurrent)&lt;/td&gt;
&lt;td&gt;2,163.86 MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4,204.55 MB/s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One EC2 client alone (same state)&lt;/td&gt;
&lt;td&gt;2,265.7 MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The total is 1.86x one client. The ceiling sits on the client side.&lt;/strong&gt; The storage side still had&lt;br&gt;
headroom; it was stopping at what one client could produce.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What determines the per-client ceiling was not measured.&lt;/strong&gt; It is about 16-17 Gbps per client, short&lt;br&gt;
of the 50 Gbps of a c5n.9xlarge.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Before you write "I measured the storage ceiling", try adding an EC2 client.&lt;/strong&gt;&lt;br&gt;
If the total grows, what you were measuring was the EC2 client's ceiling.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The same shape appeared on a path that is not block. On 2026-09-19, on the NFS path (first&lt;br&gt;
generation 2,048 MBps, SSD IOPS 40,000, two c5n.9xlarge reading 300 GiB that does not overlap),&lt;br&gt;
1,195.27 alone gave &lt;strong&gt;a total of 1,901.48 MB/s, 1.59x.&lt;/strong&gt; Alone, disk throughput utilization was&lt;br&gt;
63% and not saturated; with two clients it reaches 102-103%.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There, the single-client side was followed up the next day, and it turned out to be two settings&lt;br&gt;
rather than a resource.&lt;/strong&gt; Transfer size (&lt;code&gt;rsize&lt;/code&gt; 64 KiB to 1 MiB, +21%) and concurrency (8 to 16&lt;br&gt;
threads, +14%) took a single client to 1,646.17 MB/s, at which point the file system side pins at&lt;br&gt;
102.6%&lt;br&gt;
(&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/ja/verification/throughput-iops-concurrency.md" rel="noopener noreferrer"&gt;measurement, Japanese&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;It was followed further (2026-09-20), and "the client side" split into parts. On second&lt;br&gt;
generation 6,144 MBps / 200,000 IOPS with 4 KiB random reads, varying only how the working set is&lt;br&gt;
divided, five ways:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Division&lt;/th&gt;
&lt;th&gt;Against the reference&lt;/th&gt;
&lt;th&gt;Disk side / specified 200,000&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1 EC2 client / 1 SVM / 1 volume (reference)&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;60.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Threads doubled only&lt;/td&gt;
&lt;td&gt;+8.9%&lt;/td&gt;
&lt;td&gt;67.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;2 volumes&lt;/strong&gt; (same SVM)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+1.0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;66.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2 SVMs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+26.3%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;83.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2 EC2 clients&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+52.7%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;96.6%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;What acts on it is neither thread count nor volume count, but the number of IP addresses the client&lt;br&gt;
talks to and the number of clients.&lt;/strong&gt; With two clients the disk side reaches 96.6% of the specified&lt;br&gt;
value, and only there does the file system side become the limit&lt;br&gt;
(&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/ja/verification/throughput-iops-concurrency.md" rel="noopener noreferrer"&gt;measurement, Japanese&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The same separation was not done for the 16-17 Gbps per client in this section. There is no&lt;br&gt;
block-side measurement varying transfer size, concurrency and the number of peer IP addresses.&lt;br&gt;
&lt;strong&gt;Had I followed it that far instead of stopping at "the client side", the block side might also&lt;br&gt;
have turned out to be settings and division.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Measuring with one client does not determine the kind of ceiling" holds across protocols, so the&lt;br&gt;
procedure in this section is portable.&lt;/strong&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  At least it is not concurrency
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;The candidate "there must not be enough outstanding requests" fell.&lt;/strong&gt; In the unbroken state, I&lt;br&gt;
varied thread count alone by 4x.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Threads&lt;/th&gt;
&lt;th&gt;1 MiB sequential read&lt;/th&gt;
&lt;th&gt;Response time (&lt;strong&gt;measured&lt;/strong&gt;)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;512&lt;/td&gt;
&lt;td&gt;2,363.91 MB/s&lt;/td&gt;
&lt;td&gt;216.588 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,024&lt;/td&gt;
&lt;td&gt;2,364.28 MB/s&lt;/td&gt;
&lt;td&gt;433.283 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2,048&lt;/td&gt;
&lt;td&gt;2,365.58 MB/s&lt;/td&gt;
&lt;td&gt;860.258 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Throughput moves by 0.07% and only response time scales precisely. Applying 4x the threads does&lt;br&gt;
not raise the per-client ceiling; &lt;strong&gt;only the time requests spend waiting goes up 4x.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This table is also where the hazard of &lt;code&gt;iorate=max&lt;/code&gt; is most visible in this article.&lt;br&gt;
"2,364 MB/s" is the same on all three rows, &lt;strong&gt;but the response time behind it spans 216 ms to&lt;br&gt;
860 ms.&lt;/strong&gt; Write throughput alone and that difference disappears.&lt;/p&gt;

&lt;p&gt;The file-protocol side was the same. NFS 4 KiB random reads at 512 / 1,024 / 2,048 threads gave&lt;br&gt;
94,949.6 / 97,915.3 / 97,014.5 IOPS (a spread of 2.2%), with response times of&lt;br&gt;
5.391 / 10.456 / 21.108 ms (measured). &lt;strong&gt;That ceiling is not concurrency either.&lt;/strong&gt;&lt;br&gt;
Note that 94,949.6 is from a deployment with half the SSD capacity (4,096 against 8,192 GiB), and is&lt;br&gt;
0.7% from the original measurement of 95,630. Between those two points, capacity was not&lt;br&gt;
acting on it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do not cite those two figures as a same-conditions reproduction. Re-measured on 2026-09-20 with&lt;br&gt;
a 600 GiB working set, the reference was 81,313.9, &lt;strong&gt;14% below the figures above.&lt;/strong&gt;&lt;br&gt;
The original measurement did not record its working set, so the conditions cannot be matched.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Match the time of day when comparing configurations
&lt;/h2&gt;

&lt;p&gt;My first comparison measured four configurations in sequence, across time. And as the section&lt;br&gt;
above shows, a sequential read figure moves 1.85x with elapsed time since the write. &lt;strong&gt;I thought I&lt;br&gt;
was changing the policy; I was changing elapsed time as well.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The same shape is not confined to policies. I once wrote a 23% difference for multipathing on versus off,&lt;br&gt;
and that too measured the two at different times. &lt;strong&gt;When comparing configuration A against&lt;br&gt;
configuration B, match the elapsed time from write to read, and if you cannot match it, it is better&lt;br&gt;
not to compare.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  On choosing between protocols
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;There is no protocol difference between iSCSI and NVMe/TCP at one connection.&lt;/strong&gt; That conclusion&lt;br&gt;
is easy to miss if you compare figures measured on different file systems, so the comparison is kept&lt;br&gt;
below to show why.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Comparison&lt;/th&gt;
&lt;th&gt;iSCSI&lt;/th&gt;
&lt;th&gt;NVMe/TCP&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Different file systems side by side (misleading)&lt;/td&gt;
&lt;td&gt;1,135.18 MB/s&lt;/td&gt;
&lt;td&gt;591.64 MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;As the documented procedure has it&lt;/td&gt;
&lt;td&gt;1,135.18 MB/s&lt;/td&gt;
&lt;td&gt;1,288.87-3,407.60 MB/s (3 deployments)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4 KiB random reads under that procedure&lt;/td&gt;
&lt;td&gt;44,593 IOPS&lt;/td&gt;
&lt;td&gt;126,964 / 128,154 IOPS (2 deployments)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read on its own, the top row looks like "the ordering inverts at one connection." &lt;strong&gt;But those two&lt;br&gt;
figures were measured on different file systems, and side by side the difference reads as a protocol&lt;br&gt;
difference that isn't there.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I re-measured on one file system. A 600 GiB LUN and a 600 GiB namespace were placed together on&lt;br&gt;
a single 1,800 GiB volume, leaving &lt;strong&gt;&lt;code&gt;lun=&lt;/code&gt; as the only line differing between the parameter files.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;One connection, same file system&lt;/th&gt;
&lt;th&gt;1 MiB sequential read&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;iSCSI, 1 session&lt;/td&gt;
&lt;td&gt;1,135.19 MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NVMe/TCP, 1 I/O queue&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1,135.88 MB/s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Difference&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.06%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The ordering does not invert. There was no protocol difference at one connection.&lt;/strong&gt;&lt;br&gt;
591.64 belongs to the deployment tier: one-connection figures appear at only two positions,&lt;br&gt;
4.73 Gbps and 9.08 Gbps, and within one deployment iSCSI and NVMe/TCP appear at the same&lt;br&gt;
position. The &lt;code&gt;connect-all&lt;/code&gt; side gave 2,366.32 against the published 2,366.49, agreeing to 0.007%&lt;br&gt;
(&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/ja/verification/perf-matrix-results.md" rel="noopener noreferrer"&gt;measurement, Japanese&lt;/a&gt;).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The question left over is what 591.64 was.&lt;/strong&gt; In the single mode as published, all four workloads&lt;br&gt;
landed between 591.5 and 591.7, &lt;strong&gt;the shape of hitting the ceiling of a single path.&lt;/strong&gt; The&lt;br&gt;
re-measured run does not pin there. &lt;strong&gt;The cause has not been confirmed.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;No ratio is written. The &lt;code&gt;as the documented procedure has it&lt;/code&gt; row still has its numerator and&lt;br&gt;
denominator from different file systems, and the NVMe/TCP side scatters across deployments.&lt;br&gt;
Computed from one pair it comes to "2.08x", but &lt;strong&gt;recomputed over the measured spread it is&lt;br&gt;
1.14-3.00x.&lt;/strong&gt; Pick one figure out of that spread and write "N times", and the picking is itself the&lt;br&gt;
conclusion.&lt;/p&gt;

&lt;p&gt;The iSCSI side cannot be treated as a fixed value either. For sequential reads, one session and&lt;br&gt;
both portals agreed across two deployments (1,135.18 and 1,135.19, 0.01 MB/s apart), but &lt;strong&gt;two&lt;br&gt;
points are not a distribution.&lt;/strong&gt; And 16 sessions is still one deployment only. There is no&lt;br&gt;
basis for "iSCSI is stable". The evidence is simply not there.&lt;/p&gt;

&lt;p&gt;One thing can be kept as a comparison. 4 KiB random reads agreed to within 1% on the same two&lt;br&gt;
deployments where sequential reads scattered (126,964 and 128,154). &lt;strong&gt;It is the sequential read&lt;br&gt;
that scatters, and nothing else.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This was later corroborated. On the same deployment where a rewrite moved sequential reads by&lt;br&gt;
1.85x, 4 KiB random reads gave 131,506 (warm) against 131,550 / 131,749 / 131,736 (cold), a spread&lt;br&gt;
of 0.2%. &lt;strong&gt;Whatever moves sequential reads by 1.85x does not act on 4 KiB random reads.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;4 KiB random reads do, however, scatter across deployments for a different reason (three tiers&lt;br&gt;
observed: 131k / 203k / 242k). That cause could not be confirmed. Two things were established.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One is that 4 KiB random reads scale precisely with the specified SSD IOPS.&lt;/strong&gt;&lt;br&gt;
Only the specified value was changed, within one deployment.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provisioned SSD IOPS&lt;/th&gt;
&lt;th&gt;4 KiB random read&lt;/th&gt;
&lt;th&gt;Ratio to the specified value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;200,000 (before lowering)&lt;/td&gt;
&lt;td&gt;131,413.5 IOPS&lt;/td&gt;
&lt;td&gt;0.657&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;100,000&lt;/strong&gt; (after lowering)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;66,016.9 IOPS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.660&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100,000 (created that way on another deployment)&lt;/td&gt;
&lt;td&gt;65,070-65,947 IOPS&lt;/td&gt;
&lt;td&gt;0.651-0.659&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;200,000&lt;/strong&gt; (after &lt;strong&gt;raising&lt;/strong&gt; it on that deployment)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;131,529.7 IOPS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.658&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The ratio going down is 1.990 and going up is 2.02. &lt;strong&gt;The measured value lands at about 66% of the&lt;br&gt;
specified value, the same ratio at all four points.&lt;/strong&gt; The 1 MiB sequential read in the same&lt;br&gt;
states was 2,265.7 / 2,366.23 MB/s and does not change: sequential reads do not depend on the&lt;br&gt;
specified IOPS.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The reverse control was taken differently, in the following run.&lt;/strong&gt; The first attempt lowered from&lt;br&gt;
200,000, and the request to restore it was refused with "cannot start until at least 6 hours have&lt;br&gt;
passed since the last storage capacity update".&lt;br&gt;
&lt;strong&gt;A provisioned IOPS change is treated as a storage capacity update, with a six-hour cooldown.&lt;/strong&gt;&lt;br&gt;
Billing continues while you wait, so &lt;strong&gt;the next run was created at 100,000 and raised to 200,000&lt;br&gt;
partway through&lt;/strong&gt;: in the increasing direction it takes one update and does not hit the cooldown.&lt;br&gt;
&lt;strong&gt;An update stays in &lt;code&gt;UPDATED_OPTIMIZING&lt;/code&gt; for about 18 minutes&lt;/strong&gt; (about 17 minutes when lowering).&lt;br&gt;
&lt;strong&gt;If you measure by lowering, plan to throw the environment away; if you need a round trip, design&lt;br&gt;
it in the increasing direction.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;The other is that what sets the spread between deployments is the disk read amplification factor.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The specified value was 200,000 on every deployment. What differed was &lt;strong&gt;how many disk-side reads one&lt;br&gt;
client read becomes.&lt;/strong&gt; CloudWatch retains 5-minute values for 63 days, so the intervals whose&lt;br&gt;
average read I/O is 4 KiB can be pulled out and checked without rebuilding the environment.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Deployment&lt;/th&gt;
&lt;th&gt;Client IOPS&lt;/th&gt;
&lt;th&gt;Disk IOPS&lt;/th&gt;
&lt;th&gt;Against the specified 200,000&lt;/th&gt;
&lt;th&gt;Amplification (byte ratio)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;High side&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;203,961&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;162,562&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;81%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;96.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High side (another deployment)&lt;/td&gt;
&lt;td&gt;206,582&lt;/td&gt;
&lt;td&gt;172,158&lt;/td&gt;
&lt;td&gt;86%&lt;/td&gt;
&lt;td&gt;83.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Low side&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;131,617&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;195,905&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;98%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;193.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Low side (another deployment)&lt;/td&gt;
&lt;td&gt;130,080&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;195,941&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;98%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;190.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The IOPS the client sees is the specified IOPS divided by that environment's amplification factor.&lt;/strong&gt;&lt;br&gt;
131,617 x 1.49 = 196,109; 203,961 x 0.797 = 162,557. Both check out.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;At an amplification of about 1.5, &lt;strong&gt;the disk saturates at 98% of the specified value&lt;/strong&gt; and the
client stops at 131k&lt;/li&gt;
&lt;li&gt;At an amplification of about 0.8, &lt;strong&gt;the disk has headroom at 81%&lt;/strong&gt; and the client reaches 204k&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The "about 66% of the specified value" from the proportionality above is the reciprocal of that&lt;br&gt;
1.5.&lt;/strong&gt; The same relation was being seen two different ways.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Here I nearly settled on a wrong explanation once.&lt;/strong&gt; I thought "if it exceeds the specified value&lt;br&gt;
it must be served from memory", but &lt;strong&gt;the way to decide that is the ratio of &lt;code&gt;DiskReadBytes&lt;/code&gt; to&lt;br&gt;
&lt;code&gt;DataReadBytes&lt;/code&gt;&lt;/strong&gt; (a few percent or less means it is served from cache). Applied for real it was&lt;br&gt;
&lt;strong&gt;104-181% across all five deployments: none of them was being served from cache.&lt;/strong&gt; The cause&lt;br&gt;
was looking only at &lt;code&gt;DiskIopsUtilization&lt;/code&gt;. &lt;strong&gt;It tells you whether the disk is saturated; it tells&lt;br&gt;
you nothing about the relation to what the client sees.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you measure, take four&lt;/strong&gt;: &lt;code&gt;DataReadBytes&lt;/code&gt; / &lt;code&gt;DataReadOperations&lt;/code&gt; / &lt;code&gt;DiskReadBytes&lt;/code&gt; /&lt;br&gt;
&lt;code&gt;DiskReadOperations&lt;/code&gt;. Taken in 5-minute intervals, the average I/O size lets you cut out workload&lt;br&gt;
intervals after the fact. &lt;strong&gt;You do not have to rely on the timestamps in your own notes.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;So what is the reason the amplification factor splits between 0.8 and 1.5? This is what remained to&lt;br&gt;
the end.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here another of my hypotheses was disproved. I thought "the layout above must also be setting the&lt;br&gt;
amplification factor", but even right after a sequential refill had restored the layout to 88 KiB,&lt;br&gt;
random read amplification stayed at 190-208%. &lt;strong&gt;The layout for sequential reads and the&lt;br&gt;
amplification factor for random reads are separate variables.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There was one candidate. Line up the five deployments by &lt;strong&gt;"did 4 KiB random writes ever run on that&lt;br&gt;
device"&lt;/strong&gt; and they split cleanly.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Deployment and interval&lt;/th&gt;
&lt;th&gt;4 KiB random write history&lt;/th&gt;
&lt;th&gt;Amplification&lt;/th&gt;
&lt;th&gt;Client IOPS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Never ran (a run that measured reads only)&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;96.5%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;203,961&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An interval before it had run&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;83.3%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;206,582&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;After it ran&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;170.1%&lt;/td&gt;
&lt;td&gt;140,423&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;After it ran&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;193.7%&lt;/td&gt;
&lt;td&gt;131,617&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;After it ran (&lt;strong&gt;a sequential refill does not restore it&lt;/strong&gt;)&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;190-208%&lt;/td&gt;
&lt;td&gt;97,769-130,080&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All five followed that ordering. Even so, &lt;strong&gt;this is an ordering of observations, not a control&lt;br&gt;
experiment.&lt;/strong&gt; I wrote it down as something that would be settled by applying the steps in turn on a&lt;br&gt;
single device.&lt;/p&gt;

&lt;h3&gt;
  
  
  And that candidate was disproved too
&lt;/h3&gt;

&lt;p&gt;I applied it. &lt;strong&gt;It was disproved.&lt;/strong&gt; On a new deployment I began measuring right after a fill, with&lt;br&gt;
4 KiB random writes never having run.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;th&gt;4 KiB random read&lt;/th&gt;
&lt;th&gt;Against the specified 100,000&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Right after a fill on a new file system&lt;/strong&gt; (no random write history)&lt;/td&gt;
&lt;td&gt;65,947.6 IOPS&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.659&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;After 300 seconds of 4 KiB random writes&lt;/td&gt;
&lt;td&gt;65,430.8 IOPS&lt;/td&gt;
&lt;td&gt;0.654&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;After a sequential refill&lt;/td&gt;
&lt;td&gt;65,070.0 IOPS&lt;/td&gt;
&lt;td&gt;0.651&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;It is already 0.659 with no history. Where the "none" rows in the table above were 0.83-0.97,&lt;br&gt;
&lt;strong&gt;the same "none" condition produced a value on the 1.5 side.&lt;/strong&gt; The spread across the three states is&lt;br&gt;
1.3%, and the amplification factor moves with neither layout nor write history.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Five observations lining up cleanly was a coincidence.&lt;/strong&gt; That is the third disproved hypothesis.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;An ordering of observations is not a substitute for a control experiment.&lt;/strong&gt; All five followed it&lt;br&gt;
without exception, the mechanism was plausible, and it was wrong. &lt;strong&gt;When you write "all n cases&lt;br&gt;
follow" as a basis, check at the same time how many unvaried variables were lining up in the same&lt;br&gt;
direction.&lt;/strong&gt; In these five, the patch version and the client were lined up the same way too.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What remains is those two. The difference between the 0.83-0.97 of F-6 / F-7 and the 1.5 side&lt;br&gt;
later on comes down to the ONTAP patch version (9.18.1P6 against 9.18.1) and the client used&lt;br&gt;
for the measurement. &lt;strong&gt;Neither was varied within a single run, so they are named as candidates and&lt;br&gt;
nothing more.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;One of those two cannot be varied by a reader either.&lt;/strong&gt; &lt;code&gt;CreateFileSystem&lt;/code&gt;'s&lt;br&gt;
&lt;a href="https://docs.aws.amazon.com/fsx/latest/APIReference/API_CreateFileSystem.html" rel="noopener noreferrer"&gt;&lt;code&gt;FileSystemTypeVersion&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
is documented as being for FSx for Lustre, and FSx for ONTAP has no parameter for specifying a&lt;br&gt;
version. The list of updatable properties in&lt;br&gt;
&lt;a href="https://docs.aws.amazon.com/fsx/latest/ONTAPGuide/updating-file-system.html" rel="noopener noreferrer"&gt;Updating file systems&lt;/a&gt;&lt;br&gt;
does not include the ONTAP version either. &lt;strong&gt;So the patch version can be neither chosen nor&lt;br&gt;
pinned&lt;/strong&gt;, and a control experiment varying that variable cannot be assembled on the user side.&lt;br&gt;
&lt;strong&gt;The client is the only one that can be varied.&lt;/strong&gt; If you want to press on here, what you can do is&lt;br&gt;
show that the amplification factor does not move when the client is changed, narrowing the&lt;br&gt;
candidate to the patch version. &lt;strong&gt;It does not establish causation.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The policy, incidentally, had nothing to do with it. Switching &lt;code&gt;queue-depth&lt;/code&gt; to &lt;code&gt;numa&lt;/code&gt; to&lt;br&gt;
&lt;code&gt;round-robin&lt;/code&gt; to &lt;code&gt;queue-depth&lt;/code&gt; during a 4 KiB random read produced no step, and against the 132,044.8&lt;br&gt;
IOPS average of the run containing all three policies, &lt;code&gt;queue-depth&lt;/code&gt; alone gave 131,413.5 IOPS —&lt;br&gt;
a difference of 0.5%.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five things I hit on the instrument and the teardown
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;It took nine failures to reach a measurement. All of them gates, none of them numbers.&lt;/strong&gt;&lt;br&gt;
Four of those happened before reaching a mount, so they are in a&lt;br&gt;
&lt;a href="https://dev.to/aws-builders/moving-on-premises-block-storage-to-aws-the-divide-by-625-mbps-sizing-approach-comes-out-wrong-31pc"&gt;separate article&lt;/a&gt;&lt;br&gt;
(A: sessions and queues); what belongs here are the five on the instrument and the teardown.&lt;br&gt;
The detail is in the&lt;br&gt;
&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/ja/verification/block-measurement-runbook.md" rel="noopener noreferrer"&gt;block measurement runbook&lt;/a&gt;&lt;br&gt;
as a table of symptom, cause and how to check.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;vdbench resolves &lt;code&gt;include=&lt;/code&gt; against the current directory, not against the path given to &lt;code&gt;-f&lt;/code&gt;.
The fill "completed successfully without writing anything". &lt;strong&gt;Had I gone on without noticing, I would
have been measuring reads against unwritten blocks (which return zeros).&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;6. &lt;code&gt;key=value&lt;/code&gt; on a vdbench command line is not an override of the workload definition.&lt;/strong&gt;&lt;br&gt;
It is an assignment to a placeholder inside the parameter file. Passing &lt;code&gt;xfersize=1024k&lt;/code&gt; failed with&lt;br&gt;
&lt;code&gt;Unused parameter substitution&lt;/code&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;I reported an SSM wait limit as a failure. A 600 GiB write did not fit inside a 7.5-minute&lt;br&gt;
wait, I saw &lt;code&gt;status: InProgress&lt;/code&gt;, judged it a failure and tore the environment down.&lt;br&gt;
&lt;strong&gt;A wait limit is not a failure.&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A sentinel string matched my own explanatory text. Against an implementation that "prints&lt;br&gt;
MISSING if the module is absent", I was deciding with &lt;code&gt;grep -q 'MISSING'&lt;/code&gt;, and it matched the&lt;br&gt;
explanatory line "A MISSING line means…" in the same output, so F-2 was skipped although NVMe/TCP&lt;br&gt;
was available. &lt;strong&gt;A sentinel that your own explanatory text satisfies is not a sentinel.&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The teardown trap died partway through the teardown. Under &lt;code&gt;set -euo pipefail&lt;/code&gt;, reading&lt;br&gt;
&lt;code&gt;describe-stacks&lt;/code&gt; after a delete always fails once the stack has finished disappearing, and that&lt;br&gt;
failure kills the trap itself. The delete ran but the retry did not, and a &lt;code&gt;DELETE_FAILED&lt;/code&gt; stack was&lt;br&gt;
left behind. &lt;strong&gt;Put the delete call first in the trap, and &lt;code&gt;set +e&lt;/code&gt; immediately after it.&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Three more hit during the rewrite
&lt;/h3&gt;

&lt;p&gt;The nine above are from the first measurement. The run that recounted things per path added three&lt;br&gt;
more. &lt;strong&gt;All of them the same shape as the nine: looking successful while measuring nothing, or&lt;br&gt;
not being able to tell that nothing was measured.&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;A counter table returned empty. &lt;code&gt;nvmf_lif&lt;/code&gt; had zero rows even after 600 GiB had been written,&lt;br&gt;
and &lt;strong&gt;reading zero rows as zero bytes gets you the right conclusion for the wrong reason.&lt;/strong&gt; Which&lt;br&gt;
table to look at, and the detail of that trap, are in the &lt;a href="https://dev.to/aws-builders/amazon-fsx-for-netapp-ontap-nvmetcp-assumes-rhel-93-why-multipathing-fails-on-amazon-linux-2023-1afl"&gt;separate article&lt;/a&gt;&lt;br&gt;
(B).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A socket aggregation matched my own metric. To sum bytes per destination I treated any field&lt;br&gt;
ending in &lt;code&gt;:4420&lt;/code&gt; as a destination, and &lt;code&gt;bytes_acked:4420&lt;/code&gt; (a byte count) happened to end in&lt;br&gt;
4420, growing one spurious path named &lt;code&gt;bytes_acked&lt;/code&gt;. The value was 0 so the sum was not broken, but&lt;br&gt;
&lt;strong&gt;it is the same mistake as the eighth.&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;12. "It passed locally so it will pass in CI" did not hold.&lt;/strong&gt; This is the repository side rather&lt;br&gt;
than measurement, but the structure is the same. A check scans tracked files, and a new file is&lt;br&gt;
outside the scan while it is untracked. Green locally, red in CI. "Check after the last edit" is&lt;br&gt;
not enough; it is also "after the last &lt;code&gt;git add&lt;/code&gt;".&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The same configuration varied by 2.64x across environments.&lt;/strong&gt; Do not size from a single measured
value&lt;/li&gt;
&lt;li&gt;What is acting on it is the layout on disk. One disk-side read goes from 87-89 KiB to 11.7 KiB
(a factor of 7.5), disk IOPS rises 7.3x and saturates at 89-95% of the specified value.
&lt;strong&gt;&lt;code&gt;DiskRead / DataRead&lt;/code&gt; is about 100%, so the faster side is reading from disk too.&lt;/strong&gt; It was not
being served from memory&lt;/li&gt;
&lt;li&gt;What breaks it is small random writes only. Three reads in a row, a spread of 0.2%; 30 minutes
idle, within 0.4%; 300 seconds of sequential writes, 0.03%. 4 KiB random writes cost 0.9% at
30 seconds and 77% at 300 seconds, and a sequential refill restores it. It goes back and forth
within a single run. &lt;strong&gt;Where the threshold sits between 30 and 300 seconds is not known&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;The warm-side ceiling sits on the client side, and it is not concurrency. Two clients
concurrently total 4,204.55 MB/s, 1.86x one client. Meanwhile 512 to 2,048 threads moves
throughput by only 0.07% and only scales response time from 216.6 to 860.3 ms. NFS 4 KiB random
reads are the same, a spread of 2.2% (5.391 to 21.108 ms). &lt;strong&gt;Before you write "I measured the
storage ceiling", try adding an EC2 client. Adding threads will not tell you&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;4 KiB random reads scale precisely with the specified SSD IOPS (about 66% of the specified
value, the same ratio at four points). It holds in both the decreasing and the increasing
direction. &lt;strong&gt;Sequential reads do not depend on the specified IOPS&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;What splits IOPS between 131k and 204k across deployments is the disk read amplification factor.
Client IOPS = specified IOPS / amplification factor. &lt;strong&gt;&lt;code&gt;DiskIopsUtilization&lt;/code&gt; alone will not tell
you.&lt;/strong&gt; Take &lt;code&gt;DataReadBytes&lt;/code&gt; / &lt;code&gt;DataReadOperations&lt;/code&gt; / &lt;code&gt;DiskReadBytes&lt;/code&gt; / &lt;code&gt;DiskReadOperations&lt;/code&gt;.
What sets that amplification factor is not known. That it is neither layout nor write history
is settled; the remaining candidates are the ONTAP patch version and the client. The patch
version cannot be specified on FSx for ONTAP, so a control experiment varying that variable
cannot be assembled on the user side&lt;/li&gt;
&lt;li&gt;Observation order is not a substitute for a control experiment. &lt;strong&gt;I had a candidate that five
cases followed without exception; I aimed a measurement at it and it was disproved.&lt;/strong&gt; Unvaried
variables were simply lined up in the same direction&lt;/li&gt;
&lt;li&gt;When comparing configuration A against B, match the elapsed time from write to read. &lt;strong&gt;I compared
without matching it, wrote "1.73x from the policy", and withdrew it later&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Which protocol comes out ahead changes with the procedure and the I/O shape. Publish which
procedure measured what, not which one is faster&lt;/li&gt;
&lt;li&gt;Do not write throughput alone. The figures in this article are &lt;code&gt;iorate=max&lt;/code&gt; saturation points,
and the same 2,364 MB/s sits at a 216 ms point and at an 860 ms point. &lt;strong&gt;Without response time
alongside it, that difference disappears&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Notes on the AWS documentation
&lt;/h2&gt;

&lt;p&gt;This article contains no remarks about the documentation, and there is nothing to submit.&lt;/p&gt;

&lt;p&gt;What it contains are errors in my own way of measuring and measurements that the published&lt;br&gt;
specification cannot account for. &lt;strong&gt;Neither is a claim that the documentation is wrong, so I am&lt;br&gt;
setting them out separately.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Content&lt;/th&gt;
&lt;th&gt;Kind&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2.64x across the same configuration / 1.85x from layout&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Measurement.&lt;/strong&gt; The published specification does not promise this granularity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The cause of amplification 0.8 against 1.5 is unknown&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Left unresolved.&lt;/strong&gt; The candidates are the ONTAP patch version and the client, and &lt;strong&gt;the former cannot be chosen on FSx for ONTAP&lt;/strong&gt;, so no control experiment can be assembled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The per-client ceiling (block side)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Not measured.&lt;/strong&gt; On the NFS side it was followed through on 2026-09-20 (the table above), so the same way of varying things should apply to the block side&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Comparing policies across time, writing 1.73x, and withdrawing it&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;My error&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;nvmf_lif&lt;/code&gt; returning no rows&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;ONTAP-side behaviour.&lt;/strong&gt; A question for the vendor, not a matter of AWS documentation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The remarks about the documentation are in the other two: &lt;a href="https://dev.to/aws-builders/moving-on-premises-block-storage-to-aws-the-divide-by-625-mbps-sizing-approach-comes-out-wrong-31pc"&gt;A&lt;/a&gt; and&lt;br&gt;
&lt;a href="https://dev.to/aws-builders/amazon-fsx-for-netapp-ontap-nvmetcp-assumes-rhel-93-why-multipathing-fails-on-amazon-linux-2023-1afl"&gt;B&lt;/a&gt;, each with its own submission status.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Submission status across the three block-protocol articles and S3 Burst Part 2 is collected in&lt;br&gt;
&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/en/reference/block-protocol-aws-feedback-status.md" rel="noopener noreferrer"&gt;AWS documentation notes and inquiries across the block-protocol articles&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Primary record of the figures
&lt;/h2&gt;

&lt;p&gt;The primary record of the figures and their conditions is in&lt;br&gt;
&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/ja/verification/perf-matrix-results.md" rel="noopener noreferrer"&gt;per-protocol measurement results (Japanese)&lt;/a&gt;,&lt;br&gt;
and the reproduction procedure is in the&lt;br&gt;
&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/ja/verification/block-measurement-runbook.md" rel="noopener noreferrer"&gt;block measurement runbook (Japanese)&lt;/a&gt;.&lt;br&gt;
&lt;strong&gt;Whatever is unconfirmed is written as unconfirmed, so please do not drop that when citing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pitfalls with the VDBENCH measurement tool, and items to always record, are collected in&lt;br&gt;
&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/en/reference/block-protocol-testing-guide.md" rel="noopener noreferrer"&gt;Considerations when measuring FSx for ONTAP's block protocols&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>performance</category>
      <category>monitoring</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Moving on-premises block storage to AWS? The "divide by 625 MBps" sizing approach comes out wrong</title>
      <dc:creator>Yoshiki Fujiwara(藤原 善基)@AWS Community Builder</dc:creator>
      <pubDate>Wed, 30 Sep 2026 05:43:26 +0000</pubDate>
      <link>https://dev.to/aws-builders/moving-on-premises-block-storage-to-aws-the-divide-by-625-mbps-sizing-approach-comes-out-wrong-31pc</link>
      <guid>https://dev.to/aws-builders/moving-on-premises-block-storage-to-aws-the-divide-by-625-mbps-sizing-approach-comes-out-wrong-31pc</guid>
      <description>&lt;h1&gt;
  
  
  Moving on-premises block storage to AWS? The "divide by 625 MBps" sizing approach comes out wrong — measured session and queue counts
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;What this article does and does not cover: it covers the measured failure of the "divide by&lt;br&gt;
625 MBps" sizing approach, and four configuration failures hit before mounting. It does not&lt;br&gt;
cover performance conclusions, a comparison against file protocols, or distributing block storage&lt;br&gt;
to multiple sites (details under "What this article does not cover" below).&lt;br&gt;
&lt;strong&gt;All figures are from a Single-AZ configuration; Multi-AZ is unverified.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When using FSx for ONTAP over iSCSI or NVMe/TCP, how many sessions should you open? AWS's&lt;br&gt;
procedures surface one number: &lt;strong&gt;"Amazon EC2 single client maximum of 5 Gbps (~625 MBps)"&lt;/strong&gt;,&lt;br&gt;
which appears on both the&lt;br&gt;
&lt;a href="https://docs.aws.amazon.com/fsx/latest/ONTAPGuide/mount-iscsi-luns-linux.html" rel="noopener noreferrer"&gt;iSCSI&lt;/a&gt; and&lt;br&gt;
&lt;a href="https://docs.aws.amazon.com/fsx/latest/ONTAPGuide/provision-nvme-linux.html" rel="noopener noreferrer"&gt;NVMe/TCP&lt;/a&gt; pages as&lt;br&gt;
the lead-in to the procedure for adding sessions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;One thing first: AWS does not instruct you to divide.&lt;/strong&gt; The source text says "if you want&lt;br&gt;
throughput beyond this value, follow the EC2 network bandwidth page's procedure to add&lt;br&gt;
sessions," and it's marked &lt;code&gt;(Optional)&lt;/code&gt;. No formula is given. "required bandwidth ÷ 625 =&lt;br&gt;
session count" is a sizing approach I built after reading that figure. This article tests&lt;br&gt;
my sizing approach, not a formula AWS published.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Measuring it, that sizing approach &lt;strong&gt;missed in both directions.&lt;/strong&gt; 1.82x with one session, 0.20x&lt;br&gt;
with sixteen. And going from one session to two moved sequential throughput by zero bytes.&lt;/p&gt;

&lt;p&gt;So this figure can't be used as a divisor to determine session count. What's documented as a&lt;br&gt;
&lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-network-bandwidth.html" rel="noopener noreferrer"&gt;single flow's ceiling in EC2 network bandwidth&lt;/a&gt;&lt;br&gt;
turned out to be &lt;strong&gt;a separate thing from what a block protocol session measures per session.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;NVMe/TCP has nothing equivalent to &lt;code&gt;nr_sessions&lt;/code&gt;; the analogous quantity is queue count. That&lt;br&gt;
&lt;strong&gt;doesn't honor a request either&lt;/strong&gt; — asking for 36 produced 4.&lt;/p&gt;

&lt;h2&gt;
  
  
  What was measured
&lt;/h2&gt;

&lt;p&gt;Assuming no prior knowledge of FSx for ONTAP, here's where this article's measurement happens.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FYoshiki0705%2Fs3-burst-on-ontap-files%2Fb6b83cd3d6bc516f12478138cc36bb30a3ae0ec3%2Fdocs%2F_assets%2Fimages%2Fs3burst-block-a-sessions-en%402x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FYoshiki0705%2Fs3-burst-on-ontap-files%2Fb6b83cd3d6bc516f12478138cc36bb30a3ae0ec3%2Fdocs%2F_assets%2Fimages%2Fs3burst-block-a-sessions-en%402x.png" alt="Inside a single VPC and single AZ, one EC2 client (c5n.9xlarge) connects to one Amazon FSx for&lt;br&gt;
NetApp ONTAP (second-generation SINGLE_AZ_2) file system over either iSCSI (multiple sessions) or&lt;br&gt;
NVMe/TCP (multiple queues)" width="800" height="614"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure: what this article measures is the connection count between one EC2 client and one FSx for&lt;br&gt;
ONTAP file system, not multiple clients pooled together.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The figure states the same thing as the table below. What this article measures is the&lt;br&gt;
&lt;strong&gt;connection count&lt;/strong&gt; between one EC2 client and one FSx for ONTAP file system, not multiple&lt;br&gt;
clients pooled together.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Role in this article&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;VPC / AZ&lt;/td&gt;
&lt;td&gt;Single AZ, no network detour, so what's measured is the protocol itself&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EC2 client&lt;/td&gt;
&lt;td&gt;Varies session/queue count and measures 1 MiB sequential reads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FSx for ONTAP&lt;/td&gt;
&lt;td&gt;Provides the iSCSI LUN or the NVMe/TCP namespace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Connection count&lt;/td&gt;
&lt;td&gt;For iSCSI, TCP sessions counted with &lt;code&gt;ss&lt;/code&gt;; for NVMe/TCP, requested vs. effective queue count&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;What this article does not cover. No performance conclusion is drawn; the numbers here only&lt;br&gt;
serve as evidence that the division misses. &lt;strong&gt;The same configuration swings 2.64x across&lt;br&gt;
environments&lt;/strong&gt;, so don't use these figures for sizing. Why, and what actually drives the numbers,&lt;br&gt;
is in a &lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/.private/blog-unpublished-block-c-what-moves-the-numbers-en.md" rel="noopener noreferrer"&gt;separate article&lt;/a&gt;.&lt;br&gt;
There's no comparison against file protocols (NFS/SMB) either — there's a reason they don't belong&lt;br&gt;
in the same table. And there's no discussion of distributing block storage to multiple sites — this&lt;br&gt;
architecture's distribution layer (FlexCache) distributes volumes, not LUNs or namespaces.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;This is one of a set of three, but there's no order to read them in.&lt;/strong&gt; The block protocol&lt;br&gt;
measurements are split by reader.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Article&lt;/th&gt;
&lt;th&gt;Reader&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;A&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Session and queue counts&lt;/strong&gt; (this article)&lt;/td&gt;
&lt;td&gt;Anyone about to mount&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;B&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/aws-builders/amazon-fsx-for-netapp-ontap-nvmetcp-assumes-rhel-93-why-multipathing-fails-on-amazon-linux-2023-1afl"&gt;Checking whether ANA is available&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Anyone who followed the procedure and got stuck&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;C&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/aws-builders/why-amazon-fsx-for-netapp-ontap-block-performance-differed-by-about-2x-on-the-same-specified-35pi"&gt;What moves the numbers&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Anyone measuring or citing figures&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;None assumes the other two. Read only what you need.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Measurement environment
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;File system&lt;/td&gt;
&lt;td&gt;FSx for ONTAP second-generation SINGLE_AZ_2, 6,144 MBps x1 HA pair, SSD 4,096 GiB, provisioned 200,000 IOPS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Region&lt;/td&gt;
&lt;td&gt;ap-northeast-1, single AZ&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ONTAP&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;9.18.1P5&lt;/strong&gt; (the three divisor-table data points). &lt;strong&gt;The queue negotiation section used a separate deployment, and its version was not recorded&lt;/strong&gt; — it wasn't read at measurement time and can't be recovered after the fact. &lt;strong&gt;I can't write "all figures in this article are the same version."&lt;/strong&gt; (Multiple versions coexisting across this configuration is covered in the &lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/.private/blog-unpublished-block-c-what-moves-the-numbers-en.md" rel="noopener noreferrer"&gt;separate article&lt;/a&gt; (C). The cross-article version mapping is in the &lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/en/reference/block-protocol-ontap-version-matrix.md" rel="noopener noreferrer"&gt;ONTAP version matrix&lt;/a&gt;.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NVMe read cache&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Disabled&lt;/strong&gt; (confirmed on both nodes)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LUN / namespace&lt;/td&gt;
&lt;td&gt;600 GiB, on a 900 GiB volume&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Before measuring&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Wrote the full 600 GiB once.&lt;/strong&gt; Thin-provisioned unwritten blocks return zero, so reading without writing first measures nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client&lt;/td&gt;
&lt;td&gt;c5n.9xlarge; 50 Gbps network is the guaranteed value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Measurement tool&lt;/td&gt;
&lt;td&gt;VDBENCH 5.04.07 direct, &lt;strong&gt;&lt;code&gt;iorate=max&lt;/code&gt; (unlimited)&lt;/strong&gt;, 512 threads, 60s warmup + 300s measurement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test data / efficiency settings&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Not recorded whether the VDBENCH payload was compressible, or whether volume inline efficiency (&lt;code&gt;StorageEfficiencyEnabled&lt;/code&gt;) was enabled.&lt;/strong&gt; On the file-protocol side, efficiency settings measurably affected figures; whether the same applies to block is unconfirmed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Because the window is 300 seconds, this article's figures include bursts. They can't be cited as&lt;br&gt;
a baseline.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This article's figures are the saturation point of &lt;code&gt;iorate=max&lt;/code&gt;, not an operating point in&lt;br&gt;
production. No target IOPS was given — this is however much could be pushed through. On the&lt;br&gt;
file-protocol side of the same repository, giving a target of 4,400 IOPS produced 4,406 MB/s at&lt;br&gt;
8.86 ms, while unlimited produced 4,204 MB/s at 121.79 ms. &lt;strong&gt;Unlimited is 5% lower&lt;br&gt;
throughput and 14x the response time.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With threads fixed at 512 and unlimited, response time is an identity of &lt;code&gt;512 / IOPS&lt;/code&gt; (confirmed&lt;br&gt;
against a separate measurement: 216.6 ms derived versus 216.588 ms measured). The values below&lt;br&gt;
can be re-read through that identity — 1,135.18 MB/s is about 451 ms, 1,970.43 MB/s is&lt;br&gt;
about 260 ms. &lt;strong&gt;Both are derived values, not recorded measurements.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The divisor division doesn't hold
&lt;/h2&gt;

&lt;p&gt;Over iSCSI, I varied the &lt;strong&gt;counted number of TCP connections&lt;/strong&gt; and measured 1 MiB sequential&lt;br&gt;
reads. These are actual counts from &lt;code&gt;ss&lt;/code&gt;, not the requested value.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;TCP counted&lt;/th&gt;
&lt;th&gt;Measured&lt;/th&gt;
&lt;th&gt;Predicted from 625 MBps&lt;/th&gt;
&lt;th&gt;Measured / predicted&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1,135.18 MB/s&lt;/td&gt;
&lt;td&gt;625 MB/s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.82x&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1,135.18 MB/s&lt;/td&gt;
&lt;td&gt;1,250 MB/s&lt;/td&gt;
&lt;td&gt;0.91x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;1,970.43 MB/s&lt;/td&gt;
&lt;td&gt;10,000 MB/s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.20x&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;With one session it's faster than predicted; with sixteen it's a fifth of the prediction.&lt;/p&gt;

&lt;p&gt;And going from one session to two moves sequential throughput by zero bytes — 1,135.18 appears&lt;br&gt;
twice in a row. &lt;strong&gt;So the ceiling hit with one session is not a single flow's capacity.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;1,135 MB/s is 9.08 Gbps. In the same environment, the file protocols landed near the documented&lt;br&gt;
5 Gbps on a single connection — NFS 591.62 MB/s (4.73 Gbps), SMB 574.24 MB/s (4.59 Gbps).&lt;br&gt;
&lt;strong&gt;A single iSCSI session carries roughly twice that. Why is unconfirmed.&lt;/strong&gt; (This comparison is&lt;br&gt;
against single-connection figures from other protocols in the same environment, not a direct A/B&lt;br&gt;
test against iSCSI.)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Here's the split between what's documented and what's an unverified candidate. &lt;strong&gt;The single-flow&lt;br&gt;
5 Gbps ceiling itself is documented.&lt;/strong&gt; AWS defines a single flow as "a TCP or UDP flow identified&lt;br&gt;
by a unique 5-tuple (source IP, destination IP, source port, destination port, protocol)" and&lt;br&gt;
states that &lt;strong&gt;outside a cluster placement group, a single flow is limited to 5 Gbps&lt;/strong&gt;&lt;br&gt;
(&lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-network-bandwidth.html" rel="noopener noreferrer"&gt;Amazon EC2 instance network bandwidth&lt;/a&gt;).&lt;br&gt;
The same page documents the workarounds too — &lt;strong&gt;up to 10 Gbps inside a cluster placement group,&lt;br&gt;
up to 25 Gbps within the same AZ with ENA Express, or spreading traffic over multiple paths with&lt;br&gt;
Multipath TCP (MPTCP)&lt;/strong&gt;&lt;br&gt;
(&lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ena-express.html" rel="noopener noreferrer"&gt;ENA Express&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;From here on, these are unverified candidates.&lt;/strong&gt; Whether this measurement environment was&lt;br&gt;
inside a cluster placement group was not recorded and is unconfirmed. 9.08 Gbps would fit inside&lt;br&gt;
a 10 Gbps allowance, but &lt;strong&gt;whether this configuration was inside that allowance is unknown.&lt;/strong&gt;&lt;br&gt;
Whether iSCSI, NVMe/TCP, NFS, and SMB all count as "one" the same way against the 5-tuple&lt;br&gt;
definition is also unconfirmed. ENA elastic network interfaces are documented as having up to 8&lt;br&gt;
RSS queues&lt;br&gt;
(&lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/enhanced-networking-os.html" rel="noopener noreferrer"&gt;Optimize network performance on EC2 Windows instances&lt;/a&gt; —&lt;br&gt;
that page covers Windows; I haven't checked the equivalent for Linux), and if protocols&lt;br&gt;
distribute across CPU cores differently, the apparent ceiling could differ as a result. &lt;strong&gt;This&lt;br&gt;
article does not test these candidates. A separate measurement would be needed.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The requested queue count doesn't hold
&lt;/h2&gt;

&lt;p&gt;NVMe/TCP has nothing equivalent to &lt;code&gt;nr_sessions&lt;/code&gt;. &lt;strong&gt;It opens a TCP connection per queue&lt;/strong&gt;, so the&lt;br&gt;
quantity that corresponds to session count is queue count.&lt;/p&gt;

&lt;p&gt;Requesting via &lt;code&gt;--nr-io-queues&lt;/code&gt; produced this. NCQA (Number of Completion Queues Allocated) and&lt;br&gt;
NSQA (Number of Submission Queues Allocated) are the actual completion and submission queue&lt;br&gt;
counts the NVMe Get Feature command reports back to the host.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Requested queue count&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Effective NCQA / NSQA&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;Controller count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Single flow&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AWS's procedure as written (one &lt;code&gt;connect-all&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;unspecified&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explicit queue count&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;36&lt;/strong&gt; (vCPU count)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4 (unchanged)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Requesting 36 produced 4 regardless.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This isn't speculation — it's documented on the ONTAP side. &lt;code&gt;vserver nvme subsystem show&lt;/code&gt;'s&lt;br&gt;
description of &lt;code&gt;-default-io-queue-count&lt;/code&gt; states "The actual value used when a connection is&lt;br&gt;
established may vary depending on the host and transport protocol used," and&lt;br&gt;
&lt;a href="https://docs.netapp.com/us-en/ontap-cli/vserver-nvme-show-host-priority.html" rel="noopener noreferrer"&gt;&lt;code&gt;vserver nvme show-host-priority&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
states that &lt;strong&gt;I/O queue count is determined per combination of node, transport, and host&lt;br&gt;
priority.&lt;/strong&gt; NetApp's KB has an example where setting 15 on a subsystem produced 2 on the host&lt;br&gt;
side.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Don't use the example's value as your own environment's value. In the example above,&lt;br&gt;
&lt;code&gt;nvme-tcp&lt;/code&gt; / &lt;code&gt;regular&lt;/code&gt; is 2, but &lt;strong&gt;in this environment it was 4.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As a byproduct, one thing left as "unconfirmed" in planning got resolved. &lt;strong&gt;&lt;code&gt;queue_count&lt;/code&gt; includes&lt;br&gt;
the admin queue and matches &lt;code&gt;NCQA + 1&lt;/code&gt;&lt;/strong&gt; (1 -&amp;gt; 2, 4 -&amp;gt; 5). With one queue, there was also exactly&lt;br&gt;
one established TCP connection carrying I/O.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four failures hit before mounting
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Nine failures were hit before reaching a measurement. All were gates, not numbers.&lt;/strong&gt; Each one&lt;br&gt;
looks like it succeeded while measuring nothing. Four of those happened before mounting, so&lt;br&gt;
they're here; the rest concern the measurement tool and teardown and are in the&lt;br&gt;
&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/.private/blog-unpublished-block-c-what-moves-the-numbers-en.md" rel="noopener noreferrer"&gt;separate article&lt;/a&gt; (C:&lt;br&gt;
what moves the numbers). Details, as a table of symptom / cause / how to check, are in the&lt;br&gt;
&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/ja/verification/block-measurement-runbook.md" rel="noopener noreferrer"&gt;block measurement runbook&lt;/a&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;JunctionPath&lt;/code&gt; turned out to be required. I had assumed it wasn't needed since a LUN isn't
read through a junction, but the API rejects that. Worse, the
&lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-properties-fsx-volume-ontapconfiguration.html" rel="noopener noreferrer"&gt;property reference&lt;/a&gt;'s
entry for &lt;code&gt;JunctionPath&lt;/code&gt; says &lt;code&gt;This parameter is required.&lt;/code&gt; in the body while the same property's
&lt;code&gt;Required:&lt;/code&gt; column says &lt;code&gt;No&lt;/code&gt;. &lt;strong&gt;That's a literal self-contradiction&lt;/strong&gt; (still present as of
2026-09-17). Trust the API. It fails 25 minutes after file system creation starts.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The same page has an example of writing this without contradiction. &lt;code&gt;StorageEfficiencyEnabled&lt;/code&gt;&lt;br&gt;
uses &lt;code&gt;Required: Conditional&lt;/code&gt; and explains the condition in the body (required when the volume is&lt;br&gt;
&lt;code&gt;RW&lt;/code&gt;). &lt;strong&gt;&lt;code&gt;Conditional&lt;/code&gt; is a value this reference already uses, so &lt;code&gt;JunctionPath&lt;/code&gt; could take the&lt;br&gt;
same form.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I can't state exactly which condition makes it required. What I measured is one case,&lt;br&gt;
&lt;code&gt;OntapVolumeType: RW&lt;/code&gt;, where it was required. I did not test whether it's optional for&lt;br&gt;
&lt;code&gt;DP&lt;/code&gt;. Determining the condition belongs to whoever holds the API's implementation;&lt;br&gt;
&lt;strong&gt;what I can report stops at "the body and the &lt;code&gt;Required:&lt;/code&gt; column disagree."&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;2. AL2023 does not create &lt;code&gt;/etc/nvme/hostnqn&lt;/code&gt;.&lt;/strong&gt; AWS's procedure assumes this file exists and&lt;br&gt;
reads it with &lt;code&gt;cat&lt;/code&gt; (RHEL creates it). Generating it with &lt;code&gt;nvme gen-hostnqn&lt;/code&gt; was necessary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. iSCSI login is asynchronous.&lt;/strong&gt; &lt;code&gt;iscsiadm --login&lt;/code&gt; returns before iscsid actually connects. In&lt;br&gt;
this environment it took about 100 seconds. Counting with &lt;code&gt;ss&lt;/code&gt; right after returned 0, so&lt;br&gt;
every alias and fill attempt right after also came up empty. "The command returned" is not&lt;br&gt;
"the connection is up."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. &lt;code&gt;find_multipaths yes&lt;/code&gt; does not create a map for a single-path device.&lt;/strong&gt; When intentionally&lt;br&gt;
measuring a single flow, the path count is 1 by design, and with the default setting&lt;br&gt;
&lt;code&gt;/dev/mapper/&amp;lt;alias&amp;gt;&lt;/code&gt; never appears. Explicit registration via &lt;code&gt;multipath -a &amp;lt;wwid&amp;gt;&lt;/code&gt; was&lt;br&gt;
necessary. That wwid is also more reliably read from the device with &lt;code&gt;scsi_id&lt;/code&gt; than assembled as&lt;br&gt;
&lt;code&gt;3600a0980&lt;/code&gt;+serial (assembling it is useful as a cross-check, not as the primary source).&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sizing session count by dividing required bandwidth by 625 MBps missed in both directions.&lt;/strong&gt;
1.82x with one session, 0.20x with sixteen.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Going from one session to two doesn't move sequential throughput.&lt;/strong&gt; The ceiling hit with one
session is not a single flow's capacity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NVMe/TCP queue count is the result of negotiation.&lt;/strong&gt; Requesting 36 produced 4. Read the
effective value with &lt;code&gt;nvme get-feature --feature-id 7&lt;/code&gt;. &lt;code&gt;queue_count&lt;/code&gt; includes the admin queue,
so it equals &lt;code&gt;NCQA + 1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;All four configuration failures looked like success while measuring nothing.&lt;/strong&gt; &lt;code&gt;JunctionPath&lt;/code&gt;
is required, AL2023 doesn't create &lt;code&gt;hostnqn&lt;/code&gt;, iSCSI login is asynchronous at about 100 seconds,
and &lt;code&gt;find_multipaths yes&lt;/code&gt; doesn't map a single-path device.&lt;/li&gt;
&lt;li&gt;Don't use this article's figures for sizing. The same configuration swings 2.64x across
environments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What drives that 2.64x swing is out of scope for this article.&lt;/strong&gt; It's covered in a
&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/.private/blog-unpublished-block-c-what-moves-the-numbers-en.md" rel="noopener noreferrer"&gt;separate article&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  AWS documentation notes and support inquiries
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;This article touches two points in the documentation. One has been filed; the other is not&lt;br&gt;
being filed.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Finding&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;JunctionPath&lt;/code&gt;'s body and &lt;code&gt;Required:&lt;/code&gt; column disagree. Traced to the &lt;a href="https://docs.aws.amazon.com/fsx/latest/APIReference/API_CreateOntapVolumeConfiguration.html" rel="noopener noreferrer"&gt;API reference&lt;/a&gt;, which propagates into &lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-properties-fsx-volume-ontapconfiguration.html" rel="noopener noreferrer"&gt;CloudFormation&lt;/a&gt; and the CDK&lt;/td&gt;
&lt;td&gt;same&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Filed (2026-09-17). AWS's reply (2026-09-22): explained that when OntapVolumeType is RW, JunctionPath is required; when DP, it cannot be specified at all — so "Required: No" has a rationale when read across both volume types combined.&lt;/strong&gt; The proposed change to &lt;code&gt;Required: Conditional&lt;/code&gt; was described as feedback to be shared with the internal team, with no commitment on adoption. The question of whether the API reference is upstream of CFN/CDK went unanswered ("internal AWS information, cannot disclose"); instead AWS said it would file feedback against the API reference, CFN, and CDK pages individually&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sizing by dividing 5 Gbps (~625 MBps) doesn't match measurement&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://docs.aws.amazon.com/fsx/latest/ONTAPGuide/mount-iscsi-luns-linux.html" rel="noopener noreferrer"&gt;iSCSI&lt;/a&gt; / &lt;a href="https://docs.aws.amazon.com/fsx/latest/ONTAPGuide/provision-nvme-linux.html" rel="noopener noreferrer"&gt;NVMe/TCP&lt;/a&gt; procedures&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Not filed.&lt;/strong&gt; AWS never states a formula, so this isn't a documentation error&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Why the second one isn't filed.&lt;/strong&gt; This is a record of my own sizing approach missing, not a&lt;br&gt;
finding about the documentation. The figure itself is correctly documented as a single flow's&lt;br&gt;
ceiling; I was the one who used it as a divisor for session count, and that's where it broke&lt;br&gt;
down.&lt;/p&gt;

&lt;p&gt;The first was filed with AWS Support on 2026-09-17. The submission included the observed&lt;br&gt;
requirement (omitting it fails stack creation 25 minutes in) and the fact that the CDK inherits&lt;br&gt;
it as an optional type, requesting a fix on the API reference side.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AWS's 2026-09-22 reply settles only the rationale behind "Required: No"&lt;/strong&gt; (it holds when RW and&lt;br&gt;
DP are read as one combined attribute). It doesn't change the underlying fact that JunctionPath is&lt;br&gt;
required when creating an RW volume, and whether &lt;code&gt;Required: Conditional&lt;/code&gt; will actually be adopted&lt;br&gt;
remains unconfirmed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Primary record of the figures
&lt;/h2&gt;

&lt;p&gt;The primary record of the figures and conditions is in the&lt;br&gt;
&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/ja/verification/perf-matrix-results.md" rel="noopener noreferrer"&gt;per-protocol measurement results&lt;/a&gt;&lt;br&gt;
(Japanese); the reproduction steps are in the&lt;br&gt;
&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/ja/verification/block-measurement-runbook.md" rel="noopener noreferrer"&gt;block measurement runbook&lt;/a&gt;&lt;br&gt;
(Japanese). Anything marked unconfirmed there is marked unconfirmed for a reason — &lt;strong&gt;don't drop&lt;br&gt;
that qualifier when citing it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Considerations before sizing session and queue counts, and pitfalls hit before mounting, are&lt;br&gt;
collected in&lt;br&gt;
&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/en/reference/block-protocol-testing-guide.md" rel="noopener noreferrer"&gt;Considerations when measuring FSx for ONTAP's block protocols&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Submission status across the three block-protocol articles and S3 Burst Part 2 is collected in&lt;br&gt;
&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/en/reference/block-protocol-aws-feedback-status.md" rel="noopener noreferrer"&gt;AWS documentation notes and inquiries across the block-protocol articles&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>performance</category>
      <category>cloud</category>
      <category>linux</category>
    </item>
    <item>
      <title>Amazon S3 Files vs. Amazon FSx for NetApp ONTAP: two designs for using S3 as a file (S3 Burst Part 2)</title>
      <dc:creator>Yoshiki Fujiwara(藤原 善基)@AWS Community Builder</dc:creator>
      <pubDate>Wed, 30 Sep 2026 05:43:06 +0000</pubDate>
      <link>https://dev.to/aws-builders/amazon-s3-files-vs-amazon-fsx-for-netapp-ontap-two-designs-for-using-s3-as-a-file-s3-burst-part-4p9m</link>
      <guid>https://dev.to/aws-builders/amazon-s3-files-vs-amazon-fsx-for-netapp-ontap-two-designs-for-using-s3-as-a-file-s3-burst-part-4p9m</guid>
      <description>&lt;h1&gt;
  
  
  Amazon S3 Files vs. Amazon FSx for NetApp ONTAP: two designs for using S3 as a file, measured on the same host (S3 Burst Part 2)
&lt;/h1&gt;

&lt;p&gt;Keeping the primary copy of your data in one place while reaching it both as a file (NFS/SMB)&lt;br&gt;
and over the S3 API: have you had to weigh which design to pick before committing to one?&lt;/p&gt;

&lt;p&gt;There is more than one way on AWS to combine the two. Worth pausing on this, though: every&lt;br&gt;
design has a point where "fast enough" turns into a ceiling, and that point sits in a different&lt;br&gt;
place for each one. It helps to know where each one bottlenecks before choosing. This article&lt;br&gt;
measures three of them from the same host.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl38s3pd4b9p888q2ykcx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl38s3pd4b9p888q2ykcx.png" alt="The three-path configuration and where each bottlenecks" width="799" height="696"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The same configuration as the image above, reproduced so it reads in environments where images&lt;br&gt;
don't render.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    subgraph AWS["AWS Cloud"]
        subgraph VPC["VPC, single AZ"]
            EC2["EC2 client"]
            subgraph A["A: This architecture"]
                AP["S3 Access Point"]
                FSXN[("Amazon FSx for&amp;lt;br/&amp;gt;NetApp ONTAP")]
                AP --&amp;gt; FSXN
            end
            S3["Amazon S3&amp;lt;br/&amp;gt;(bucket)"]
            S3FILES["Amazon S3 Files&amp;lt;br/&amp;gt;(via efs-proxy)"]
            EC2 --&amp;gt;|"write over S3 API"| AP
            FSXN --&amp;gt;|"read over NFS / SMB"| EC2
            EC2 &amp;lt;--&amp;gt;|"read/write over S3 API"| S3
            EC2 &amp;lt;--&amp;gt;|"read/write over NFS"| S3FILES
        end
    end&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;The figure states the same thing as the table below. Only A uses a different protocol for&lt;br&gt;
writes versus reads; B and C have the same client reading and writing over the same protocol.&lt;br&gt;
The figure's three panels map directly to the three below.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Write&lt;/th&gt;
&lt;th&gt;Read&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;A&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FSx for ONTAP's S3 Access Point (this architecture, below)&lt;/td&gt;
&lt;td&gt;S3 API&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;NFS / SMB&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;B&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Amazon S3&lt;/td&gt;
&lt;td&gt;S3 API&lt;/td&gt;
&lt;td&gt;S3 API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;C&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Amazon S3 Files (an S3 bucket mounted over NFS)&lt;/td&gt;
&lt;td&gt;NFS&lt;/td&gt;
&lt;td&gt;NFS&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Only A uses a different protocol and client for writes versus reads.&lt;/strong&gt; That's the substance of&lt;br&gt;
this architecture: something written over the S3 API is read over NFS/SMB with no copy job in&lt;br&gt;
between. That's why the figure shows two arrow types for A, while B and C are a single&lt;br&gt;
bidirectional arrow — the same client reads and writes over the same protocol in B and C.&lt;/p&gt;

&lt;p&gt;Measure throughput and lay it out in a table, and the three numbers sit side by side. Sitting side&lt;br&gt;
by side reads as "this one is faster," but each of the three was bottlenecked for a different&lt;br&gt;
reason. Under each panel of the figure is where that path was actually bottlenecked. A throughput&lt;br&gt;
value provisioned on the file system, client-side network bandwidth, the CPU of a process running&lt;br&gt;
on the client. &lt;strong&gt;What the bottleneck is matters more to design than what the ceiling number is.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The measuring side has its own prerequisite. &lt;strong&gt;There are five settings that, left at default,&lt;br&gt;
measure something other than what you intended.&lt;/strong&gt; That's covered in the&lt;br&gt;
&lt;a href="https://hakobiya.hatenablog.com/entry/fsxn-file-portal-5-what-only-the-cluster-tells-you" rel="noopener noreferrer"&gt;previous article (Part 5, "five defaults that can invalidate a measurement")&lt;/a&gt;.&lt;br&gt;
This article's figures were measured after changing all five from default.&lt;/p&gt;
&lt;h2&gt;
  
  
  Scope of this article
&lt;/h2&gt;
&lt;h3&gt;
  
  
  What it covers
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The three ceilings that bind on the FSx for ONTAP side, and which one actually applied&lt;/li&gt;
&lt;li&gt;What bottlenecks each of the three paths&lt;/li&gt;
&lt;li&gt;What FlexCache does for reads&lt;/li&gt;
&lt;li&gt;What becomes the ceiling as client count scales to 8&lt;/li&gt;
&lt;li&gt;Whether a 15-minute sustained write decays&lt;/li&gt;
&lt;li&gt;The bottleneck shift with small objects&lt;/li&gt;
&lt;li&gt;What each tool can and can't observe&lt;/li&gt;
&lt;li&gt;What hasn't been measured yet&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  What it does not cover
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Which configuration is faster.&lt;/strong&gt; The three figures in this article &lt;strong&gt;mix a provisioned
ceiling with an unprovisioned elastic ceiling.&lt;/strong&gt; They aren't laid out to be read side by side for
a winner, so wherever a number appears, the type of ceiling it's hitting is stated alongside it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How to choose a configuration itself.&lt;/strong&gt; Where to place the read side, which side holds the
primary copy, when you want writes visible on the bucket side — these are decided by other
factors and throughput numbers alone don't decide them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The semantics of Amazon S3 Files propagation.&lt;/strong&gt; That file-to-bucket propagation converges
within roughly a 60-second window, and the reverse direction is on the order of seconds, is a
different subject and belongs in a separate article.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IAM policy and the authorization model.&lt;/strong&gt; The AWS side of an S3 Access Point (IAM identity
policy, access point policy, VPC endpoint policy, SCP) and ONTAP's two-layer authorization are a
different axis from this article's subject (throughput). Minimum-privilege policy examples are
in the
&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/en/reference/limits/s3ap-design-guide.md#authorization-design--least-privilege-policy-examples" rel="noopener noreferrer"&gt;S3 AP design guide's authorization design section&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Measurement environment
&lt;/h2&gt;

&lt;p&gt;Figures can't be reproduced without the environment they came from, so it's placed first.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Measured on&lt;/td&gt;
&lt;td&gt;2026-09-01 and 2026-09-02&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Region&lt;/td&gt;
&lt;td&gt;ap-northeast-1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ONTAP&lt;/td&gt;
&lt;td&gt;9.18.1P3D1 (the second-generation NFS 2-host measurement used 9.18.1P6. The cross-article version mapping is in the &lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/en/reference/block-protocol-ontap-version-matrix.md" rel="noopener noreferrer"&gt;ONTAP version matrix&lt;/a&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File system&lt;/td&gt;
&lt;td&gt;SINGLE_AZ_1 (first-generation Single-AZ). Measured at two throughput capacities, 128 MBps and 2048 MBps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSD&lt;/td&gt;
&lt;td&gt;1024 GiB, &lt;code&gt;DiskIopsConfiguration&lt;/code&gt; at two points, 3,072 and 40,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client&lt;/td&gt;
&lt;td&gt;c5n.9xlarge (single-client measurements), c5n.2xlarge x8 (client-count scaling test — adding one client at a time and observing how the combined throughput grows)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Volume&lt;/td&gt;
&lt;td&gt;UNIX security style, NFSv3, &lt;code&gt;actimeo=0&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test data&lt;/td&gt;
&lt;td&gt;Uncompressible (from &lt;code&gt;/dev/urandom&lt;/code&gt;). Zero-filled data was not used. &lt;strong&gt;These are figures from a world with efficiency features off&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AZ configuration&lt;/td&gt;
&lt;td&gt;All figures are from a Single-AZ configuration. &lt;strong&gt;Multi-AZ is unverified&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both NFS and SMB were measured, but the scope differs. The per-protocol comparison table and&lt;br&gt;
per-path bottlenecks are measured over NFS. What was measured over SMB is the growth pattern with&lt;br&gt;
increasing client count, the 15-minute sustained write, and Multichannel channel count. &lt;strong&gt;The&lt;br&gt;
seven scenarios under cache control were not measured over SMB.&lt;/strong&gt; This architecture can be read&lt;br&gt;
over either NFS or SMB, but this article's figures are all NFS. If you plan to read over SMB,&lt;br&gt;
the figures below don't directly apply. What carries over is the measurement approach and the&lt;br&gt;
procedure for isolating which type of ceiling is binding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reproducibility under identical conditions was 0.02-0.14%.&lt;/strong&gt; With that baseline established, a&lt;br&gt;
"moved 30%" statement later in this article can be said not to be run-to-run noise.&lt;/p&gt;

&lt;p&gt;Unless otherwise noted, figures in the body are single measurements. Where something was&lt;br&gt;
measured twice, both values are shown as &lt;code&gt;415.1 / 415.7&lt;/code&gt; rather than averaged or taking a median.&lt;br&gt;
Totals in the client-count tables are the sum of each host's value.&lt;/p&gt;

&lt;p&gt;And every figure in this article is a saturation point — however much could be pushed through at&lt;br&gt;
a fixed concurrency. No point gives a target throughput or target IOPS. This distinction&lt;br&gt;
matters as much as stating which type of ceiling applies — a saturation point answers "where does&lt;br&gt;
it choke," not "what response time does it deliver at that load." Measuring both on a separate file&lt;br&gt;
system (second-generation, 6,144 MBps), a target of 4,400 IOPS produced 4,406 MB/s at 8.86 ms,&lt;br&gt;
while unlimited produced 4,204 MB/s at 121.79 ms. &lt;strong&gt;Unlimited is 5% lower throughput and 14x&lt;br&gt;
the response time.&lt;/strong&gt; Source:&lt;br&gt;
&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/ja/verification/perf-matrix-results.md#fsx-for-ontap-%E7%AC%AC%E4%BA%8C%E4%B8%96%E4%BB%A3nconnect16auto_vdbench%E7%9B%AE%E6%A8%99%E5%80%A4%E3%82%92%E6%8C%AF%E3%81%A3%E3%81%9F%E7%B3%BB%E5%88%97" rel="noopener noreferrer"&gt;the series with a varied target value&lt;/a&gt;&lt;br&gt;
(Japanese).&lt;/p&gt;

&lt;p&gt;Response time exists for the S3 API path and not for the file path. The S3 API side's body&lt;br&gt;
notes p50 alongside throughput (363.7 ms at concurrency 1, 1054.9 ms at concurrency 64, and so on).&lt;br&gt;
The file path's load generator didn't retain the values it output, and the environment has since&lt;br&gt;
been torn down. &lt;strong&gt;Because concurrency was fixed, it can be re-derived through the identity&lt;br&gt;
&lt;code&gt;concurrency / IOPS&lt;/code&gt;, but that's a derived value, not a measurement.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A separate article pushes further on the same property on the block protocol side&lt;br&gt;
(&lt;a href="https://dev.to/aws-builders/why-amazon-fsx-for-netapp-ontap-block-performance-differed-by-about-2x-on-the-same-specified-35pi"&gt;separate article&lt;/a&gt;). It&lt;br&gt;
covers a case where quadrupling thread count moves throughput by only 0.07% while response time&lt;br&gt;
alone quadruples, and &lt;strong&gt;the same 2,364 MB/s appears at both a 216 ms point and an 860 ms point.&lt;/strong&gt;&lt;br&gt;
That same caution applies when citing this article's figures.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The block protocol side is a set of three.&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://dev.to/aws-builders/moving-on-premises-block-storage-to-aws-the-divide-by-625-mbps-sizing-approach-comes-out-wrong-31pc"&gt;Session and queue counts&lt;/a&gt;&lt;br&gt;
(for anyone about to mount),&lt;br&gt;
&lt;a href="https://dev.to/aws-builders/amazon-fsx-for-netapp-ontap-nvmetcp-assumes-rhel-93-why-multipathing-fails-on-amazon-linux-2023-1afl"&gt;checking whether ANA is available&lt;/a&gt;&lt;br&gt;
(for anyone who followed the procedure and got stuck), and&lt;br&gt;
&lt;a href="https://dev.to/aws-builders/why-amazon-fsx-for-netapp-ontap-block-performance-differed-by-about-2x-on-the-same-specified-35pi"&gt;what moves the numbers&lt;/a&gt;&lt;br&gt;
(for anyone measuring or citing figures). There's no order to read them in.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The measurement host was c5n.9xlarge. The reason is isolation. &lt;strong&gt;With an "Up to"-labeled instance,&lt;br&gt;
once a measured value plateaus, you can't tell whether you hit the storage-side ceiling or the&lt;br&gt;
client-side bandwidth ceiling.&lt;/strong&gt; c5n.9xlarge's 50 Gbps is a guaranteed value. Every figure below&lt;br&gt;
sits well under 50 Gbps, so the possibility that the client-side bandwidth was the cause of a&lt;br&gt;
plateau can be ruled out.&lt;/p&gt;

&lt;p&gt;Only the client-count scaling test used eight c5n.2xlarge instances instead, since that test wants&lt;br&gt;
more clients rather than a bigger single client.&lt;/p&gt;
&lt;h3&gt;
  
  
  The client-count scaling test and the sustained write used a separate file system
&lt;/h3&gt;

&lt;p&gt;The table above is first-generation. Only the client-count scaling test and the 15-minute&lt;br&gt;
sustained write were measured on a separate, second-generation file system. &lt;strong&gt;The first-generation&lt;br&gt;
one is a shared environment, and raising or lowering its provisioned throughput capacity would&lt;br&gt;
affect other measurements.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Measured on&lt;/td&gt;
&lt;td&gt;2026-09-05&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File system&lt;/td&gt;
&lt;td&gt;SINGLE_AZ_2 (second-generation Single-AZ), 6,144 MBps x1 HA pair&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSD&lt;/td&gt;
&lt;td&gt;4,096 GiB, provisioned 200,000 IOPS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client&lt;/td&gt;
&lt;td&gt;c5n.2xlarge at 1 / 2 / 4 / 6 / 8 hosts. &lt;strong&gt;64 threads and &lt;code&gt;nconnect=16&lt;/code&gt; fixed per host&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Because the environment differs, these figures can't be placed in the same column as the table&lt;br&gt;
above. &lt;strong&gt;Load per host was fixed and only the host count was varied, because fixing total thread&lt;br&gt;
count and dividing by host count would change two things at once per data point, making it&lt;br&gt;
impossible to separate which one caused the effect.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Six settings to change from default before measuring throughput (Part 5)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Each one, left at default, measures something other than what you intended.&lt;/strong&gt; And the number&lt;br&gt;
that comes back doesn't look unnatural, so measuring alone won't reveal it. The table and how each&lt;br&gt;
was noticed are in Part 5; only the names are listed here.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Linux's NFS mount opens only one TCP connection by default (plateaus around 590 MB/s)&lt;/li&gt;
&lt;li&gt;Data created with &lt;code&gt;dd if=/dev/zero&lt;/code&gt; (all-zero blocks never reach disk)&lt;/li&gt;
&lt;li&gt;Volume inline efficiency (compression/deduplication) enabled&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DiskIopsConfiguration&lt;/code&gt; at &lt;code&gt;AUTOMATIC&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Reads slightly exceeding the read cache's capacity&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  More on item 5 — the read cache's two-tier structure
&lt;/h3&gt;

&lt;p&gt;Item 5 is covered in detail because it bears directly on this article's figures.&lt;/p&gt;

&lt;p&gt;FSx for ONTAP's file server has &lt;strong&gt;two tiers of read cache: in-memory and NVMe&lt;/strong&gt;&lt;br&gt;
(&lt;a href="https://docs.aws.amazon.com/fsx/latest/ONTAPGuide/performance.html" rel="noopener noreferrer"&gt;performance specs&lt;/a&gt;). For&lt;br&gt;
ap-northeast-1's first-generation Single-AZ, what's published differs by tier.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;Capacity at 2048 MBps&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;In-memory&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;256 GB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Performance specs, the "other regions" table&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NVMe read cache&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Not stated&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No column exists in that same table&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For NVMe, only a condition is stated in a separate section: "A Single-AZ 1 created on or after&lt;br&gt;
2022-11-28 with a throughput capacity of 2 GBps or more gets a read cache." This file system meets&lt;br&gt;
that condition. &lt;strong&gt;Checking with the ONTAP CLI directly confirmed it was enabled.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;128 MBps configuration : system node external-cache -&amp;gt; 0 records
2048 MBps configuration: is_enabled: true (both nodes)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And how SSD IOPS gets used is stated explicitly in the same documentation. &lt;strong&gt;SSD IOPS is used only&lt;br&gt;
when reading data that's in neither the in-memory cache nor the NVMe cache.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With that in mind, the measurement can be read this way. A 280 GiB read exceeds the in-memory&lt;br&gt;
cache's 256 GB (= 238 GiB) by 18%, but there's an NVMe read cache behind it. This&lt;br&gt;
configuration's NVMe capacity isn't published (in the four regions with a column, it's 1,900 GB at&lt;br&gt;
the same 2048 MBps). &lt;strong&gt;The measured 98.5-99.9% of read bytes not going to disk is consistent with&lt;br&gt;
coming back from these two tiers&lt;/strong&gt; (breakdown under "Per-tool visibility" below).&lt;/p&gt;

&lt;p&gt;That's why the observation "raising SSD IOPS from 3,072 to 40,000 made reads 7.14x faster"&lt;br&gt;
didn't square with the documentation. If it's coming back from cache, SSD IOPS shouldn't matter.&lt;/p&gt;

&lt;p&gt;That mismatch went away on re-measurement (2026-09-17). Setting the working set to 512 GiB —&lt;br&gt;
2.15x the in-memory cache's 238 GiB — and disabling the NVMe cache, re-measuring showed&lt;br&gt;
96-126% of read bytes coming from disk, with the low end saturating at &lt;code&gt;DiskIopsUtilization&lt;/code&gt;&lt;br&gt;
112%. &lt;strong&gt;"SSD IOPS determines the disk throughput level" is consistent with measurement under&lt;br&gt;
this condition.&lt;/strong&gt; So the mismatch's cause wasn't the documentation — it was my measurement:&lt;br&gt;
a 280 GiB working set only exceeded the cache by 18%.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The ratio itself changed, though.&lt;/strong&gt; 256.47 -&amp;gt; 1,246.67 MB/s, 4.86x. 7.14x doesn't&lt;br&gt;
reproduce. And what's capping the high side, around 1,250 MB/s, is none of the published&lt;br&gt;
utilization metrics (disk throughput 67% / IOPS 43% / network 43%). It isn't concurrency&lt;br&gt;
either — going from 8 to 32 to 64 threads drops it from 1,071 to 999 to 958, with response time&lt;br&gt;
alone scaling proportionally (6.9 -&amp;gt; 31.0 -&amp;gt; 59.3 ms). This is left unresolved.&lt;/p&gt;

&lt;p&gt;To check in your own environment, the order is two steps. Check for an NVMe cache with&lt;br&gt;
&lt;code&gt;system node external-cache&lt;/code&gt;, and then read a volume that reliably exceeds the sum of both&lt;br&gt;
tiers in one pass. &lt;strong&gt;A first-generation Single-AZ at 2 GBps or more gets an NVMe cache by&lt;br&gt;
default — there's no way to prevent it from being created, only to create it and then disable&lt;br&gt;
it.&lt;/strong&gt; I forgot this once and measured four points with it left enabled.&lt;/p&gt;
&lt;h3&gt;
  
  
  Item 6 — SMB Multichannel disabled by default
&lt;/h3&gt;

&lt;p&gt;ONTAP's SMB Multichannel is disabled by default. This is a separate setting from SMB 3.x&lt;br&gt;
itself, so a channel can stay at one even while negotiated at &lt;code&gt;dialect=3.1.1&lt;/code&gt;. That state held in&lt;br&gt;
this measurement environment: while one host was reading, only one established TCP connection to&lt;br&gt;
the SVM existed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enabling it doesn't reach connections already established.&lt;/strong&gt; Enabling only affects new&lt;br&gt;
connections. Removing and re-mapping the drive kept the same local port, and it only became four&lt;br&gt;
channels after restarting the SMB client service.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;max_connections_per_session&lt;/code&gt; is a separate value. &lt;strong&gt;With Multichannel disabled, setting it to 32&lt;br&gt;
still yields one channel, and you can misread "raised the limit but it didn't increase" as a&lt;br&gt;
result.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The mechanism, sources, and where to read channel count are in&lt;br&gt;
&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/ja/reference/limits/smb-multichannel-enablement.md" rel="noopener noreferrer"&gt;SMB Multichannel is disabled by default, and enabling it doesn't reach already-established connections&lt;/a&gt;&lt;br&gt;
(Japanese).&lt;/p&gt;
&lt;h2&gt;
  
  
  Three ceilings that limit writes on the FSx for ONTAP side
&lt;/h2&gt;

&lt;p&gt;Before getting into the three-path comparison, here's what limits this architecture's own side.&lt;br&gt;
&lt;strong&gt;There's a ceiling that doesn't move even when throughput capacity is raised, and not knowing it&lt;br&gt;
leads to a sizing miss.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One term first. Throughput capacity is a value — 128 / 256 / 512 / 1024 / 2048 MBps and so on —&lt;br&gt;
that you set on the file system. It isn't metered usage; you're billed by time against the&lt;br&gt;
value you set. This article measures at two points, 128 MBps and 2048 MBps.&lt;/p&gt;

&lt;p&gt;For ap-northeast-1's first-generation Single-AZ, three ceilings bind on writes, and &lt;strong&gt;the one that&lt;br&gt;
actually applies is whichever is smallest.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Ceiling&lt;/th&gt;
&lt;th&gt;This environment's value&lt;/th&gt;
&lt;th&gt;How it's determined&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Provisioned throughput capacity&lt;/td&gt;
&lt;td&gt;128 / 2048 MBps&lt;/td&gt;
&lt;td&gt;Set by you&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disk throughput out of SSD&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;768 MBps&lt;/strong&gt; (768 MBps/TiB x 1 TiB, when SSD IOPS is &lt;code&gt;AUTOMATIC&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Driven by the provisioned SSD IOPS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per-HA-pair ceiling&lt;/td&gt;
&lt;td&gt;Write &lt;strong&gt;750 MBps&lt;/strong&gt; (read is 2,048 MBps)&lt;/td&gt;
&lt;td&gt;Determined by the file system's generation and configuration&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Source: &lt;a href="https://docs.aws.amazon.com/fsx/latest/ONTAPGuide/performance.html" rel="noopener noreferrer"&gt;performance specs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The second row isn't a fixed value determined by SSD capacity alone.&lt;/strong&gt; 768 MBps/TiB is what you&lt;br&gt;
get when SSD IOPS is set to &lt;code&gt;AUTOMATIC&lt;/code&gt; (3 IOPS/GiB). Raising SSD IOPS with &lt;code&gt;USER_PROVISIONED&lt;/code&gt;&lt;br&gt;
raises that 768 MBps/TiB figure right along with it. Conversely, raising only throughput&lt;br&gt;
capacity to 2048 MBps while leaving SSD IOPS at &lt;code&gt;AUTOMATIC&lt;/code&gt; leaves disk throughput capped at 768&lt;br&gt;
MBps/TiB. There are two levers, and raising only one doesn't move the ceiling.&lt;/p&gt;

&lt;p&gt;Laying out which configuration hits which ceiling:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Throughput capacity&lt;/th&gt;
&lt;th&gt;Ceiling that applied&lt;/th&gt;
&lt;th&gt;Measured (uncompressible, 8 MiB, concurrency 64)&lt;/th&gt;
&lt;th&gt;Against that ceiling&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;128 MBps&lt;/td&gt;
&lt;td&gt;The provisioned 128 MBps&lt;/td&gt;
&lt;td&gt;133.4 MB/s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;104%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2048 MBps&lt;/td&gt;
&lt;td&gt;The per-HA-pair write ceiling, 750 MBps&lt;/td&gt;
&lt;td&gt;415 MB/s (measured twice: 415.1 / 415.7)&lt;/td&gt;
&lt;td&gt;55%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Landing at 104% of the 128 MBps configuration — slightly over the provisioned value — is a&lt;br&gt;
burst effect.&lt;/strong&gt; This configuration's disk throughput can sustain 128 MBps continuously, or 600&lt;br&gt;
MBps briefly. Bursting was available but the measurement stopped at 133.4, so the provisioned&lt;br&gt;
throughput capacity was the binding ceiling in this configuration.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;On second-generation, the meaning of the provisioned value itself is different.&lt;/strong&gt; In a separate&lt;br&gt;
measurement, a second-generation Single-AZ's provisioned value points to a disk throughput&lt;br&gt;
&lt;strong&gt;baseline&lt;/strong&gt;, and the network-side baseline is roughly twice that. On a configuration provisioned&lt;br&gt;
at 1,536 MBps, 1 MiB sequential reads produced &lt;strong&gt;2,882 MB/s for 27 minutes, then 1,439 MB/s.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Carrying this table's first-generation reading over to second-generation misses a read sizing&lt;br&gt;
estimate by 2x&lt;/strong&gt;&lt;br&gt;
(&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/ja/verification/throughput-capacity-burst-and-baseline.md" rel="noopener noreferrer"&gt;measurement record&lt;/a&gt;)&lt;br&gt;
(Japanese).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At the 2048 MBps configuration, the provisioned value stopped being the ceiling. The denominator&lt;br&gt;
for percentage changes here, so both are given. 415 MB/s is 20% against the provisioned 2048&lt;br&gt;
MBps. &lt;strong&gt;Against the per-HA-pair write ceiling of 750 MBps that actually applied, it's 55%.&lt;/strong&gt; The&lt;br&gt;
surprise of "provisioned 2048 MBps, only got 415" is explained by the provisioned value no longer&lt;br&gt;
being the applicable ceiling.&lt;/p&gt;

&lt;p&gt;What's consuming the remaining 45% — the unused portion of the 750 MBps — hasn't been isolated.&lt;br&gt;
Two candidates come from the published specs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Writes use twice the network bandwidth of reads.&lt;/strong&gt; This is documented explicitly, because a
write replicates to the secondary file server, so a single write turns into twice the network
throughput.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A fixed cost per request.&lt;/strong&gt; Raising concurrency only stretched p50 (median latency) out to
1054.9 ms (at concurrency 64). If there's a fixed cost per single request, raising concurrency
doesn't raise requests-per-second. &lt;strong&gt;If request rate doesn't rise, total throughput doesn't
rise either.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which one is doing how much hasn't been isolated. This isn't a case where "the reason it doesn't&lt;br&gt;
reach 750 is this one thing" can be stated.&lt;/p&gt;
&lt;h2&gt;
  
  
  Bottlenecks sitting in different places per path
&lt;/h2&gt;

&lt;p&gt;This is the main point. Three numbers that come out in the same unit, MB/s, were each plateauing&lt;br&gt;
somewhere different. &lt;strong&gt;The figure's A / B / C map in that order.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Where the bottleneck sits&lt;/th&gt;
&lt;th&gt;What increases it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;This architecture (FSx for ONTAP S3 Access Point)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Over the S3 API path&lt;/strong&gt;, the &lt;strong&gt;provisioned&lt;/strong&gt; throughput capacity on the file system, shared across all clients&lt;/td&gt;
&lt;td&gt;Raise the provisioned value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amazon S3&lt;/td&gt;
&lt;td&gt;Not the service side — &lt;strong&gt;client-side network bandwidth&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Add more clients&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amazon S3 Files&lt;/td&gt;
&lt;td&gt;The CPU of the &lt;strong&gt;proxy process&lt;/strong&gt; running on the client&lt;/td&gt;
&lt;td&gt;Use a bigger client&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: the row above is scoped to the S3 API path. It's based on measurement (1.01x with 4&lt;br&gt;
clients). &lt;strong&gt;On the file path (NFS/SMB) of the same file system, reads exceeded the provisioned&lt;br&gt;
value of 6,144 MBps by more than 2x.&lt;/strong&gt; Reads served from cache don't go through disk, so stating&lt;br&gt;
"the provisioned value is the ceiling" without naming the path would contradict that&lt;br&gt;
measurement. Details in&lt;br&gt;
&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/ja/verification/perf-matrix-results.md" rel="noopener noreferrer"&gt;per-protocol measurement results&lt;/a&gt;&lt;br&gt;
(Japanese).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here's the decision flow for telling which of the above a measurement is hitting, in your own&lt;br&gt;
environment.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    START["A measured value plateaus"] --&amp;gt; Q1{"Does the total grow&amp;lt;br/&amp;gt;when adding clients?"}
    Q1 --&amp;gt;|"No"| C1["Provisioned throughput capacity&amp;lt;br/&amp;gt;on the file system&amp;lt;br/&amp;gt;(this architecture's S3 API path)"]
    Q1 --&amp;gt;|"Yes"| Q2{"Is the per-client value&amp;lt;br/&amp;gt;near the client's network bandwidth?"}
    Q2 --&amp;gt;|"Yes"| C2["Client-side network bandwidth&amp;lt;br/&amp;gt;(Amazon S3)"]
    Q2 --&amp;gt;|"No"| Q3{"Is CPU high on a process&amp;lt;br/&amp;gt;running on the client?"}
    Q3 --&amp;gt;|"Yes"| C3["CPU of a proxy process&amp;lt;br/&amp;gt;on the client&amp;lt;br/&amp;gt;(Amazon S3 Files)"]
    Q3 --&amp;gt;|"No"| C4["A factor outside these three.&amp;lt;br/&amp;gt;Isolate it separately"]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;The figure states the same thing as the table below. &lt;strong&gt;A single-host measurement alone can't&lt;br&gt;
determine the type&lt;/strong&gt; — telling them apart needs either adding clients, or holding transfer size&lt;br&gt;
and concurrency fixed while varying something else at one host (details under&lt;br&gt;
a single-client figure that was actually the client's own limit&lt;br&gt;
above).&lt;/p&gt;
&lt;h3&gt;
  
  
  FSx for ONTAP — a shared ceiling that wasn't one thing
&lt;/h3&gt;

&lt;p&gt;This is the left side of the figure's panel A, the S3 API write side. &lt;strong&gt;Adding clients doesn't&lt;br&gt;
raise the total.&lt;/strong&gt; With the same setting and varying client count, 4 clients produced 1.01x.&lt;br&gt;
Since the total doesn't change, each client's share is roughly 1/n of it.&lt;/p&gt;

&lt;p&gt;The reason is that the ceiling is a value provisioned on the file system. &lt;strong&gt;This is a&lt;br&gt;
configuration where capacity planning is done per file system, not per client.&lt;/strong&gt; If more clients&lt;br&gt;
are expected, plan for it on the throughput capacity side.&lt;/p&gt;

&lt;p&gt;Note that reads came out higher than the provisioned value. On a 128 MBps configuration, reads&lt;br&gt;
reached 579.3 MB/s (8 MiB, concurrency 16), &lt;strong&gt;4.5x the provisioned value.&lt;/strong&gt; Using the write-side&lt;br&gt;
ceiling to size reads will miss.&lt;/p&gt;
&lt;h4&gt;
  
  
  How the ceiling splits as client count grows to 8
&lt;/h4&gt;

&lt;p&gt;The result above, up to 4 clients, is first-generation with the provisioned value as the ceiling.&lt;br&gt;
&lt;strong&gt;Scaling to 8 clients / 128 connections on second-generation, with the same file system, same&lt;br&gt;
setting, and same connection count, the total swung 5.5x.&lt;/strong&gt; The only thing changed was "where each&lt;br&gt;
host reads from."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy7j7ut1k3bvkcpp5xc91.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy7j7ut1k3bvkcpp5xc91.png" alt="The two ceilings a read can hit" width="800" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With every host reading the same file:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hosts&lt;/th&gt;
&lt;th&gt;Connections&lt;/th&gt;
&lt;th&gt;Client total&lt;/th&gt;
&lt;th&gt;Port-measured&lt;/th&gt;
&lt;th&gt;Per host&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;2,664.58 MB/s&lt;/td&gt;
&lt;td&gt;2,698.7 MiB/s&lt;/td&gt;
&lt;td&gt;2,664.58&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;8,562.94 MB/s&lt;/td&gt;
&lt;td&gt;8,635.2 MiB/s&lt;/td&gt;
&lt;td&gt;2,140.74&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;128&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;11,916.29 MB/s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;12,173.0 MiB/s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1,489.54&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;With the working set held at a fixed 600 GiB, split into 75 GiB per host at 8 hosts, &lt;strong&gt;so no block&lt;br&gt;
is shared:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hosts&lt;/th&gt;
&lt;th&gt;Connections&lt;/th&gt;
&lt;th&gt;Client total&lt;/th&gt;
&lt;th&gt;Port-measured&lt;/th&gt;
&lt;th&gt;Per host&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;2,299.05 MB/s&lt;/td&gt;
&lt;td&gt;2,323.4 MiB/s&lt;/td&gt;
&lt;td&gt;1,149.53&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;128&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2,173.37 MB/s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2,191.7 MiB/s&lt;/td&gt;
&lt;td&gt;271.67&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Quadrupling connections from 32 to 128 doesn't grow the total.&lt;/strong&gt; It's already saturated at 32.&lt;/p&gt;

&lt;p&gt;The difference comes down to how much is served from the file server's read cache. SSD IOPS is&lt;br&gt;
used only when reading data in neither the in-memory nor the NVMe cache, per the&lt;br&gt;
&lt;a href="https://docs.aws.amazon.com/fsx/latest/ONTAPGuide/performance.html" rel="noopener noreferrer"&gt;performance specs&lt;/a&gt;. Overlap&lt;br&gt;
in read ranges pulls toward the former; no overlap pulls toward the latter. &lt;strong&gt;There isn't one&lt;br&gt;
ceiling — there are two, and which one a workload hits depends on the workload.&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  A hypothesis that turned out wrong
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Before this measurement, I had assumed the SVM's single data LIF was a roughly 4,400 MB/s&lt;br&gt;
ceiling. That was wrong.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Since client-reported totals aren't evidence on their own, I cross-checked against ONTAP's&lt;br&gt;
physical port cumulative counters (&lt;code&gt;nic_common&lt;/code&gt;'s &lt;code&gt;transmit_bytes&lt;/code&gt;), differenced. The&lt;br&gt;
port-measured value exceeded the client total by within 1.3% at every point, and all 8 points went&lt;br&gt;
through the same single port. &lt;strong&gt;That means a single LIF was carrying 12,173 MiB/s — 102 Gbps.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One thing tripped up this cross-check. &lt;strong&gt;The instantaneous-value counter returned 0 even while&lt;br&gt;
traffic was actually flowing.&lt;/strong&gt; Only the differenced cumulative value was usable.&lt;/p&gt;

&lt;p&gt;This changes how capacity planning should work. &lt;strong&gt;Rather than counting hosts, decide first whether&lt;br&gt;
each host's read range overlaps.&lt;/strong&gt; A non-overlapping design pulls toward the disk path's ceiling,&lt;br&gt;
so adding hosts doesn't grow the total — that's what the 32-connection and 128-connection rows&lt;br&gt;
above show.&lt;/p&gt;
&lt;h4&gt;
  
  
  A single-client figure that was actually the client's own limit
&lt;/h4&gt;

&lt;p&gt;A second, similarly shaped mistake, this time by me. In a separate round, on a first-generation&lt;br&gt;
2,048 MBps configuration, I got roughly 1,250 MB/s and &lt;strong&gt;treated it as "this configuration's read&lt;br&gt;
ceiling."&lt;/strong&gt; I checked with two clients (2026-09-19).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;First-generation 2,048 MBps, SSD 1,024 GiB, SSD IOPS 40,000 (&lt;code&gt;USER_PROVISIONED&lt;/code&gt;), NVMe read&lt;br&gt;
cache disabled, volume 800 GiB, client c5n.9xlarge x2, &lt;strong&gt;each reading a non-overlapping 300&lt;br&gt;
GiB&lt;/strong&gt;, 1 MiB sequential, 8 threads, &lt;code&gt;o_direct&lt;/code&gt;, ONTAP 9.18.1P6, ap-northeast-1.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Condition&lt;/th&gt;
&lt;th&gt;Client total&lt;/th&gt;
&lt;th&gt;Disk throughput utilization&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Single host only&lt;/td&gt;
&lt;td&gt;1,195.27 MB/s&lt;/td&gt;
&lt;td&gt;63% (SSD IOPS also 40%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Two hosts simultaneously&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;981.90 + 919.58 = 1,901.48 MB/s&lt;/strong&gt; (1.59x)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;102-103%&lt;/strong&gt; (burst)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;With a single host, the disk path was only at 63% — the file system side had headroom.&lt;/strong&gt; Only&lt;br&gt;
with two hosts did the disk side saturate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;From here I chased "so what's the per-host ceiling?" The answer was two of my own settings, not&lt;br&gt;
a resource.&lt;/strong&gt; On the same file system, I varied &lt;code&gt;rsize&lt;/code&gt; to 1 MiB and thread count (the next day).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Condition (1 host)&lt;/th&gt;
&lt;th&gt;Measured&lt;/th&gt;
&lt;th&gt;Response&lt;/th&gt;
&lt;th&gt;Disk throughput utilization&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Transfer 64 KiB, 8 threads&lt;/td&gt;
&lt;td&gt;1,195.27 MB/s&lt;/td&gt;
&lt;td&gt;6.69 ms&lt;/td&gt;
&lt;td&gt;63%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transfer 1 MiB, 8 threads&lt;/td&gt;
&lt;td&gt;1,446.66 MB/s&lt;/td&gt;
&lt;td&gt;5.443 ms&lt;/td&gt;
&lt;td&gt;77.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transfer 1 MiB, &lt;strong&gt;16 threads&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1,646.17 MB/s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;8.208 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;102.6%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transfer 1 MiB, 32 threads&lt;/td&gt;
&lt;td&gt;1,647.42 MB/s&lt;/td&gt;
&lt;td&gt;16.420 ms&lt;/td&gt;
&lt;td&gt;102.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transfer 1 MiB, 64 threads&lt;/td&gt;
&lt;td&gt;1,648.71 MB/s&lt;/td&gt;
&lt;td&gt;32.838 ms&lt;/td&gt;
&lt;td&gt;102.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Transfer size added +21%, concurrency added another +14%, and then it stopped.&lt;/strong&gt; Past 16 threads,&lt;br&gt;
only response time keeps doubling while throughput doesn't move. It's queueing, not gaining.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And a single host reaches the same 102.6% that two hosts reached.&lt;/strong&gt; IOPS sat at 50.9% and network&lt;br&gt;
at 66.3%, neither saturated. I could not name a specific client-side device that was saturated.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;This "can't name it" was traced to a different tier and resolved separately (2026-09-20).&lt;/strong&gt; On&lt;br&gt;
second-generation, 6,144 MBps / 200,000 IOPS, measuring &lt;strong&gt;five ways of splitting the working set&lt;/strong&gt;&lt;br&gt;
shows adding volumes moves it only &lt;strong&gt;+1.0%&lt;/strong&gt;, while &lt;strong&gt;adding SVMs moves it +26.3% and adding&lt;br&gt;
clients moves it +52.7%.&lt;/strong&gt; So what matters is &lt;strong&gt;the number of distinct IPs the client talks to&lt;br&gt;
and the client count&lt;/strong&gt;, not thread count (+8.9% going from 512 to 1,024). &lt;strong&gt;With two clients, the&lt;br&gt;
disk side reaches 96.6% of the provisioned value, and only then does the file system side become&lt;br&gt;
the actual limiter.&lt;/strong&gt; No single device can be named because the limiter isn't one counter — it's&lt;br&gt;
&lt;strong&gt;how receive processing is distributed&lt;/strong&gt;&lt;br&gt;
(&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/ja/verification/throughput-iops-concurrency.md#4-kib-%E3%83%A9%E3%83%B3%E3%83%80%E3%83%A0%E8%AA%AD%E3%81%BF%E3%81%AE%E5%A4%A9%E4%BA%95%E3%81%AE%E6%89%80%E5%9C%A82026-09-20" rel="noopener noreferrer"&gt;measurement&lt;/a&gt;)&lt;br&gt;
(Japanese).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This reinforces this article's claim. The table at the top says to confirm the type of ceiling;&lt;br&gt;
a single-host measurement can't determine that type. &lt;strong&gt;Using a value measured alone as a ceiling&lt;br&gt;
for capacity planning under-buys.&lt;/strong&gt; There are two ways to check — adding hosts and fixing transfer&lt;br&gt;
size/concurrency — and both arrive at the same conclusion.&lt;/p&gt;

&lt;p&gt;I also chased the discrepancy between them, without using any additional resources. Both file&lt;br&gt;
systems had already been deleted, but &lt;strong&gt;CloudWatch retains data for 63 days after deletion.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;This architecture doesn't publish a single burst-remaining metric&lt;/strong&gt; (zero out of 77 metrics
contain &lt;code&gt;Balance&lt;/code&gt;). My earlier explanation, "because burst remainders weren't aligned," doesn't
hold, because nothing to align exists.&lt;/li&gt;
&lt;li&gt;The denominator for utilization was the provisioned value itself (2,048 MB/s).
&lt;code&gt;DiskReadBytes / (utilization / 100)&lt;/code&gt; converges to 2,048 across every minute. &lt;strong&gt;On
first-generation, over 100% just means "more than the provisioned value came out," not evidence
of spending a burst allowance.&lt;/strong&gt; Second-generation's 1,536 config at 203.5% is against a
baseline, so the same metric has a different denominator by generation.&lt;/li&gt;
&lt;li&gt;And both sessions were hitting the same server-side ceiling of about 2,040 MB/s. &lt;strong&gt;The 15%
client-reported difference wasn't a difference in what the file system delivered.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As one discrepancy closed, a second one — between two counter systems — opened, and it closed&lt;br&gt;
the same day too. At saturation, the server reported about 2,043 MB/s while vdbench reported&lt;br&gt;
1,646-1,649 (19% short). The cause was how the measurement was taken. Aligning the window's&lt;br&gt;
start to a minute boundary and comparing only the minutes fully contained in the window, the&lt;br&gt;
two systems agree within 0.2% (300s window: 271.04 vs 271.57; 900s window: 326.70 vs 326.07).&lt;br&gt;
The 19% arose from taking a 60-second average of a minute where load was only partially applied&lt;br&gt;
as the server-side value. Against a steady-state 271 MB/s, the start minute was 216.85 and the end&lt;br&gt;
minute 58.47. &lt;strong&gt;There's nothing to restate about FSx for ONTAP here.&lt;/strong&gt; Making the two agree&lt;br&gt;
requires both aligning the window and discarding the partial minutes&lt;br&gt;
(&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/ja/verification/throughput-iops-concurrency.md#%E7%AA%93%E3%82%92%E5%88%86%E5%A2%83%E7%95%8C%E3%81%AB%E5%90%88%E3%82%8F%E3%81%9B%E3%81%9F-2-%E7%B3%BB%E7%B5%B1%E3%81%AE%E6%AF%94%E8%BC%832026-09-20" rel="noopener noreferrer"&gt;measurement&lt;/a&gt;)&lt;br&gt;
(Japanese).&lt;/p&gt;
&lt;h3&gt;
  
  
  The shared ceiling split two ways over SMB too
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The same shape showed up over SMB.&lt;/strong&gt; Varying only client count across 8 clients didn't produce&lt;br&gt;
one single answer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwgczwjvrkjttr0ncmvjc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwgczwjvrkjttr0ncmvjc.png" alt="The SMB host-count test's configuration" width="799" height="628"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Everything below the share is identical between the two.&lt;/strong&gt; Same single CIFS share, same SMB 3.1.1&lt;br&gt;
with 4 Multichannel channels, same physical port on the same node of the same SVM. Only what each&lt;br&gt;
host reads differs.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Total at 8 hosts&lt;/th&gt;
&lt;th&gt;Read range&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;10,721.19 MB/s (port-measured 11,194.7 MiB/s)&lt;/td&gt;
&lt;td&gt;Every host reads the same range of the same file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4,225.31 MB/s&lt;/td&gt;
&lt;td&gt;Each host reads its own 1/8 of the range&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The former was still climbing at 8 hosts; the latter plateaued between 4 and 8. The former's&lt;br&gt;
growth is the result of ONTAP's memory serving the overlap. &lt;strong&gt;Citing only one of them leads to the&lt;br&gt;
opposite conclusion.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Client-side totals aren't evidence on their own here either. A single read can satisfy multiple&lt;br&gt;
clients, so bytes never sent over the wire can get counted. Every point here is cross-checked&lt;br&gt;
against ONTAP's physical port cumulative counters.&lt;/p&gt;

&lt;p&gt;Per-point figures, and what this measurement doesn't answer (the 180-second window doesn't&lt;br&gt;
separate burst from baseline), are in the&lt;br&gt;
&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/ja/verification/perf-matrix-results.md#smb-%E3%81%AE%E5%8F%B0%E6%95%B0%E8%A9%A6%E9%A8%931--4--8-%E5%8F%B0" rel="noopener noreferrer"&gt;SMB host-count test&lt;/a&gt;&lt;br&gt;
(Japanese).&lt;/p&gt;
&lt;h3&gt;
  
  
  Amazon S3 — a total proportional to client count
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The figure's panel B is made of just this one bidirectional arrow.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hosts&lt;/th&gt;
&lt;th&gt;Write total&lt;/th&gt;
&lt;th&gt;Per host&lt;/th&gt;
&lt;th&gt;Read total&lt;/th&gt;
&lt;th&gt;Per host&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;581.5 MB/s&lt;/td&gt;
&lt;td&gt;581.5&lt;/td&gt;
&lt;td&gt;496.3 MB/s&lt;/td&gt;
&lt;td&gt;496.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1158.0 MB/s&lt;/td&gt;
&lt;td&gt;579.0&lt;/td&gt;
&lt;td&gt;977.5 MB/s&lt;/td&gt;
&lt;td&gt;488.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2362.5 MB/s&lt;/td&gt;
&lt;td&gt;590.6&lt;/td&gt;
&lt;td&gt;1974.7 MB/s&lt;/td&gt;
&lt;td&gt;493.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;3601.4 MB/s&lt;/td&gt;
&lt;td&gt;600.2&lt;/td&gt;
&lt;td&gt;3038.5 MB/s&lt;/td&gt;
&lt;td&gt;506.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4783.2 MB/s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;597.9&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3992.2 MB/s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;499.0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Going from 1 to 8 clients, write totals grew 8.23x and reads 8.04x. The per-host value doesn't&lt;br&gt;
drop.&lt;/strong&gt; The total reached 38.3 Gbps at 8 hosts, and this range never showed a point where it&lt;br&gt;
stopped being proportional to host count. Each host used a distinct prefix, so per-prefix request&lt;br&gt;
ceilings weren't tested.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The roughly 500 MB/s visible with a single host was not Amazon S3's ceiling — it was the&lt;br&gt;
client's.&lt;/strong&gt; Since adding hosts grows the total, capacity planning here comes down to counting&lt;br&gt;
client-side bandwidth.&lt;/p&gt;
&lt;h3&gt;
  
  
  Amazon S3 Files — the CPU of a proxy process on the client
&lt;/h3&gt;

&lt;p&gt;This path's structure differs from the other two, so the structure is described first. &lt;strong&gt;The&lt;br&gt;
figure's panel C shows the client and &lt;code&gt;efs-proxy&lt;/code&gt; sharing the same box — that's it.&lt;/strong&gt; Mounting S3&lt;br&gt;
Files starts a transfer process called &lt;code&gt;efs-proxy&lt;/code&gt; on the client, and the NFS client connects to&lt;br&gt;
that process. It's visible in the mount info.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;127.0.0.1:/ nfs4 rw,vers=4.2,rsize=1048576,wsize=1048576,proto=tcp,port=20812,addr=127.0.0.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The mount target is &lt;code&gt;127.0.0.1&lt;/code&gt; — itself.&lt;/strong&gt; Data is then carried from &lt;code&gt;efs-proxy&lt;/code&gt; to the service&lt;br&gt;
side. FSx for ONTAP's mount target is the file system itself, so this is structurally different.&lt;/p&gt;

&lt;p&gt;With that structure in mind, I measured what&lt;br&gt;
&lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-files-quotas.html" rel="noopener noreferrer"&gt;not supporting &lt;code&gt;nconnect&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
means in practice. &lt;code&gt;nconnect&lt;/code&gt; is an option that increases the number of TCP connections an NFS&lt;br&gt;
mount opens.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stream count&lt;/th&gt;
&lt;th&gt;Read&lt;/th&gt;
&lt;th&gt;Write&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;67.9 MB/s&lt;/td&gt;
&lt;td&gt;34.6 MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;264.3 MB/s&lt;/td&gt;
&lt;td&gt;140.5 MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;451.3 MB/s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;281.0 MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;404.8 / 450.0 MB/s&lt;/td&gt;
&lt;td&gt;531.8 MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Reads reach about 450 MB/s at 8 streams and don't grow further at 16&lt;/strong&gt; (writes are still growing&lt;br&gt;
at 16, 531.8 MB/s). What follows covers the read plateau. 450 MB/s is 3.6 Gbps, under the&lt;br&gt;
single-flow ceiling&lt;br&gt;
(&lt;a href="https://docs.aws.amazon.com/fsx/latest/ONTAPGuide/performance.html" rel="noopener noreferrer"&gt;roughly 5 Gbps per network flow&lt;/a&gt;).&lt;br&gt;
So the single-flow ceiling isn't what's binding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The plateau sits at &lt;code&gt;efs-proxy&lt;/code&gt;'s CPU.&lt;/strong&gt; While reading at 16 streams, CPU usage on the&lt;br&gt;
&lt;code&gt;efs-proxy&lt;/code&gt; process was 67.3% + 18.1% + 14.9% (an 8-vCPU host).&lt;/p&gt;

&lt;p&gt;This is where the meaning of not supporting &lt;code&gt;nconnect&lt;/code&gt; becomes clear. What &lt;code&gt;nconnect&lt;/code&gt; increases&lt;br&gt;
is the connection count of the NFS mount — between the NFS client and &lt;code&gt;efs-proxy&lt;/code&gt;. &lt;strong&gt;Since the&lt;br&gt;
plateau is &lt;code&gt;efs-proxy&lt;/code&gt;'s own CPU, even if it were supported, it wouldn't reach where the plateau&lt;br&gt;
actually is.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So it isn't "slow because &lt;code&gt;nconnect&lt;/code&gt; isn't supported." &lt;strong&gt;Since the bottleneck is a process's CPU on&lt;br&gt;
the client, the fix is a bigger client, not a mount option.&lt;/strong&gt; &lt;code&gt;efs-proxy --tls&lt;/code&gt; being on this path&lt;br&gt;
is the cost of a configuration where in-transit encryption is the default, not a defect.&lt;/p&gt;

&lt;p&gt;One practical note. Specifying &lt;code&gt;nconnect&lt;/code&gt; hung the mount (90-second timeout). Not a rejection,&lt;br&gt;
not silent ignoring. The first measurement was entirely lost to this. &lt;strong&gt;When trying an unsupported&lt;br&gt;
option, wrap it with &lt;code&gt;timeout&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Absence of decay over a 15-minute sustained write
&lt;/h2&gt;

&lt;p&gt;Writes had been measured in a 120-second window, so I re-measured at 900 seconds, on the&lt;br&gt;
second-generation environment.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Steady-state window average&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;2,063.00 MB/s&lt;/strong&gt; (900 seconds after a 60-second warmup)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10-second interval min / max&lt;/td&gt;
&lt;td&gt;1,749.0 / 2,128.4 MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cumulative written&lt;/td&gt;
&lt;td&gt;Roughly &lt;strong&gt;1.77 TiB&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decay&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Not observed. The first minute and the last minute sit in the same range&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Absorption somewhere can be ruled out.&lt;/strong&gt; In-memory cache is 256 GB and NVMe cache is disabled;&lt;br&gt;
the amount written is 7x that. Volume efficiency was &lt;code&gt;disabled&lt;/code&gt;, with 0 savings.&lt;/p&gt;

&lt;p&gt;Stating the relationship to the published value directly: the documentation says for&lt;br&gt;
second-generation, "reads get the full throughput capacity, writes get one-third of it," and&lt;br&gt;
it separately names 6,144 MBps as an exception to that general rule, listing it in a separate&lt;br&gt;
table with Single-AZ writes at 1,024 MBps. &lt;strong&gt;So the published value that should apply to a 6,144&lt;br&gt;
configuration is 1,024 MBps.&lt;/strong&gt; The measurement was 2.05x that.&lt;/p&gt;

&lt;p&gt;This single point reproduced on a separate file system later. Same 6,144 MBps provisioned&lt;br&gt;
value, 1,800 seconds (twice this measurement's window), 2,097 MB/s, a 0.3% difference between&lt;br&gt;
the first 300 seconds and the last 300. &lt;strong&gt;Different day, different file system, different window&lt;br&gt;
length: 2,063 and 2,097, a 1.6% difference.&lt;/strong&gt; The no-decay conclusion is supported by two&lt;br&gt;
observations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lowering the provisioned value and measuring again still exceeds it in the same direction.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provisioned&lt;/th&gt;
&lt;th&gt;Published value that should apply&lt;/th&gt;
&lt;th&gt;Measured (sustained, median)&lt;/th&gt;
&lt;th&gt;Against the published value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1,536&lt;/td&gt;
&lt;td&gt;One-third of the general rule -&amp;gt; &lt;strong&gt;512 MB/s&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1,333 MB/s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.60x&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6,144&lt;/td&gt;
&lt;td&gt;Exception table's &lt;strong&gt;1,024 MB/s&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;2,097 MB/s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.05x&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Both points exceed the published write ceiling.&lt;/strong&gt; Same direction, ratios of 2.60 and 2.05.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;This used to be described a different way.&lt;/strong&gt; It previously read "the general rule holds at&lt;br&gt;
6,144 and misses at 1,536," but &lt;strong&gt;6,144 / 3 = 2,048 being close to the measured 2,097 is a&lt;br&gt;
coincidence.&lt;/strong&gt; The documentation names 6,144 as an exception to the general rule, so &lt;strong&gt;applying&lt;br&gt;
the general rule there doesn't hold in the first place.&lt;/strong&gt; Corrected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which figure refers to what still isn't determined.&lt;/strong&gt; The table header reads "Maximum&lt;br&gt;
throughput &lt;strong&gt;from SSD storage&lt;/strong&gt;," which may refer to a ceiling scoped to the SSD path only.&lt;br&gt;
&lt;strong&gt;But this measurement closed off the likely absorption points&lt;/strong&gt; — the cumulative 1.77 TiB is&lt;br&gt;
7x the 256 GB in-memory cache, the NVMe cache was confirmed disabled on both nodes, efficiency&lt;br&gt;
was &lt;code&gt;disabled&lt;/code&gt; with 0 savings. &lt;strong&gt;Why it still comes out roughly 2x can't be explained from this&lt;br&gt;
side.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The design-level effect is over-, not under-provisioning. Working backward from the general&lt;br&gt;
rule to hit a write of 1,333 MB/s looks like it needs a 4,000 MBps-class provisioned value, but&lt;br&gt;
1,536 was measured to be enough. The second-generation provisioned rate is $2.013/MBps-month&lt;br&gt;
(ap-northeast-1, fetched 2026-09-04), so &lt;strong&gt;this misreading translates directly into overbuying.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Only two points were measured: Single-AZ, 1 HA pair.&lt;/strong&gt; Multi-AZ, 2+ pairs, and provisioned values&lt;br&gt;
other than 1,536 and 6,144 weren't measured&lt;br&gt;
(&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/ja/verification/throughput-capacity-burst-and-baseline.md" rel="noopener noreferrer"&gt;measurement record&lt;/a&gt;)&lt;br&gt;
(Japanese).&lt;/p&gt;

&lt;h3&gt;
  
  
  Capacity growth from continued writing
&lt;/h3&gt;

&lt;p&gt;Trying to continue into a read-side measurement after this, &lt;strong&gt;it didn't complete. The cause is a&lt;br&gt;
documented specification — a gap in my own design, not a documentation error.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before starting, snapshot count was 0 and the snapshot policy was &lt;code&gt;none&lt;/code&gt;. 30 minutes into the run, a&lt;br&gt;
snapshot named starting with &lt;code&gt;backup-&lt;/code&gt; appeared, holding 739 GiB, and the write rate dropped from&lt;br&gt;
2,200 MB/s to 267 MB/s. Volume usage went from 88% to 100%, with 9.9 GiB free. &lt;strong&gt;Deleting files&lt;br&gt;
with &lt;code&gt;rm&lt;/code&gt; didn't return the free space.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The cause is the automatic daily backup.&lt;br&gt;
&lt;a href="https://docs.aws.amazon.com/fsx/latest/ONTAPGuide/using-backups.html" rel="noopener noreferrer"&gt;Volume backups&lt;/a&gt; state that a&lt;br&gt;
backup first takes a volume snapshot, and &lt;strong&gt;that snapshot sits inside the volume, consuming&lt;br&gt;
capacity, and stays until the next backup.&lt;/strong&gt; For an overwrite-heavy workload, the pre-overwrite&lt;br&gt;
blocks are retained, so the amount written becomes capacity as-is.&lt;/p&gt;

&lt;p&gt;Why it doesn't return on deletion is&lt;br&gt;
&lt;a href="https://repost.aws/knowledge-center/fsx-ontap-correct-snapshot-spill" rel="noopener noreferrer"&gt;also documented&lt;/a&gt;.&lt;br&gt;
&lt;strong&gt;Data deleted from the active file system is not released as long as a snapshot still references&lt;br&gt;
it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Checking for snapshots before starting doesn't prevent this. Before the run it reads normally,&lt;br&gt;
because no snapshot exists yet. There was a recovery mechanism, though.&lt;br&gt;
&lt;a href="https://kb.netapp.com/on-prem/ontap/Ontap_OS/OS-KBs/What_is_volume_autosize_in_Data_ONTAP" rel="noopener noreferrer"&gt;It's documented&lt;/a&gt;&lt;br&gt;
that &lt;code&gt;volume autosize&lt;/code&gt; and Snapshot autodelete are designed to work together. Neither was active&lt;br&gt;
by default in this setup. The actual situation was that the default was in effect because&lt;br&gt;
neither was specified in the template. &lt;strong&gt;"Not specified" isn't "disabled."&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  15 minutes over SMB
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;No decay over SMB either.&lt;/strong&gt; The 900-second steady-state window averaged 1,488.03 MB/s, with a&lt;br&gt;
10-second-interval min of 1,452.70 and max of 1,514.40; the first-third average was 1,487.55&lt;br&gt;
against a last-third of 1,486.57.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Changing the client from an 8-vCPU type to a 36-vCPU type only moved it 0.6%&lt;/strong&gt; (1,478.95 versus&lt;br&gt;
1,488.03). This value isn't determined on the client side.&lt;/p&gt;

&lt;p&gt;The initial observation that a 300-second measurement (1,698.42 MB/s) was 12.4% higher than the&lt;br&gt;
900-second one doesn't reproduce (re-measured 2026-09-20). Varying only window length at 6,144&lt;br&gt;
MBps, the longer window actually came out 4.0% higher (1,375.4 versus 1,430.3 MB/s). Channel count&lt;br&gt;
stayed constant regardless of measurement duration, and even writes moved only 1.9% across window&lt;br&gt;
lengths. &lt;strong&gt;The original 12.4% gap looks like a one-off value, not a property of window length.&lt;/strong&gt;&lt;br&gt;
That said, the longer window's vdbench format was interrupted partway through, so it wasn't a&lt;br&gt;
comparable pair&lt;br&gt;
(&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/ja/verification/throughput-iops-concurrency.md#smb-%E3%81%AE%E7%AA%93%E9%95%B7-124-%E3%81%AE%E5%86%8D%E7%8F%BE%E4%B8%8D%E6%88%90%E7%AB%8B2026-09-20" rel="noopener noreferrer"&gt;re-measurement record&lt;/a&gt;)&lt;br&gt;
(Japanese).&lt;/p&gt;

&lt;p&gt;Against the same physical port on the same node of the same file system, NFS measured 2,063.00&lt;br&gt;
MB/s and SMB 1,488.03 MB/s. Since the source of concurrency differs (16 connections specified by&lt;br&gt;
the client versus 4 negotiated channels), this can't be attributed to a difference in the&lt;br&gt;
protocols themselves.&lt;/p&gt;

&lt;p&gt;Conditions and the 10-second-interval progression are in&lt;br&gt;
&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/ja/verification/perf-matrix-results.md#smb-%E3%81%AE%E6%9B%B8%E3%81%8D%E8%BE%BC%E3%81%BF%E3%81%AE%E6%8C%81%E7%B6%9A15-%E5%88%86" rel="noopener noreferrer"&gt;the SMB sustained write&lt;/a&gt;&lt;br&gt;
(Japanese).&lt;/p&gt;

&lt;h2&gt;
  
  
  Additional read capacity via FlexCache
&lt;/h2&gt;

&lt;p&gt;One more thing was measured on the read side. This section covers the figure's panel A, where&lt;br&gt;
Origin and Cache sit as separate file systems. &lt;strong&gt;FlexCache isn't a mechanism for sharing the&lt;br&gt;
origin's (the replication source volume's) performance — it's a mechanism that adds separate&lt;br&gt;
capacity on the read side.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Measurement point&lt;/th&gt;
&lt;th&gt;Measured, uncompressible&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;FlexCache first read (cache not yet filled; read while filling)&lt;/td&gt;
&lt;td&gt;306.8 MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FlexCache resident (second read onward; cache already filled)&lt;/td&gt;
&lt;td&gt;882.7 / 907.1 MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Direct origin read (control — the comparison baseline; already read once and cached)&lt;/td&gt;
&lt;td&gt;383.3 MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Resident is 2.31x a direct origin read.&lt;/strong&gt; The cache is a separate file system from the origin,&lt;br&gt;
with its own throughput capacity, its own memory, and its own burst allowance. So while reading&lt;br&gt;
from the cache, the origin's own ceiling doesn't apply.&lt;/p&gt;

&lt;p&gt;The cost is the first read. &lt;strong&gt;Data that's never been read comes in at 306.8 the first time — 1/2.88&lt;br&gt;
of resident.&lt;/strong&gt; That's the figure to design around.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;One qualification.&lt;/strong&gt; This was measured with both origin and cache at 128 MBps. Raising the&lt;br&gt;
origin to 2048 MBps puts a direct origin read into the 2000 MB/s class, so &lt;strong&gt;depending on&lt;br&gt;
configuration, the 2.31x could reverse. Unmeasured.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Bottleneck shift with small objects
&lt;/h2&gt;

&lt;p&gt;Raising throughput capacity 16x and SSD IOPS 13x didn't raise request rate. The six measured&lt;br&gt;
points were 4 KiB reads and writes at concurrency 64 and 256, and 64 KiB reads and writes at&lt;br&gt;
concurrency 64. &lt;strong&gt;None of the six grew, and writes dropped 25%.&lt;/strong&gt; Against 40,000 IOPS, 279-605&lt;br&gt;
req/s — 0.7-1.5% of provisioned IOPS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The bottleneck sits on the S3 Access Point's request-handling side.&lt;/strong&gt; For designs that handle a&lt;br&gt;
large volume of small objects, raising throughput capacity and SSD IOPS doesn't help.&lt;/p&gt;

&lt;h2&gt;
  
  
  Per-tool visibility
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What's available to check "what is this actually limited by" while measuring differs by path.&lt;/strong&gt;&lt;br&gt;
Not knowing this means missing the numbers needed to identify a cause.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you want to see&lt;/th&gt;
&lt;th&gt;Reaches it&lt;/th&gt;
&lt;th&gt;Doesn't reach it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Per-volume throughput&lt;/td&gt;
&lt;td&gt;ONTAP's volume counters&lt;/td&gt;
&lt;td&gt;Client-side measurement (turns into a single-TCP-flow ceiling)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node CPU&lt;/td&gt;
&lt;td&gt;CloudWatch (&lt;code&gt;AWS/FSx&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;fsxadmin&lt;/code&gt;'s REST doesn't reach it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fraction that went to disk&lt;/td&gt;
&lt;td&gt;CloudWatch's &lt;code&gt;DiskReadBytes&lt;/code&gt; / &lt;code&gt;DataReadBytes&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Can't be determined from the client side&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S3 Files propagation lag&lt;/td&gt;
&lt;td&gt;CloudWatch (&lt;code&gt;AWS/S3/Files&lt;/code&gt;'s &lt;code&gt;ExportAge&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Measuring from the client side turns into roughly 590 MB/s. Linux's NFS mount opens only one&lt;br&gt;
TCP connection per server by default, and that's what limits it first. &lt;strong&gt;If you want to measure&lt;br&gt;
the storage-side ceiling, read the storage-side counters.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;CPU on both paths was around 22%. &lt;strong&gt;The NFS path delivers twice this architecture's throughput at&lt;br&gt;
the same CPU level.&lt;/strong&gt; So this architecture's cost isn't inside ONTAP — it's upstream, on the S3&lt;br&gt;
translation side.&lt;/p&gt;

&lt;p&gt;And on reads, &lt;code&gt;DiskReadBytes&lt;/code&gt; / &lt;code&gt;DataReadBytes&lt;/code&gt; was 1.4 / 0.9 / 1.5%. &lt;strong&gt;98.5-99.9% of read bytes&lt;br&gt;
didn't go to disk.&lt;/strong&gt; That means it came back from cache, which links directly to Part 5's fifth&lt;br&gt;
default.&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS documentation notes and support inquiries
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;This article touches three points in the published specs. Two were filed on 2026-09-17.&lt;/strong&gt; The&lt;br&gt;
source for all three is&lt;br&gt;
&lt;a href="https://docs.aws.amazon.com/fsx/latest/ONTAPGuide/performance.html" rel="noopener noreferrer"&gt;the performance specs&lt;/a&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Content&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Second-generation writes exceed the published ceiling at both points measured (2.60x the general rule's 512 at a 1,536 provisioned value; 2.05x the exception table's 1,024 at a 6,144 provisioned value)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Gap between published value and measurement.&lt;/strong&gt; Likely absorption points (two-tier cache, efficiency) closed off&lt;/td&gt;
&lt;td&gt;Filed (2026-09-17). &lt;strong&gt;AWS's reply (2026-09-24): the "up to a third" figure is not a hard limit but a sizing indicator.&lt;/strong&gt; A write uses bandwidth twice over — once to the active server, once to sync the standby server — but network burst capacity can push measured throughput above a third of the provisioned value, and AWS considers that expected behavior, not an anomaly. The table heading's "Maximum" is described as the ceiling to expect once burst is not a factor and the same file system is shared with other workloads. For sizing, AWS pointed back to the original rule of thumb: provision three times the required write throughput (e.g., a 1,333 MB/s requirement calls for a 6,144 MBps configuration)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ap-northeast-1's NVMe read cache capacity is missing from the table (the four regions with a column have it listed). The management page is also written scoped to "second-generation" only, so a first-generation user with a qualifying config can't tell they have a cache&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Missing content.&lt;/strong&gt; Presence was confirmable via ONTAP; capacity was not&lt;/td&gt;
&lt;td&gt;Filed (2026-09-17). &lt;strong&gt;AWS's reply (2026-09-21): no documentation change.&lt;/strong&gt; AWS's position is that a first-generation Single-AZ configuration at 2,048 MBps lacking a cache is not something the documentation promises against, and that enumerating every such corner case isn't practical, so the existing wording stands. Pointed instead to &lt;code&gt;system node external-cache show&lt;/code&gt; for checking presence and capacity per file system. &lt;strong&gt;Both the capacity-publication ask and the management-page scope ask were effectively declined&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The observation that raising SSD IOPS made reads 7.14x faster didn't square with "SSD IOPS is used only for reads not served from cache"&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;My own measurement gap.&lt;/strong&gt; Hadn't read a volume that reliably exceeded the sum of both cache tiers&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Not filed.&lt;/strong&gt; Re-measuring 2026-09-17 with the working set at 2.15x the cache size squared with the documentation (ratio came out 4.86x; 7.14x doesn't reproduce). &lt;strong&gt;Not a documentation error&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Why the third isn't filed. The mismatch's cause was on my side. Re-measuring settled it&lt;br&gt;
(2026-09-17). With the working set at 512 GiB — 2.15x the in-memory cache — and the NVMe cache&lt;br&gt;
disabled, 96-126% of read bytes came from disk, with the low end saturating at&lt;br&gt;
&lt;code&gt;DiskIopsUtilization&lt;/code&gt; 112%. &lt;strong&gt;This matches the documentation. Not a documentation error, so not&lt;br&gt;
filed.&lt;/strong&gt; Only the ratio changed — 4.86x, and 7.14x doesn't reproduce. And what's capping the&lt;br&gt;
high side, roughly 1,250 MB/s, remains unresolved — that isn't a finding against the published&lt;br&gt;
spec, it's a range I haven't measured.&lt;/p&gt;

&lt;p&gt;The first and second were filed. &lt;strong&gt;Both were filed without me deciding "the correct value is&lt;br&gt;
this"&lt;/strong&gt; — as a gap or omission for whoever holds the values to judge. The first was framed as the&lt;br&gt;
question "what does 'Maximum throughput from SSD storage (write)' actually bound?" and the second&lt;br&gt;
as two points: publishing the capacity, and the management page's stated scope.&lt;/p&gt;

&lt;p&gt;The first used to be described a different way in this article. It previously read "which of&lt;br&gt;
the general rule and the exception table applies flips depending on the value," but &lt;strong&gt;the&lt;br&gt;
documentation names 6,144 as an exception to the general rule&lt;/strong&gt;, so applying the general rule&lt;br&gt;
there doesn't hold in the first place. Corrected before filing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Both first replies came in on 2026-09-21.&lt;/strong&gt; The first stopped at confirming AWS's own measurement&lt;br&gt;
clears a third of the provisioned value; the second landed on a decision not to change the&lt;br&gt;
documentation. The underlying fact — a first-generation Single-AZ configuration at 2,048 MBps can&lt;br&gt;
lack a cache — is something the documentation genuinely never promised against, and not&lt;br&gt;
enumerating every corner case is a reasonable line to draw. &lt;strong&gt;What didn't go through, though, is&lt;br&gt;
separate from that: asking for the capacity to be published, and asking the management page's&lt;br&gt;
stated scope to cover first generation too, are both requests that a documented gap doesn't&lt;br&gt;
answer by itself.&lt;/strong&gt; Recording that they didn't land. The per-file-system way to check&lt;br&gt;
(&lt;code&gt;system node external-cache show&lt;/code&gt;) is documented, so &lt;strong&gt;checking it yourself is unaffected.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The first one got a follow-up on 2026-09-24.&lt;/strong&gt; The "up to a third" figure turned out to be a&lt;br&gt;
sizing indicator rather than a hard limit — a write spends bandwidth twice over (once to the&lt;br&gt;
active server, once to sync the standby), and network burst capacity can push it above a third of&lt;br&gt;
the provisioned value, which AWS considers expected rather than anomalous. The table heading's&lt;br&gt;
"Maximum" is described as the ceiling to expect once burst isn't a factor and the file system is&lt;br&gt;
shared with other workloads. For sizing, AWS pointed back to the original rule of thumb: provision&lt;br&gt;
three times the required write throughput. &lt;strong&gt;This explanation doesn't dispute that the&lt;br&gt;
measurement exceeded the published value — it only says how to read that published value.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The block protocol side's findings and filing status can be traced from the&lt;br&gt;
&lt;a href="https://dev.to/aws-builders/why-amazon-fsx-for-netapp-ontap-block-performance-differed-by-about-2x-on-the-same-specified-35pi"&gt;separate article&lt;/a&gt;.&lt;/strong&gt;&lt;br&gt;
Submission status across the three block-protocol articles and this article is collected in&lt;br&gt;
&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/en/reference/block-protocol-aws-feedback-status.md" rel="noopener noreferrer"&gt;AWS documentation notes and inquiries across the block-protocol articles&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What hasn't been measured yet
&lt;/h2&gt;

&lt;p&gt;Meaning not yet measured, not "can't be."&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reading more than 2x the cache size in one pass. At 512 GiB (2.15x the cache), 4.86x
(256.47 -&amp;gt; 1,246.67 MB/s). The low side saturating on SSD IOPS is confirmed. &lt;strong&gt;What was capping
the high side, around 1,250 MB/s, turned out to be transfer size and concurrency, not a
resource&lt;/strong&gt; (details under
a single-client figure that was actually the client's own limit
above)&lt;/li&gt;
&lt;li&gt;Effect of measurement duration. Measurement duration itself is not the cause. &lt;strong&gt;What carries
the gap is provisioned IOPS, payload compressibility, and unit-to-unit variance&lt;/strong&gt;
(&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/ja/verification/throughput-iops-concurrency.md#%E6%B8%AC%E5%AE%9A%E6%99%82%E9%96%93%E3%81%A0%E3%81%91%E3%82%92%E6%8C%AF%E3%81%A3%E3%81%9F%E7%B5%90%E6%9E%9C--16-%E3%81%AE%E4%B8%8D%E5%9C%A82026-09-20" rel="noopener noreferrer"&gt;measurement&lt;/a&gt;)
(Japanese)&lt;/li&gt;
&lt;li&gt;Measuring FlexCache uncompressed on a 2048 MBps configuration. &lt;strong&gt;The "2.31x" could reverse
depending on configuration&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;What's capping the roughly 300 MB/s on the 128 MBps configuration. Reads on the same
configuration converge to 297-317 MB/s for both warm and cold (values from the same measurement
set as this article, not shown in the body table). This used to be described as "consistent
with plateauing on a burst," but reading burst remainders ruled that out. &lt;strong&gt;The remainder
plateaued at 99 -&amp;gt; 95% and returned to 99% after the load stopped — not exhausted.&lt;/strong&gt; Against a
disk burst ceiling of 600 MBps, the measured 254 MB/s is 42%; against a network burst ceiling of
1,250 MBps, roughly 285 MB/s is 23% — neither ceiling was reached. It isn't the roughly 590
MB/s single-flow ceiling either. Where it sits is unresolved
(&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/ja/verification-status.md" rel="noopener noreferrer"&gt;measurement&lt;/a&gt;)
(Japanese). Note that &lt;code&gt;DiskReadBytes&lt;/code&gt; / &lt;code&gt;DataReadBytes&lt;/code&gt; in the same window was 87-97%, so
reads on this configuration are coming from disk — a contrast with the 2048 MBps
configuration's 1.4%, consistent with an in-memory cache of only 16 GB&lt;/li&gt;
&lt;li&gt;The host count at which Amazon S3's total stops being proportional to host count. It's still
proportional at 8 hosts / 38.3 Gbps&lt;/li&gt;
&lt;li&gt;Adding S3 Files proxies. A configuration with separate mounts running more &lt;code&gt;efs-proxy&lt;/code&gt;
instances hasn't been tested&lt;/li&gt;
&lt;li&gt;More than 8 hosts. The side sharing the same file dropped to a 1.12x increment at 8 hosts, so
where it stops hasn't been observed&lt;/li&gt;
&lt;li&gt;A point below 32 connections on non-overlapping ranges. It was already saturated at 32, so
the connection count where saturation begins hasn't been bracketed&lt;/li&gt;
&lt;li&gt;A sustained write longer than 900 seconds. No decay over 15 minutes, but nothing beyond that
has been checked&lt;/li&gt;
&lt;li&gt;Reads with the fraction served from cache varied. Capacity filled up and the run was
aborted, so none of the 7 planned points were captured&lt;/li&gt;
&lt;li&gt;Time-of-day effects. Two different days were checked, but not different times of day&lt;/li&gt;
&lt;li&gt;SMB's 7 scenarios under cache control. Stopped by fill, not capacity. &lt;code&gt;format=yes&lt;/code&gt; ignores
the run block's &lt;code&gt;xfersize&lt;/code&gt; and &lt;code&gt;threads&lt;/code&gt;, writing at 128 KiB and queue depth 2, so overwriting
the same file that otherwise reaches 1,488 MB/s only produced 165 MB/s (315 MB/s with
&lt;code&gt;formatxfersize=1m&lt;/code&gt;; queue depth stayed at 2). &lt;strong&gt;The parameter file points 8 SDs at a single
file; splitting each range into its own file looks likely to solve both the speed and the design
problem at once. This is a prediction, not a result.&lt;/strong&gt; Background in
&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/ja/verification/perf-matrix-results.md#smb-%E3%81%AE%E3%82%AD%E3%83%A3%E3%83%83%E3%82%B7%E3%83%A5%E5%88%B6%E5%BE%A1%E4%B8%8B%E3%81%A7%E6%AD%A2%E3%81%BE%E3%81%A3%E3%81%9F-2-%E3%81%8B%E6%89%80" rel="noopener noreferrer"&gt;two places stuck under cache control&lt;/a&gt;
(Japanese)&lt;/li&gt;
&lt;li&gt;Why SMB's 300-second and 900-second values differ. Doesn't reproduce. Re-measuring at
6,144 MBps removes the window-length difference; the longer window's vdbench run had also been
interrupted, so it wasn't a comparable pair in the first place
(&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/ja/verification/throughput-iops-concurrency.md#smb-%E3%81%AE%E7%AA%93%E9%95%B7-124-%E3%81%AE%E5%86%8D%E7%8F%BE%E4%B8%8D%E6%88%90%E7%AB%8B2026-09-20" rel="noopener noreferrer"&gt;measurement&lt;/a&gt;)
(Japanese)&lt;/li&gt;
&lt;li&gt;Conditions for raising Multichannel's channel count above 4. Setting
&lt;code&gt;max_connections_per_session&lt;/code&gt; to 32 still produced 4. &lt;strong&gt;The 4 comes from the client side's
&lt;code&gt;ConnectionCountPerRssNetworkInterface&lt;/code&gt;&lt;/strong&gt;, which determines channel count per RSS-capable NIC.
The server side's 32 is a per-session ceiling, not the limiter on the increasing side. Adding
NICs or raising this value hasn't been tested&lt;/li&gt;
&lt;li&gt;The discrepancy between server-side counters and the client-side measurement tool. The cause
was measurement method — aligning the window's start to a minute boundary and comparing only
the minutes fully contained in the window, &lt;strong&gt;the two systems agree within 0.2%&lt;/strong&gt;
(&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/ja/verification/throughput-iops-concurrency.md#%E7%AA%93%E3%82%92%E5%88%86%E5%A2%83%E7%95%8C%E3%81%AB%E5%90%88%E3%82%8F%E3%81%9B%E3%81%9F-2-%E7%B3%BB%E7%B5%B1%E3%81%AE%E6%AF%94%E8%BC%832026-09-20" rel="noopener noreferrer"&gt;measurement&lt;/a&gt;)
(Japanese)&lt;/li&gt;
&lt;li&gt;What the 4 KiB random read ceiling actually is. It's on the client side. What matters is the
number of distinct IPs the client talks to and the client count, not thread count.
&lt;strong&gt;When citing this figure, write "per client"&lt;/strong&gt; — it isn't the file system's ceiling
(&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/ja/verification/throughput-iops-concurrency.md#4-kib-%E3%83%A9%E3%83%B3%E3%83%80%E3%83%A0%E8%AA%AD%E3%81%BF%E3%81%AE%E5%A4%A9%E4%BA%95%E3%81%AE%E6%89%80%E5%9C%A82026-09-20" rel="noopener noreferrer"&gt;measurement&lt;/a&gt;)
(Japanese)&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Why 4 KiB random reads plateau at 47.5% of provisioned SSD IOPS. The ratio doesn't reproduce.&lt;br&gt;
Smaller configurations are disk-IOPS-limited; larger ones follow a different formula.&lt;br&gt;
&lt;strong&gt;So "47.5% of provisioned" is a value specific to that one configuration, not an&lt;br&gt;
invariant with scale.&lt;/strong&gt; When citing it, always state the 6,144 MBps / 200,000 IOPS condition&lt;br&gt;
alongside it&lt;br&gt;
(&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/ja/verification/perf-matrix-results.md#%E6%8C%87%E5%AE%9A-iops-%E3%82%92-14-%E3%81%AB%E3%81%97%E3%81%9F%E3%81%A8%E3%81%8D%E3%81%AE%E6%AF%94%E3%81%AE%E4%B8%8D%E6%88%90%E7%AB%8B2026-09-20" rel="noopener noreferrer"&gt;measurement&lt;/a&gt;)&lt;br&gt;
(Japanese)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Metadata-operation performance under a high file count (creating, enumerating, and stat-ing a&lt;br&gt;
large number of files). What this article measured is throughput, IOPS, and concurrency, not&lt;br&gt;
metadata operations when the file count is large. NetApp's High File Count NAS Workloads&lt;br&gt;
Technical Report suggests that inode capacity and a single directory's maximum size bear on&lt;br&gt;
enumeration cost, but there's no figure measured on this architecture (S3 API collect -&amp;gt; origin&lt;br&gt;
-&amp;gt; FlexCache distribution). Measuring it would need an origin volume holding a sufficient file&lt;br&gt;
count and a measurement setup for metadata operations over it&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The time taken to enumerate a directory holding a large number of files (&lt;code&gt;ListObjectsV2&lt;/code&gt;, or an&lt;br&gt;
NFS / SMB directory walk). The enumeration cost when files concentrate in a single directory&lt;br&gt;
is something this project hasn't measured on this architecture&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;FlexCache's metadata-replication cost under a high file count. How the cost of replicating&lt;br&gt;
metadata from origin to cache scales with file count is something this project hasn't measured&lt;br&gt;
on this architecture. It's a different axis from this article's throughput measurements&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;Measuring three paths from the same host, the bottleneck sat in three different places for all&lt;br&gt;
three. A throughput capacity provisioned on the file system, client-side network bandwidth, and&lt;br&gt;
the CPU of a process on the client. &lt;strong&gt;This difference doesn't close as you add more clients.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And on the FSx for ONTAP side, there wasn't one ceiling.&lt;/strong&gt; With the same file system and the same&lt;br&gt;
connection count, the total swings 5.5x depending only on whether each EC2 client's read range overlaps.&lt;/p&gt;

&lt;p&gt;Which one to choose comes down to where you place the read side, which side holds the primary&lt;br&gt;
copy, and when you want a write visible on the bucket side. Throughput numbers are one input into&lt;br&gt;
that decision, and they come later in the order of things to check.&lt;/p&gt;

&lt;p&gt;What's worth taking away is a procedure: &lt;strong&gt;check the type of ceiling before comparing&lt;br&gt;
throughput.&lt;/strong&gt; Whether it's a provisioned value, client-side bandwidth, or a process's CPU on the&lt;br&gt;
client. Line up numbers of different types in the same column, and you get a table shaped like a&lt;br&gt;
comparison without being one.&lt;/p&gt;

&lt;p&gt;And a value measured with a single EC2 client can't determine that type. On the same file system,&lt;br&gt;
adding a second EC2 client grew the total 1.59x, and fixing transfer size and concurrency while&lt;br&gt;
staying at one client grew it 1.38x. Either way arrives at the same ceiling. &lt;strong&gt;There are two ways&lt;br&gt;
to check the type, and neither costs much extra.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A measurement that reproduces within 0.02-0.14% under identical conditions can still swing 30%&lt;br&gt;
from a single misunderstanding about conditions. "Stable" isn't "correct." And this article's&lt;br&gt;
figures are for one specific throughput capacity and one specific payload. &lt;strong&gt;They don't transfer&lt;br&gt;
directly as your environment's numbers. What transfers is the measurement approach.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Hope this helps anyone running the same kind of measurement.&lt;/p&gt;

&lt;p&gt;Defaults to check before measuring and items to always record are collected in&lt;br&gt;
&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/en/reference/performance-testing-guide.md" rel="noopener noreferrer"&gt;Performance testing considerations&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Until next time.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>performance</category>
      <category>infrastructure</category>
    </item>
    <item>
      <title>Amazon FSx for NetApp ONTAP NVMe/TCP assumes RHEL 9.3: why multipathing fails on Amazon Linux 2023</title>
      <dc:creator>Yoshiki Fujiwara(藤原 善基)@AWS Community Builder</dc:creator>
      <pubDate>Wed, 30 Sep 2026 05:42:42 +0000</pubDate>
      <link>https://dev.to/aws-builders/amazon-fsx-for-netapp-ontap-nvmetcp-assumes-rhel-93-why-multipathing-fails-on-amazon-linux-2023-1afl</link>
      <guid>https://dev.to/aws-builders/amazon-fsx-for-netapp-ontap-nvmetcp-assumes-rhel-93-why-multipathing-fails-on-amazon-linux-2023-1afl</guid>
      <description>&lt;h1&gt;
  
  
  Amazon FSx for NetApp ONTAP's NVMe/TCP procedure assumes RHEL 9.3: why multipathing is unavailable on Amazon Linux 2023, and how to check before you are billed
&lt;/h1&gt;

&lt;p&gt;Followed the AWS procedure and run &lt;code&gt;cat /sys/module/nvme_core/parameters/multipath&lt;/code&gt; only to find&lt;br&gt;
the file isn't there?&lt;/p&gt;

&lt;p&gt;Moving your target Linux distribution to Amazon Linux 2023 can stop a documented command from&lt;br&gt;
working exactly as written. You are reading the procedure correctly, yet your environment alone&lt;br&gt;
gives a different result — and tracking the cause down to a kernel build setting takes time.&lt;br&gt;
This article is for anyone who got stuck at that point.&lt;/p&gt;

&lt;p&gt;What this article does and does not cover: it covers how to confirm multipathing is unusable on&lt;br&gt;
AL2023, and how to count whether it is actually using the second path. It does not cover&lt;br&gt;
performance comparisons or a generalized conclusion for distributions other than AL2023 (details&lt;br&gt;
under "What this article does not cover" below). All figures are from a Single-AZ configuration;&lt;br&gt;
Multi-AZ is unverified.&lt;/p&gt;

&lt;p&gt;dev.to series: &lt;code&gt;FSx for ONTAP Block Protocols, Measured&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  What was checked
&lt;/h2&gt;

&lt;p&gt;Assuming no prior knowledge of FSx for ONTAP, here's what this article is confirming.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Location&lt;/strong&gt;: a single AWS VPC, single AZ&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client&lt;/strong&gt;: one EC2 instance (Amazon Linux 2023)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Target&lt;/strong&gt;: one Amazon FSx for NetApp ONTAP file system (second-generation SINGLE_AZ_2). The HA
pair's two controllers hold two paths to one namespace

&lt;ul&gt;
&lt;li&gt;Path 1: to the &lt;code&gt;optimized&lt;/code&gt; controller (shortest path)&lt;/li&gt;
&lt;li&gt;Path 2: to the &lt;code&gt;non-optimized&lt;/code&gt; controller. Only when multipathing is available can the host
tell that path 2 is a standby&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FYoshiki0705%2Fs3-burst-on-ontap-files%2Fb6b83cd3d6bc516f12478138cc36bb30a3ae0ec3%2Fdocs%2F_assets%2Fimages%2Fs3burst-block-b-multipath-en%402x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FYoshiki0705%2Fs3-burst-on-ontap-files%2Fb6b83cd3d6bc516f12478138cc36bb30a3ae0ec3%2Fdocs%2F_assets%2Fimages%2Fs3burst-block-b-multipath-en%402x.png" alt="One EC2 client connects to an FSx for ONTAP HA pair's two controllers, over Path 1 (optimized,&lt;br&gt;
the shortest route) and Path 2 (non-optimized, which looks like a normal route without ANA), both&lt;br&gt;
reaching the same one namespace" width="800" height="682"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure: an FSx for ONTAP HA pair has two paths to one namespace, and **Asymmetric Namespace&lt;br&gt;
Access is the mechanism that tells the host which one is the shortest path&lt;/em&gt;* (referred to as&lt;br&gt;
multipathing throughout this article). Without this mechanism in the kernel, those two paths&lt;br&gt;
don't appear as "one shortest path plus one standby": they appear as two separate devices&lt;br&gt;
pointing at the same data.*&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Role in this article&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;VPC / AZ&lt;/td&gt;
&lt;td&gt;Single AZ, so both paths to the HA pair's controllers sit on the same network&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EC2 client&lt;/td&gt;
&lt;td&gt;Checks for multipathing and the kernel configuration behind it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;optimized path&lt;/td&gt;
&lt;td&gt;The shortest path as determined by multipathing. Real data is expected to flow here&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;non-optimized path&lt;/td&gt;
&lt;td&gt;The failover path. Without multipathing it appears as an ordinary path too&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;AWS's&lt;br&gt;
&lt;a href="https://docs.aws.amazon.com/fsx/latest/ONTAPGuide/provision-nvme-linux.html" rel="noopener noreferrer"&gt;Provisioning NVMe/TCP for Linux&lt;/a&gt;&lt;br&gt;
includes a step that confirms multipath has come up: the fourth item under "To discover the target&lt;br&gt;
NVMe nodes". &lt;strong&gt;It says that a returned &lt;code&gt;Y&lt;/code&gt; means success&lt;/strong&gt; (wording as of 2026-09-17; every&lt;br&gt;
reference to that page below was checked on the same day).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/module/nvme_core/parameters/multipath
&lt;span class="c"&gt;# Y means success&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;On the Amazon Linux 2023 AMIs I used, that file did not exist.&lt;/strong&gt; The kernel does not have&lt;br&gt;
Asymmetric Namespace Access (a mechanism that tells the host which of several paths to the same&lt;br&gt;
namespace is the shortest one) in it. The range I checked is the three kernel series returned by&lt;br&gt;
&lt;code&gt;al2023-ami-kernel-default-x86_64&lt;/code&gt;, so I cannot write "AL2023 always behaves this way". Checking&lt;br&gt;
takes one command, and a later section gives it.&lt;/p&gt;

&lt;p&gt;The procedure is written for RHEL 9.3. The same page's "Before you begin" says&lt;br&gt;
&lt;code&gt;Create an EC2 instance running Red Hat Enterprise Linux (RHEL) 9.3&lt;/code&gt;. Immediately after that there&lt;br&gt;
is a statement about other distributions. In summary: on AMIs other than RHEL 9.3 some of the&lt;br&gt;
utilities may already be present or the install command may differ, but &lt;strong&gt;apart from installing&lt;br&gt;
packages, the commands in this section are valid on other EC2 Linux AMIs too&lt;/strong&gt; (the original wording&lt;br&gt;
is at the end of "Before you begin" on that page).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What this article reports is two counterexamples to that statement.&lt;/strong&gt; Neither of them is&lt;br&gt;
explained by package installation.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command from the documentation&lt;/th&gt;
&lt;th&gt;Result on AL2023&lt;/th&gt;
&lt;th&gt;A packaging problem?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cat /etc/nvme/hostnqn&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;File does not exist&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No. &lt;code&gt;nvme-cli&lt;/code&gt; is installed; whether it creates this file differs by distribution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cat /sys/module/nvme_core/parameters/multipath&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;File does not exist&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No. This is a &lt;strong&gt;kernel build option&lt;/strong&gt; (&lt;code&gt;CONFIG_NVME_MULTIPATH&lt;/code&gt;) and a package cannot change it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;You can check this before any billing starts.&lt;/strong&gt; That is the most practical part of this article,&lt;br&gt;
I think.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is not a claim that "the documentation is wrong". It is a report that &lt;strong&gt;these two do not&lt;br&gt;
fall inside&lt;/strong&gt; the stated range (apart from installing packages, valid on other AMIs too).&lt;br&gt;
The state of my feedback to AWS is at the end of this article.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then, after bringing in a kernel that does have multipathing, I found that &lt;strong&gt;the &lt;code&gt;optimized&lt;/code&gt; /&lt;br&gt;
&lt;code&gt;non-optimized&lt;/code&gt; output of &lt;code&gt;nvme list-subsys&lt;/code&gt; says "there are two paths", not "two paths are in&lt;br&gt;
use".&lt;/strong&gt; Whether they are in use has to be counted separately, and there is a trap in choosing&lt;br&gt;
which table to count.&lt;/p&gt;

&lt;p&gt;What this article does not cover. It does not compare performance. The figures here exist to&lt;br&gt;
show that two clients agreed under the same conditions, and &lt;strong&gt;they cannot be used for sizing.&lt;/strong&gt; What&lt;br&gt;
moves the numbers is in &lt;a href="https://dev.to/aws-builders/why-amazon-fsx-for-netapp-ontap-block-performance-differed-by-about-2x-on-the-same-specified-35pi"&gt;a separate article&lt;/a&gt;. All figures are from a&lt;br&gt;
Single-AZ configuration; Multi-AZ is unverified.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is one of three, but there is no reading order. The block-protocol measurements are split&lt;br&gt;
by reader.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Article&lt;/th&gt;
&lt;th&gt;Reader&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;A&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/aws-builders/moving-on-premises-block-storage-to-aws-the-divide-by-625-mbps-sizing-approach-comes-out-wrong-31pc"&gt;Session and queue counts&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Anyone about to mount&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;B&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Checking whether multipathing is available&lt;/strong&gt; (this article)&lt;/td&gt;
&lt;td&gt;Anyone who followed the procedure and got stuck&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;C&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/aws-builders/why-amazon-fsx-for-netapp-ontap-block-performance-differed-by-about-2x-on-the-same-specified-35pi"&gt;What moves the numbers&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Anyone measuring or citing figures&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;None of them assumes the other two. Read only the part you need.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  The environment I measured
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;File system&lt;/td&gt;
&lt;td&gt;Amazon FSx for NetApp ONTAP, second generation SINGLE_AZ_2, 6,144 MBps × 1 HA pair, 4,096 GiB SSD, 200,000 provisioned IOPS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Region&lt;/td&gt;
&lt;td&gt;ap-northeast-1, single AZ&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ONTAP&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;9.18.1&lt;/strong&gt; (the reads and writes in the agreement table). &lt;strong&gt;For the 12 sequential-write points I failed to record the version.&lt;/strong&gt; &lt;code&gt;FileSystemTypeVersion&lt;/code&gt; returned &lt;code&gt;null&lt;/code&gt; and I tore the environment down before re-reading it from the cluster API. The kernel investigation also spans deployments on other versions. The cross-article version mapping is in the &lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/en/reference/block-protocol-ontap-version-matrix.md" rel="noopener noreferrer"&gt;ONTAP version matrix&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NVMe read cache&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Disabled&lt;/strong&gt; (confirmed on both nodes)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LUN / namespace&lt;/td&gt;
&lt;td&gt;600 GiB, on a 900 GiB volume&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Before measuring&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Wrote the full 600 GiB once.&lt;/strong&gt; Unwritten blocks on a thin-provisioned volume return zeros, so reading without writing first measures nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client&lt;/td&gt;
&lt;td&gt;c5n.9xlarge; 50 Gbps network is a guaranteed figure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Instrument&lt;/td&gt;
&lt;td&gt;VDBENCH 5.04.07 directly, &lt;strong&gt;&lt;code&gt;iorate=max&lt;/code&gt; (unlimited)&lt;/strong&gt;, 512 threads, 60 s warm-up + 300 s measurement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test data / efficiency settings&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Not recorded whether the VDBENCH payload was compressible, or whether volume inline efficiency (&lt;code&gt;StorageEfficiencyEnabled&lt;/code&gt;) was enabled.&lt;/strong&gt; On the file-protocol side, efficiency settings measurably affected figures; whether the same applies to block is unconfirmed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The window is 300 seconds, so the figures in this article include burst. They cannot be cited as a&lt;br&gt;
baseline.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The figures in this article are &lt;code&gt;iorate=max&lt;/code&gt; saturation points, not operating points. No target&lt;br&gt;
IOPS was given; they are what the system took when everything available was pushed at it. On the&lt;br&gt;
file-protocol side of the same repository, giving a target of 4,400 IOPS produced 4,406 MB/s at&lt;br&gt;
8.86 ms, while unlimited produced 4,204 MB/s at 121.79 ms. &lt;strong&gt;Unlimited was 5% lower in&lt;br&gt;
throughput and 14 times higher in response time.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With 512 threads fixed and no rate limit, response time becomes the identity &lt;code&gt;512 ÷ IOPS&lt;/code&gt;&lt;br&gt;
(derived 216.6 ms against measured 216.588 ms in a separate run). The figures below can be read&lt;br&gt;
through that identity: 1,277.48 MB/s is about 401 ms, and 131,736.5 IOPS is about&lt;br&gt;
3.9 ms. &lt;strong&gt;Both are derived values and were not recorded as measurements.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For this article's purpose, being a saturation point is not a disadvantage. The figures are&lt;br&gt;
here to show that Rocky and RHEL agreed under the same conditions, and as long as both are&lt;br&gt;
saturated the same way, the agreement test holds. &lt;strong&gt;Those values still cannot be used for&lt;br&gt;
sizing.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Behaviour on this AMI
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;On this AMI the file named at the top does not exist.&lt;/strong&gt; Reading the kernel configuration gives&lt;br&gt;
&lt;code&gt;# CONFIG_NVME_MULTIPATH is not set&lt;/code&gt;, and &lt;code&gt;modinfo nvme_core&lt;/code&gt; has no multipath parameter either.&lt;br&gt;
So multipathing is not in the kernel.&lt;/p&gt;

&lt;p&gt;As a result the two controllers are not merged into one device; they appear as &lt;strong&gt;separate devices&lt;br&gt;
pointing at the same namespace UUID&lt;/strong&gt; (&lt;code&gt;/dev/nvme2n1&lt;/code&gt; and &lt;code&gt;/dev/nvme3n1&lt;/code&gt;). &lt;code&gt;nvme list-subsys&lt;/code&gt; shows&lt;br&gt;
no &lt;code&gt;optimized&lt;/code&gt; / &lt;code&gt;non-optimized&lt;/code&gt; either.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The AWS procedure is written for RHEL 9.3, where it is present.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Please do not generalise this to "multipathing is not available on AL2023". What I observed were the&lt;br&gt;
AMIs that the &lt;code&gt;al2023-ami-kernel-default-x86_64&lt;/code&gt; SSM parameter returned on 2026-09-12 and&lt;br&gt;
09-13, and &lt;strong&gt;I did not record the kernel versions.&lt;/strong&gt; The template said "record the kernel version&lt;br&gt;
at measurement time" in place of pinning the AMI, and the block phase did not implement that.&lt;br&gt;
That is a gap in my records.&lt;/p&gt;

&lt;p&gt;Checking in your own environment is more reliable, and it is one command.&lt;/p&gt;


&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; NVME_MULTIPATH /boot/config-&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;I later widened the observation to three kernel series.&lt;/strong&gt; All three returned by&lt;br&gt;
&lt;code&gt;al2023-ami-kernel-default-x86_64&lt;/code&gt; (6.1.186 / 6.12.103 / 6.18.48) gave&lt;br&gt;
&lt;code&gt;# CONFIG_NVME_MULTIPATH is not set&lt;/code&gt;. You can check this before creating a file system, by&lt;br&gt;
extracting the config from the kernel package in the repository. It is known before billing&lt;br&gt;
starts, so checking in that order is cheaper.&lt;/p&gt;

&lt;p&gt;To measure multipathing I then brought in another distribution. &lt;strong&gt;The AWS procedure is written for RHEL 9.3,&lt;br&gt;
so I put both Rocky Linux 9.7, a rebuild, and RHEL 9.7 itself on the same namespace and compared&lt;br&gt;
them.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Workload&lt;/th&gt;
&lt;th&gt;RHEL 9.7&lt;/th&gt;
&lt;th&gt;Rocky 9.7&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1 MiB sequential read&lt;/td&gt;
&lt;td&gt;1,277.48 MB/s&lt;/td&gt;
&lt;td&gt;1,276.60–1,279.22 MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4 KiB random read&lt;/td&gt;
&lt;td&gt;131,736.5 IOPS&lt;/td&gt;
&lt;td&gt;131,550.6 / 131,749.4 IOPS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4 KiB random write&lt;/td&gt;
&lt;td&gt;195,439.8 IOPS&lt;/td&gt;
&lt;td&gt;194,960.6 IOPS&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Only the reads agreed in the same state&lt;/strong&gt; (within 0.1%). Same minor version, same &lt;code&gt;nvme-cli&lt;/code&gt;&lt;br&gt;
(2.16-1.el9), same cold state. The kernels differ in z-stream (5.14.0-611.55.1 against 611.5.1).&lt;br&gt;
For reads, there is now one piece of grounds for reading a rebuild's figure as the figure for the&lt;br&gt;
product itself.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Both write workloads had the Rocky side measured warm, so the states are not aligned. Counting&lt;br&gt;
the 0.3% difference on 4 KiB random write as "agreement" was my error, and &lt;strong&gt;the note in that same&lt;br&gt;
table contradicted my own conclusion&lt;/strong&gt; (corrected 2026-09-20). For that shape I can say neither&lt;br&gt;
that it holds nor that it does not.&lt;/p&gt;

&lt;p&gt;Sequential write was re-measured later, in the same state, for that shape alone. &lt;strong&gt;It does not&lt;br&gt;
agree (2026-09-17).&lt;/strong&gt; On a different file system (second generation, 1,536 MBps, 50,000 provisioned&lt;br&gt;
IOPS), same namespace, same state, same parameters, six runs each.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Client&lt;/th&gt;
&lt;th&gt;Min&lt;/th&gt;
&lt;th&gt;Median&lt;/th&gt;
&lt;th&gt;Max&lt;/th&gt;
&lt;th&gt;Spread&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Rocky 9.7&lt;/td&gt;
&lt;td&gt;842.70&lt;/td&gt;
&lt;td&gt;1,040.26&lt;/td&gt;
&lt;td&gt;1,092.50&lt;/td&gt;
&lt;td&gt;29.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RHEL 9.7&lt;/td&gt;
&lt;td&gt;1,105.75&lt;/td&gt;
&lt;td&gt;1,112.65&lt;/td&gt;
&lt;td&gt;1,129.30&lt;/td&gt;
&lt;td&gt;2.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Units MB/s (1 MiB sequential write, 8 threads, &lt;code&gt;o_direct&lt;/code&gt;, 120-second window). These 12&lt;br&gt;
points were taken under conditions that differ from the environment described at the top: a&lt;br&gt;
different file system (second generation, 1,536 MBps, 50,000 provisioned IOPS), 8 threads rather&lt;br&gt;
than 512, and 120 seconds rather than 300. The &lt;code&gt;512 ÷ IOPS&lt;/code&gt; reading given at the top cannot be&lt;br&gt;
applied to these 12 points. &lt;strong&gt;RHEL's minimum is above Rocky's maximum&lt;/strong&gt;: six against six, with no&lt;br&gt;
overlap in range. RHEL's median is 7.0% above Rocky's, and the asymmetry in spread is larger than&lt;br&gt;
the difference in medians.&lt;/p&gt;

&lt;p&gt;So "a rebuild's figure can be read as the figure for the product itself" is limited to the&lt;br&gt;
reads. When citing a sequential-write figure, say which of the two it was measured on.&lt;br&gt;
4 KiB random write was not re-measured in the same state, so for that shape I can say neither&lt;br&gt;
that it holds nor that it does not. &lt;strong&gt;I cannot state a cause.&lt;/strong&gt; The only difference on record is&lt;br&gt;
the kernel z-stream, and I did not vary it on its own. During the runs the file system side was at&lt;br&gt;
72–81% disk throughput, 16–19% IOPS and 31–38% network, none of them saturated (so this is not a&lt;br&gt;
case of the two meeting at a ceiling). For these 12 points I failed to record the ONTAP version.&lt;br&gt;
&lt;code&gt;FileSystemTypeVersion&lt;/code&gt; returned &lt;code&gt;null&lt;/code&gt; and I tore the environment down before re-reading it from&lt;br&gt;
the cluster API.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;iopolicy&lt;/code&gt; on a kernel with multipathing, and the value the procedure states
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;This is the third counterexample.&lt;/strong&gt; &lt;code&gt;iopolicy&lt;/code&gt; (Linux's multipath setting that decides how I/O&lt;br&gt;
is distributed across multiple paths to an NVMe device: &lt;code&gt;round-robin&lt;/code&gt; cycles through them,&lt;br&gt;
&lt;code&gt;queue-depth&lt;/code&gt; sends to whichever has the shortest queue) is what step 5 of the same procedure&lt;br&gt;
says to confirm as &lt;code&gt;round-robin&lt;/code&gt; (and the example &lt;code&gt;nvme list-subsys&lt;/code&gt; output at step 7 also shows&lt;br&gt;
&lt;code&gt;iopolicy=round-robin&lt;/code&gt;).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Value the documentation says to confirm&lt;/td&gt;
&lt;td&gt;&lt;code&gt;round-robin&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Measured on Rocky Linux 9.7 / RHEL 9.7&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;queue-depth&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What set it&lt;/td&gt;
&lt;td&gt;the udev rule &lt;code&gt;71-nvmf-netapp.rules&lt;/code&gt; shipped by &lt;code&gt;nvme-cli&lt;/code&gt; (2.16-1.el9)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The kernel default is &lt;code&gt;numa&lt;/code&gt;, neither &lt;code&gt;round-robin&lt;/code&gt; nor &lt;code&gt;queue-depth&lt;/code&gt;.&lt;/strong&gt; It was &lt;code&gt;queue-depth&lt;/code&gt; in&lt;br&gt;
this environment because the NetApp-oriented udev rule bundled with the &lt;code&gt;nvme-cli&lt;/code&gt; package set it.&lt;br&gt;
Following the confirmation step returns a value other than the expected one.&lt;/p&gt;

&lt;p&gt;I measured the effect on performance. It is negligible: switching policy mid-run produced no&lt;br&gt;
step change, and the difference was 0.5%. The figures and the method are in&lt;br&gt;
&lt;a href="https://dev.to/aws-builders/why-amazon-fsx-for-netapp-ontap-block-performance-differed-by-about-2x-on-the-same-specified-35pi"&gt;a separate article&lt;/a&gt; (C). So this is not a story about &lt;code&gt;round-robin&lt;/code&gt; being&lt;br&gt;
required for speed. &lt;strong&gt;The only part worth reporting is that the confirmation step returns a value&lt;br&gt;
that differs from the documentation.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This can change with the &lt;code&gt;nvme-cli&lt;/code&gt; version. What I observed here is one version, 2.16-1.el9,&lt;br&gt;
and &lt;strong&gt;I did not confirm whether other versions ship the same udev rule.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  How to check whether multipathing is using the second path
&lt;/h2&gt;

&lt;p&gt;This section was added afterwards. It became necessary in order to do the analysis above, and it&lt;br&gt;
is also the reason I once published a wrong conclusion.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;nvme list-subsys&lt;/code&gt; shows the two controllers as &lt;code&gt;optimized&lt;/code&gt; / &lt;code&gt;non-optimized&lt;/code&gt;, and prints &lt;code&gt;iopolicy&lt;/code&gt;&lt;br&gt;
as well. &lt;strong&gt;That is a display of "there are two paths", not of "two paths are in use".&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At first I varied &lt;code&gt;iopolicy&lt;/code&gt; three ways, compared total throughput, saw that only &lt;code&gt;queue-depth&lt;/code&gt; was&lt;br&gt;
faster, and wrote that "only &lt;code&gt;queue-depth&lt;/code&gt; uses the second path". &lt;strong&gt;That was wrong.&lt;/strong&gt; I had not&lt;br&gt;
counted bytes per path. A client-side total that does not increase is consistent with two different&lt;br&gt;
facts: "the second path is not being used", and "it is being used but the ceiling is nearer than&lt;br&gt;
the path".&lt;/p&gt;
&lt;h3&gt;
  
  
  Bytes per path can be read from two places
&lt;/h3&gt;

&lt;p&gt;On the ONTAP side they can be read from a counter table. &lt;strong&gt;There is a trap here.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Table&lt;/th&gt;
&lt;th&gt;Result in this configuration&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nvmf_lif&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Zero rows.&lt;/strong&gt; Still empty after writing 600 GiB over NVMe/TCP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;lif&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Reports &lt;strong&gt;0 bytes&lt;/strong&gt; for the LIF in question. It does not count NVMe-oF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;nvmf_tcp_port&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Has the real data.&lt;/strong&gt; &lt;code&gt;read_data&lt;/code&gt; / &lt;code&gt;write_data&lt;/code&gt; / &lt;code&gt;total_ops&lt;/code&gt; per LIF&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;"The table exists but has no rows" and "this version does not have that table" add up to the same&lt;br&gt;
appearance. Return 0 and move on, and &lt;strong&gt;you reach the correct conclusion, "the second path saw&lt;br&gt;
0 bytes", for the wrong reason.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It can also be counted on the client side. NVMe/TCP controllers have different destination&lt;br&gt;
addresses, so aggregating established sockets by destination separates the paths. That is a second&lt;br&gt;
source, independent of the ONTAP side.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ss &lt;span class="nt"&gt;-tin&lt;/span&gt; state established | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'/:4420/{...}'&lt;/span&gt;   &lt;span class="c"&gt;# sum bytes_sent / bytes_received per destination&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Counted properly, the second path was not running
&lt;/h3&gt;

&lt;p&gt;I ran four workloads under &lt;code&gt;queue-depth&lt;/code&gt; and took before/after differences.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Read bytes&lt;/th&gt;
&lt;th&gt;Write bytes&lt;/th&gt;
&lt;th&gt;Total ops&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;optimized&lt;/td&gt;
&lt;td&gt;+about 1,012 GiB&lt;/td&gt;
&lt;td&gt;+about 730 GiB&lt;/td&gt;
&lt;td&gt;641,908 → 120,008,866&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;non-optimized&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;±0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;±0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4 → 4&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Neither reads nor writes went to the second path.&lt;/strong&gt; The ONTAP side and the client side agreed, and&lt;br&gt;
it was the same on Rocky and on RHEL (the second path's increment across all workloads was about&lt;br&gt;
20 KB).&lt;/p&gt;

&lt;p&gt;Further, switching &lt;code&gt;iopolicy&lt;/code&gt; within a single run produces no step change. With a sequential read&lt;br&gt;
running I changed &lt;code&gt;queue-depth&lt;/code&gt; → &lt;code&gt;numa&lt;/code&gt; → &lt;code&gt;queue-depth&lt;/code&gt;, and the values sampled at one-second&lt;br&gt;
intervals stayed inside the same 1,150–1,400 MB/s range both before and after each switch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;All three kernel series returned by &lt;code&gt;al2023-ami-kernel-default-x86_64&lt;/code&gt; (6.1.186 / 6.12.103 /
6.18.48) had &lt;code&gt;CONFIG_NVME_MULTIPATH&lt;/code&gt; unset.&lt;/strong&gt; Measuring multipathing requires a different distribution.
&lt;strong&gt;Asking AWS why AL2023 is excluded from the procedure (reply 2026-09-21) surfaced a likely
reason:&lt;/strong&gt; &lt;code&gt;nvme connect-all&lt;/code&gt; establishes both paths and auto-discovers the alternate LIF even
when only one is given, but without &lt;code&gt;CONFIG_NVME_MULTIPATH&lt;/code&gt; the kernel never merges them into
one namespace device. The procedure's later steps assume that merge already happened, so
proceeding unmerged leaves the other path writable against the same namespace with no multipath
software mediating it: &lt;strong&gt;a data-corruption risk&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You can check before creating a file system.&lt;/strong&gt; Extract the config from the kernel package in the
repository and you know before billing starts. On a running instance it is the one command
&lt;code&gt;grep -i NVME_MULTIPATH /boot/config-$(uname -r)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rocky Linux 9.7's figures can be read as RHEL 9.7's figures, but only for the reads.&lt;/strong&gt; On the
same namespace in the same state they agreed within 0.1%. Both write workloads had the Rocky side
measured warm, so the states are not aligned. Counting the 0.3% difference on 4 KiB random write
as agreement was my error, and for that shape I can say neither that it holds nor that it does
not (corrected 2026-09-20). Sequential write does not agree: re-measured later in the same state,
six runs each, RHEL's median was 7.0% higher and RHEL's minimum was above Rocky's maximum (spread
29.6% for Rocky against 2.1% for RHEL). When citing, say which of the two it was measured on&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;optimized&lt;/code&gt; / &lt;code&gt;non-optimized&lt;/code&gt; in &lt;code&gt;nvme list-subsys&lt;/code&gt; is a display of path existence.&lt;/strong&gt; For whether
they are in use, count with the &lt;code&gt;nvmf_tcp_port&lt;/code&gt; counters. &lt;code&gt;nvmf_lif&lt;/code&gt; returns no rows, and &lt;code&gt;lif&lt;/code&gt;
does not count NVMe-oF&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In this configuration, not one byte went to the second path even under &lt;code&gt;queue-depth&lt;/code&gt;.&lt;/strong&gt; Two
places agreed (the ONTAP-side counters and the client-side sockets), and it was the same on Rocky
and on RHEL&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  AWS documentation notes and support inquiries
&lt;/h2&gt;

&lt;p&gt;This article touches three places in the documentation. All three were submitted on 2026-09-17.&lt;/p&gt;

&lt;p&gt;The source for all of them is&lt;br&gt;
&lt;a href="https://docs.aws.amazon.com/fsx/latest/ONTAPGuide/provision-nvme-linux.html" rel="noopener noreferrer"&gt;Provisioning NVMe/TCP for Linux&lt;/a&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Remark&lt;/th&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Against "apart from installing packages, valid on other EC2 Linux AMIs too": &lt;code&gt;cat /etc/nvme/hostnqn&lt;/code&gt; finds no file on AL2023&lt;/td&gt;
&lt;td&gt;Submitted (2026-09-17). &lt;strong&gt;Reproduced in the same environment, confirming the wording doesn't hold as-is for the AL2023 environment checked.&lt;/strong&gt; The two proposed fixes (scoping the wording, adding a generation step) have been shared as an improvement request. Adoption, content, and timing are unconfirmed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Against the same statement: &lt;code&gt;cat /sys/module/nvme_core/parameters/multipath&lt;/code&gt; finds no file on AL2023 (kernel build option)&lt;/td&gt;
&lt;td&gt;Submitted (2026-09-17). &lt;strong&gt;The observation was confirmed and filed as documentation feedback. Fix timing undecided.&lt;/strong&gt; A follow-up question ("why is AL2023 excluded from scope?") also got a technical explanation: proceeding with the paths unmerged leaves the other path writable against the same namespace with no multipath software mediating it, a &lt;strong&gt;data-corruption risk&lt;/strong&gt;, so the exclusion has a technical rationale&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Step 5 directs confirming that &lt;code&gt;iopolicy&lt;/code&gt; is &lt;code&gt;round-robin&lt;/code&gt;, but the measurement on Rocky 9.7 / RHEL 9.7 is &lt;code&gt;queue-depth&lt;/code&gt;. &lt;code&gt;nvme-cli&lt;/code&gt;'s version boundary is &lt;strong&gt;2.11 -&amp;gt; 2.12&lt;/strong&gt; (see below)&lt;/td&gt;
&lt;td&gt;Submitted (2026-09-17). &lt;strong&gt;On 2026-09-30 it was acknowledged that a note is needed stating whether &lt;code&gt;iopolicy&lt;/code&gt; resolves to &lt;code&gt;round-robin&lt;/code&gt; or &lt;code&gt;queue-depth&lt;/code&gt; depends on the environment, and this was fed to the responsible team&lt;/strong&gt; (with a note that NetApp's own published documentation carries the same point). The content and timing of any change are not disclosed in advance, so they are unconfirmed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The form I submitted was this. Verbatim quotation (which line of which step), reproduction steps,&lt;br&gt;
expected and measured values, and the range I confirmed across three kernel series, written as&lt;br&gt;
such. &lt;strong&gt;Not generalising to "multipathing is not available on AL2023"&lt;/strong&gt; is the same in the feedback: the&lt;br&gt;
observation is limited to the three series returned by &lt;code&gt;al2023-ami-kernel-default-x86_64&lt;/code&gt;.&lt;br&gt;
For the second one I also included the control under which the procedure does hold (on a kernel&lt;br&gt;
with &lt;code&gt;CONFIG_NVME_MULTIPATH=y&lt;/code&gt;, &lt;code&gt;Y&lt;/code&gt; is returned). It is not a report that the procedure itself is&lt;br&gt;
wrong.&lt;/p&gt;

&lt;p&gt;The third is not submitted as a performance problem. &lt;strong&gt;The difference between policies is 0.5% in&lt;br&gt;
this configuration, and the report is the single point that the confirmation step returns a value&lt;br&gt;
differing from the documentation.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The third changed shape before submission, after I looked at the package side. Reading&lt;br&gt;
&lt;code&gt;71-nvmf-netapp.rules&lt;/code&gt; out of four builds of the public &lt;code&gt;nvme-cli&lt;/code&gt; package (the SRPMs for 2.11 /&lt;br&gt;
2.13 / 2.16, plus the installed 2.16-1.el9) showed that 2.11 has &lt;code&gt;round-robin&lt;/code&gt; and 2.13 changed it&lt;br&gt;
to &lt;code&gt;queue-depth&lt;/code&gt; (confirmed 2026-09-17). At that point 2.12 had not been examined, so the boundary&lt;br&gt;
could only be written as "somewhere between 2.11 and 2.13".&lt;/p&gt;

&lt;p&gt;Submission status across the three block-protocol articles and S3 Burst Part 2 is collected in&lt;br&gt;
&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/en/reference/block-protocol-aws-feedback-status.md" rel="noopener noreferrer"&gt;AWS documentation notes and inquiries across the block-protocol articles&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A pointer to the upstream v2.11 and v2.12 sources followed, showing the boundary is v2.12.&lt;br&gt;
Fetching and checking that commit (2026-09-19, &lt;code&gt;71-nvmf-netapp.rules.in&lt;/code&gt;) confirmed that&lt;br&gt;
&lt;code&gt;queue-depth&lt;/code&gt; was already in place as of v2.12. &lt;strong&gt;So the version boundary is settled at&lt;br&gt;
2.11 -&amp;gt; 2.12&lt;/strong&gt; (the original "somewhere between 2.11 and 2.13" narrowed to 2.12, from both that&lt;br&gt;
pointer and my own re-check). This is agreed to be a version drift rather than an error in the&lt;br&gt;
documentation.&lt;/p&gt;

&lt;p&gt;On 2026-09-30, it was acknowledged for this point that a note is needed stating that whether&lt;br&gt;
&lt;code&gt;iopolicy&lt;/code&gt; resolves to &lt;code&gt;round-robin&lt;/code&gt; or &lt;code&gt;queue-depth&lt;/code&gt; depends on the environment, and it was fed to&lt;br&gt;
the responsible team.&lt;br&gt;
&lt;a href="https://docs.netapp.com/us-en/ontap-sanhost/nvme-rhel-9x.html" rel="noopener noreferrer"&gt;NetApp's NVMe-oF procedure for RHEL 9.x&lt;/a&gt;&lt;br&gt;
carries the same point (from RHEL 9.7 the default is &lt;code&gt;queue-depth&lt;/code&gt;; on 9.6 &lt;code&gt;round-robin&lt;/code&gt; is the&lt;br&gt;
default with &lt;code&gt;queue-depth&lt;/code&gt; selectable), consistent with this understanding. &lt;strong&gt;The content and timing&lt;br&gt;
of any change are not disclosed in advance, so its reflection in the documentation is unconfirmed.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Primary record of the figures
&lt;/h2&gt;

&lt;p&gt;The primary record of the figures and their conditions is in&lt;br&gt;
&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/ja/verification/perf-matrix-results.md" rel="noopener noreferrer"&gt;the protocol measurement results&lt;/a&gt;,&lt;br&gt;
and the reproduction steps are in&lt;br&gt;
&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/ja/verification/block-measurement-runbook.md" rel="noopener noreferrer"&gt;the block measurement runbook&lt;/a&gt;.&lt;br&gt;
What is unconfirmed is written as unconfirmed there, so &lt;strong&gt;please do not drop that when citing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;How to check whether multipathing is available, counter-table traps, and &lt;code&gt;iopolicy&lt;/code&gt; version differences are&lt;br&gt;
collected in&lt;br&gt;
&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/b39f5d8a7bcd3365d9e84bf64ec12ff8188678a1/docs/en/reference/block-protocol-testing-guide.md" rel="noopener noreferrer"&gt;Considerations when measuring FSx for ONTAP's block protocols&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>linux</category>
      <category>networking</category>
      <category>cloud</category>
    </item>
    <item>
      <title>AWS Transform Now Supports Block Storage Migration to FSx for ONTAP — Benefits and Pitfalls from a Hands-On EC2 Test</title>
      <dc:creator>Yoshiki Fujiwara(藤原 善基)@AWS Community Builder</dc:creator>
      <pubDate>Mon, 28 Sep 2026 13:42:13 +0000</pubDate>
      <link>https://dev.to/aws-builders/aws-transform-now-supports-block-storage-migration-to-fsx-for-ontap-benefits-and-pitfalls-from-a-1hhe</link>
      <guid>https://dev.to/aws-builders/aws-transform-now-supports-block-storage-migration-to-fsx-for-ontap-benefits-and-pitfalls-from-a-1hhe</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;This is Part 2 of the series "The Data Foundation for AWS Modernization." &lt;a href="https://dev.to/aws-builders/designing-aws-modernization-with-vmware-migration-as-the-entry-point-why-fsx-for-ontap-as-the-3k24"&gt;Part 1&lt;/a&gt; set out the lens: migration is not the goal but the entry point, and putting FSx for ONTAP at the data foundation lets you re-choose the compute. This part takes that entry point through AWS Transform, the core of modernization, and verifies hands-on that the data area lands on FSx for ONTAP as part of the migration on AWS. Migration from an on-premises VMware environment will come separately; the idea here is to start modernizing from the tightly coupled EC2 × EBS configuration by decoupling the data store.&lt;/p&gt;

&lt;p&gt;Until now, when you migrated servers with AWS Transform (MGN), every disk was placed on Amazon EBS. If the source was not ONTAP and you wanted the data area on Amazon FSx for NetApp ONTAP (FSx for ONTAP below), you needed a "two-step migration" — migrate to EBS first, then move the data — or a third-party block storage migration tool.&lt;/p&gt;

&lt;p&gt;With the update of 30 August 2026, &lt;strong&gt;FSx for ONTAP can now be chosen directly as the MGN target storage type&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS Transform announces general availability of Amazon FSx for NetApp ONTAP support: &lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/09/aws-transform-fsx-netapp-ontap-support/" rel="noopener noreferrer"&gt;https://aws.amazon.com/about-aws/whats-new/2026/09/aws-transform-fsx-netapp-ontap-support/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That looked useful, so I tried it hands-on straight away.&lt;br&gt;
The source in this test was &lt;strong&gt;not&lt;/strong&gt; an on-premises VMware environment but an EC2 instance already on AWS (Amazon Linux 2023). Using that EC2 as the source, I ran the new path that migrates the data area (EBS) directly to an FSx for ONTAP iSCSI LUN.&lt;/p&gt;

&lt;p&gt;Running it for real turned up several pitfalls that reading the documentation alone does not reveal.&lt;br&gt;
Besides sorting out what this new feature can and cannot do, this article records the traps I actually stepped on during the test — errors hidden behind a successful job, and an unexpected capacity spike.&lt;/p&gt;

&lt;p&gt;Here are the highlights up front, especially the traps to watch for.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only &lt;strong&gt;data volumes&lt;/strong&gt; can be targeted. Boot is always Amazon EBS.&lt;/li&gt;
&lt;li&gt;What became GA is "the MGN target storage type". It is a different thing from presenting FSx for ONTAP as a VMware datastore (the Amazon EVS side).&lt;/li&gt;
&lt;li&gt;In MGN's phases, "SNAPSHOT" was a volume Snapshot done as a metadata operation, and "LAUNCH" was the creation of a FlexClone (about 44 seconds for 8 GiB).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;[Trap 1] Finalize is not just clean-up; it is the step where physical capacity temporarily peaks (about 2x)&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;[Trap 2]&lt;/strong&gt; Even when the job status becomes &lt;code&gt;COMPLETED&lt;/code&gt;, &lt;strong&gt;a failed final snapshot (fallback) or the creation of an unbootable target can be hiding behind it&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Test Assumptions and Scope
&lt;/h3&gt;

&lt;p&gt;The test was run with the following environment and scope.&lt;br&gt;
(Region: ap-northeast-1 / Source: Amazon EC2 (AL2023) / ONTAP: 9.18.1P3D1)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[What was run]&lt;/strong&gt;&lt;br&gt;
The whole flow: agent installation → replication → test launch → cutover → Finalize → teardown.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[Out of scope for this article (not verified)]&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Migration with VMware as the source (no vCenter was prepared, so EC2 stood in as the source)&lt;/li&gt;
&lt;li&gt;FSx for ONTAP as an Amazon EVS datastore (a different feature)&lt;/li&gt;
&lt;li&gt;Durations at production-scale data volumes (the test used a tiny 16 GiB environment, dominated by fixed overhead)&lt;/li&gt;
&lt;li&gt;Windows sources, and access over SMB / NFS&lt;/li&gt;
&lt;li&gt;Post-migration optimization with &lt;code&gt;lun move start&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  The Shape of the Configuration
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo204zcvvabvj4phoc428.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo204zcvvabvj4phoc428.png" alt="A table-shaped diagram pairing three Amazon EC2 instances with their boot / root volume and their data volume. The rows are, from the top, the source (Amazon EC2 in this test), staging and after cutover. The columns are the boot / root volume and the data volume. The boot column is Amazon Elastic Block Store in all three rows: boot 8 GiB at the source, the boot staging at staging, and root 8 GiB after cutover. In the data column, only the source row is Amazon Elastic Block Store (data 4 GiB × 2); staging and after cutover are Amazon FSx for NetApp ONTAP (data staging, then the target FlexVol over iSCSI). The rows are joined downwards by block replication and cutover arrows" width="800" height="793"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 1: three EC2 instances and where each volume goes: the boot column is EBS in all three rows, and only the data column changes to FSx for ONTAP from staging on (&lt;a href="https://raw.githubusercontent.com/Yoshiki0705/VMware-Migration-EC2-ONTAP/3421f0b8d162eff3f612f544acdf99f624411a0d/docs/_assets/images/png/atx-fsxn-data-path-en-dark%402x.png" rel="noopener noreferrer"&gt;dark theme&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp9inulvx8bxc11ekqat6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp9inulvx8bxc11ekqat6.png" alt="The control path. AWS Transform reaches the ONTAP management endpoint (REST API / 443) inside the customer VPC through AWS PrivateLink and a Network Load Balancer. The management endpoint belongs to Amazon FSx for NetApp ONTAP, whose icon and service name sit directly above the box. The client certificate goes from AWS Secrets Manager, outside the VPC, to AWS Transform" width="800" height="427"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 2: the control path, from AWS Transform to the ONTAP management endpoint (&lt;a href="https://raw.githubusercontent.com/Yoshiki0705/VMware-Migration-EC2-ONTAP/3421f0b8d162eff3f612f544acdf99f624411a0d/docs/_assets/images/png/atx-fsxn-control-path-en-dark%402x.png" rel="noopener noreferrer"&gt;dark theme&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The point is that the boot area and the data area go to different places. &lt;strong&gt;The boot column stays Amazon EBS in all three rows, and only the data column changes to FSx for ONTAP from the staging row on.&lt;/strong&gt; The target instance boots from EBS and receives the data over iSCSI. The source in this test was EC2, but the lower two rows of the configuration are the same whether the source is a VMware datastore or on-premises block storage.&lt;/p&gt;

&lt;p&gt;For the control path in Figure 2, the official blog says a PrivateLink connection is established automatically, but in practice a Network Load Balancer (NLB) and a VPC endpoint service are created inside your VPC. They incur hourly and LCU charges.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Normal operation (replicating)&lt;/th&gt;
&lt;th&gt;After cutover&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Source EC2&lt;/td&gt;
&lt;td&gt;Running. The agent keeps sending blocks&lt;/td&gt;
&lt;td&gt;Can be stopped&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Replication server EC2&lt;/td&gt;
&lt;td&gt;MGN launches it automatically&lt;/td&gt;
&lt;td&gt;Terminated by Finalize&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amazon EBS&lt;/td&gt;
&lt;td&gt;Boot staging&lt;/td&gt;
&lt;td&gt;Becomes the target's root volume&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FSx for ONTAP&lt;/td&gt;
&lt;td&gt;Staging FlexVol and LUN&lt;/td&gt;
&lt;td&gt;Target FlexVol (FlexClone)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network Load Balancer&lt;/td&gt;
&lt;td&gt;Running&lt;/td&gt;
&lt;td&gt;Remains after Finalize (manual deletion needed)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Table 1: each layer after cutover: the one that needs manual deletion is the Network Load Balancer&lt;/p&gt;


&lt;h2&gt;
  
  
  The Most Important Distinction: What Became GA Is the MGN Target
&lt;/h2&gt;

&lt;p&gt;Mix this up and you will evaluate the wrong product. It is easily confused with "FSx for ONTAP can be used as a VMware datastore".&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;Current support&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Can FSx for ONTAP be used as the MGN target storage type?&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes (GA with this release)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Is FSx for ONTAP presented as a VMware datastore?&lt;/td&gt;
&lt;td&gt;No. The datastore is a separate feature on the Amazon EVS side&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can the boot disk also go on FSx for ONTAP?&lt;/td&gt;
&lt;td&gt;No. Boot is always Amazon EBS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can EBS and FSx for ONTAP be mixed within one server?&lt;/td&gt;
&lt;td&gt;No. All data volumes use the same type&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can you migrate agentlessly?&lt;/td&gt;
&lt;td&gt;No. Agent-based replication only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Which protocol do clients connect with?&lt;/td&gt;
&lt;td&gt;iSCSI. The data is placed as LUNs inside a FlexVol&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Table 2: questions and current support: the agentless path is not supported&lt;/p&gt;

&lt;p&gt;The Known limitations in the MGN documentation state &lt;code&gt;Agent-based replication only&lt;/code&gt;. If you were planning around vCenter's agentless path, the plan has to change. The path depends on whether you leave VMs for EC2, or keep the VMs and move only the storage out.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fifkdinxlwqr195t8zjlw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fifkdinxlwqr195t8zjlw.png" alt="The replication template edit screen of AWS Transform MGN. The Amazon FSx for NetApp ONTAP configuration section has two inputs: Storage Virtual Machine (SVM) ID and FSx Storage Secret ARN. The note above them reads: Data disks will be migrated to FSx for ONTAP. Boot disk is always migrated to EBS as required by Amazon EC2." width="800" height="426"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 3: the FSx for ONTAP settings in the replication template. Only the SVM ID and the Secret ARN are specified, and the note states that boot goes to EBS&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Environments This Configuration Suits
&lt;/h2&gt;

&lt;p&gt;Whether a migration fits depends on the shape of the workload, not on the product of the source storage.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The boot / root area and the data area are separate, and the data area is on block storage&lt;/li&gt;
&lt;li&gt;You want capabilities such as Snapshot and thin provisioning on the data area after migration&lt;/li&gt;
&lt;li&gt;You want to turn "migrate, then move the data" into a single step&lt;/li&gt;
&lt;li&gt;You already run ONTAP and want the same mechanisms (Snapshot / FlexClone / SnapMirror) after migration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most of these apply whatever the source storage is. Even when the source is ONTAP, however, permissions, quotas and Snapshot policies are not migrated and need to be set up again.&lt;/p&gt;
&lt;h3&gt;
  
  
  Environments Where Another Option Fits Better for Now
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Boot and data sit on the same volume&lt;/li&gt;
&lt;li&gt;You cannot install an agent (agentless is not supported)&lt;/li&gt;
&lt;li&gt;The source is Windows and you want to move the data as SMB shares&lt;/li&gt;
&lt;li&gt;File-level migration is enough and block-level replication is not needed (a file transfer such as AWS DataSync is sufficient)&lt;/li&gt;
&lt;li&gt;The source is ONTAP and you want to take the volumes as they are (SnapMirror takes fewer steps)&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  A Mini Glossary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Term&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;Where it appears in this article&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SVM&lt;/td&gt;
&lt;td&gt;A logical server inside the file system&lt;/td&gt;
&lt;td&gt;Where MGN creates the FlexVol and LUN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FlexVol&lt;/td&gt;
&lt;td&gt;A volume that holds data. LUNs live inside it too&lt;/td&gt;
&lt;td&gt;MGN creates one for staging and one for the target&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LUN&lt;/td&gt;
&lt;td&gt;A unit of block storage, exposed over iSCSI&lt;/td&gt;
&lt;td&gt;One source disk becomes one LUN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Snapshot&lt;/td&gt;
&lt;td&gt;A point-in-time image inside a volume&lt;/td&gt;
&lt;td&gt;MGN's SNAPSHOT phase creates it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FlexClone&lt;/td&gt;
&lt;td&gt;A writable clone made from a Snapshot&lt;/td&gt;
&lt;td&gt;The target FlexVol is one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Split&lt;/td&gt;
&lt;td&gt;Detaching a FlexClone from its parent and materializing it&lt;/td&gt;
&lt;td&gt;Finalize runs it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h2&gt;
  
  
  The Procedure and the Measured Time for Each Step
&lt;/h2&gt;

&lt;p&gt;The entry point is the AWS Transform workspace. MGN runs the migration itself, but it is AWS Transform that handles everything from planning to landing zone and network as a single job.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Measured&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Initialize MGN&lt;/td&gt;
&lt;td&gt;Done from the console. The CLI failed (see below)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Install the agent&lt;/td&gt;
&lt;td&gt;Failed once (see below)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Initial full sync (16 GiB / 3 disks)&lt;/td&gt;
&lt;td&gt;226 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Test launch&lt;/td&gt;
&lt;td&gt;SNAPSHOT 43 seconds, CONVERSION about 4 minutes 30 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Cutover (correct order)&lt;/td&gt;
&lt;td&gt;MGN job 649 seconds; 817 seconds from issuing it to confirming boot&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Finalize&lt;/td&gt;
&lt;td&gt;Split under 60 seconds; about 33 minutes until clean-up completed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;(&lt;em&gt;n=1 measurements at 16 GiB / 3 disks. Fixed overhead dominates, so they cannot be used as a basis for RTO.&lt;/em&gt;)&lt;/p&gt;


&lt;h2&gt;
  
  
  Snapshot and FlexClone: What AWS Transform's Phase Names Refer To
&lt;/h2&gt;

&lt;p&gt;I lined up the ONTAP side by timestamp to see which ONTAP operation each AWS Transform phase name corresponds to.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;AWS Transform phase&lt;/th&gt;
&lt;th&gt;Corresponding ONTAP operation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SNAPSHOT&lt;/td&gt;
&lt;td&gt;Creating a volume Snapshot of the staging FlexVol&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LAUNCH&lt;/td&gt;
&lt;td&gt;Creating a FlexClone from that Snapshot&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The SNAPSHOT phase is not a plain data copy but a metadata operation (44 seconds for 8 GiB). The target volume is a clone with &lt;code&gt;is_flexclone: true&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;What matters most is that &lt;strong&gt;a FlexClone uses almost no physical capacity when it is created&lt;/strong&gt;.&lt;br&gt;
Measured just before Finalize, the target volume's logical size was 7.91 GiB while its physical consumption was only 35.5 MiB. Capacity planning has to look at physical values, not logical ones.&lt;/p&gt;


&lt;h2&gt;
  
  
  Finalize Is Not Clean-up; It Is Where Capacity Peaks
&lt;/h2&gt;

&lt;p&gt;Running Finalize starts a split of the FlexClone (the operation that detaches it and makes it independent), which materializes the data.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Elapsed from T0&lt;/th&gt;
&lt;th&gt;Observed change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Immediately&lt;/td&gt;
&lt;td&gt;Lifecycle &lt;code&gt;CUTOVER&lt;/code&gt;, replication &lt;code&gt;DISCONNECTED&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;About 3 minutes&lt;/td&gt;
&lt;td&gt;FlexClone split starts. Physical consumption rising from 35.5 MiB → 3.94 GiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;About 4 minutes&lt;/td&gt;
&lt;td&gt;Split completes. Physical consumption 8.57 GiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;About 13 minutes&lt;/td&gt;
&lt;td&gt;Staging FlexVol deleted, replication server EC2 terminated&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;There was a gap of about 9 minutes between the split completing and the staging volume being deleted.&lt;br&gt;
&lt;strong&gt;During those 9 minutes, physical capacity holds twice the migrated data.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you run Finalize while the aggregate's free space is below the migrated data size, it is likely to get stuck here. Keep in mind that Finalize is a capacity risk, not an availability risk.&lt;/p&gt;


&lt;h2&gt;
  
  
  Four Places I Stumbled
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. A Failed Final Snapshot Behind a Successful Job
&lt;/h3&gt;

&lt;p&gt;To measure downtime, I shut down the source OS and then ran cutover. The job returned &lt;code&gt;COMPLETED&lt;/code&gt; and the target &lt;code&gt;LAUNCHED&lt;/code&gt;. It looked like a success, but the job log recorded this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;09:14:21  SNAPSHOT_START
09:19:22  SNAPSHOT_FAIL            timed out after 300 seconds
09:19:23  USING_PREVIOUS_SNAPSHOT

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final sync did not complete, and the job fell back to the previous snapshot. The cause was my procedure: shutting down the OS also stopped the replication agent, and taking the crash-consistent snapshot timed out.&lt;/p&gt;

&lt;p&gt;The correct procedure is to stop only the application's writes and run cutover with the OS and the agent still running. "Stop writes on the source" in the official documentation does not mean shutting down the OS.&lt;/p&gt;

&lt;p&gt;In this test there were no recent writes, so the data matched, but doing this in a real environment &lt;strong&gt;loses the latest data written after the snapshot it fell back to.&lt;/strong&gt; What makes this risky is that it does not show up in the job status (&lt;code&gt;status&lt;/code&gt; or &lt;code&gt;launchStatus&lt;/code&gt;) at all. By the API's design, the job returns &lt;code&gt;COMPLETED&lt;/code&gt; as long as processing finishes, even after a fallback.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson&lt;/strong&gt;: After cutover, always check &lt;code&gt;event&lt;/code&gt; in &lt;code&gt;describe-job-log-items&lt;/code&gt;. A &lt;code&gt;COMPLETED&lt;/code&gt; job is not evidence that the final sync succeeded.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Two Unbootable Targets: NVMe Device Names Re-enumerated
&lt;/h3&gt;

&lt;p&gt;I ran cutover in the correct order twice, and both produced instances that would not boot (a UEFI shell reboot loop). The job log showed no abnormal events, and the status was &lt;code&gt;COMPLETED&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Examining the boot EBS volume of an unbootable instance, &lt;strong&gt;the 8 GiB boot volume held the contents of a 4 GiB data disk.&lt;/strong&gt; The boot and data disk assignment had been swapped.&lt;/p&gt;

&lt;p&gt;The cause: when the source was stopped and restarted as part of the test, &lt;strong&gt;NVMe device names were re-enumerated and the boot disk moved from &lt;code&gt;/dev/nvme0n1&lt;/code&gt; to &lt;code&gt;/dev/nvme1n1&lt;/code&gt;&lt;/strong&gt;. MGN's staging-type assignment appears to be keyed on the device name, and it is not re-evaluated after the name moves.&lt;/p&gt;

&lt;p&gt;The official best practices say not to restart the source before cutover in the first place. If a restart does happen, check the disk assignment with a command like the one below and redo the test launch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check which device is treated as the boot disk, and its assignment&lt;/span&gt;
aws mgn get-replication-configuration &lt;span class="nt"&gt;--source-server-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SRC&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'replicatedDisks[?isBootDisk==`true`].[deviceName,stagingDiskType]'&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; table

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. No Repair API, and Three Contradicting Errors
&lt;/h3&gt;

&lt;p&gt;I called &lt;code&gt;UpdateReplicationConfiguration&lt;/code&gt; to fix the disk assignment, but whatever parameters I passed, it returned contradicting errors, and the API could not fix it.&lt;/p&gt;

&lt;p&gt;In the end I deleted the source server and reinstalled the agent, which turned out to be unnecessary: rerunning the installer is the documented procedure.&lt;br&gt;
Rerunning the installer re-establishes the list of replicated disks and their mapping against the actual machine. To avoid losing launch template customizations, try rerunning the installer before deleting anything.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. The Installer's Hint Did Not Match the Real Cause
&lt;/h3&gt;

&lt;p&gt;When the agent installation failed, the installer displayed &lt;code&gt;Are kernel linux headers installed correctly?&lt;/code&gt;. Reading the log closely, the real cause was lack of space in &lt;code&gt;/tmp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;On Amazon Linux 2023, &lt;code&gt;/tmp&lt;/code&gt; is tmpfs; on a t3.small it has only 955 MiB, which does not meet the installer's requirement (1 GB or more free).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo env &lt;/span&gt;&lt;span class="nv"&gt;TMPDIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/tmp/mgn-build &lt;span class="nv"&gt;TEMP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/tmp/mgn-build &lt;span class="nv"&gt;TMP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/tmp/mgn-build &lt;span class="se"&gt;\&lt;/span&gt;
  ./aws-replication-installer-init &lt;span class="nt"&gt;--region&lt;/span&gt; ap-northeast-1 &lt;span class="nt"&gt;--no-prompt&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pointing it at &lt;code&gt;/var/tmp&lt;/code&gt; like this works around it. The installer's hint is not necessarily the real cause, so check the error code in the log.&lt;/p&gt;




&lt;h2&gt;
  
  
  Teardown Checklist
&lt;/h2&gt;

&lt;p&gt;Finalize does not finish the clean-up. The correct teardown order for leaving nothing behind is as follows (steps 1–7 depend on each other).&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Terminate the target and source EC2 instances&lt;/li&gt;
&lt;li&gt;Delete the MGN source server&lt;/li&gt;
&lt;li&gt;Reject the VPC endpoint connection, then delete the endpoint service&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Delete the NLB and its target group (important: charges continue until you delete them by hand)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Delete the target FlexVol with &lt;code&gt;fsx delete-volume&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Delete the SVM with &lt;code&gt;fsx delete-storage-virtual-machine&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Delete the ONTAP &lt;code&gt;security login&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The staging EBS volumes are deleted automatically with a delay, but the target's root EBS volume has no &lt;code&gt;DeleteOnTermination&lt;/code&gt; and must be deleted manually.&lt;/p&gt;




&lt;h2&gt;
  
  
  Initializing AWS Transform from the CLI: Create the IAM Roles First
&lt;/h2&gt;

&lt;p&gt;If running &lt;code&gt;aws mgn initialize-service&lt;/code&gt; from the API or CLI fails, the cause is that the IAM roles have not been created.&lt;br&gt;
Initializing from the console creates the IAM roles for you, but on the CLI path you need to create eight roles (including &lt;code&gt;AWSApplicationMigrationFsxProxyRole&lt;/code&gt; for FSx for ONTAP) and attach their policies first.&lt;/p&gt;




&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;With this GA, a migration now finishes with the data area already on FSx for ONTAP as iSCSI LUNs, and the step of moving the data afterwards is gone.&lt;/p&gt;

&lt;p&gt;Any server whose boot and data are separate fits the same shape, whatever the original storage.&lt;br&gt;
On the other hand, watch for the configuration spanning two kinds of storage, the physical capacity spike during Finalize, and MGN-specific traps such as "a job status of COMPLETED does not mean the final sync succeeded".&lt;/p&gt;

&lt;p&gt;I hope this test record helps anyone migrating servers with separate boot and data to EC2 while considering FSx for ONTAP as the home for the data area.&lt;/p&gt;




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/09/aws-transform-fsx-netapp-ontap-support/" rel="noopener noreferrer"&gt;AWS What's New: AWS Transform announces general availability of Amazon FSx for NetApp ONTAP support&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/mgn/latest/ug/fsx-ontap.html" rel="noopener noreferrer"&gt;MGN: FSx for ONTAP configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/mgn/latest/ug/best_practices_mgn.html" rel="noopener noreferrer"&gt;MGN: Best practices for AWS Transform MGN&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/fsx/latest/ONTAPGuide/evs-ontap.html" rel="noopener noreferrer"&gt;FSx for ONTAP: Using Amazon Elastic VMware Service with FSx for ONTAP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Yoshiki0705/VMware-Migration-EC2-ONTAP/blob/main/docs/en/atx-fsxn-ga-verification.md" rel="noopener noreferrer"&gt;Verification report (GitHub)&lt;/a&gt;: the test environment configuration, the measured teardown procedure, and the list of feedback sent to AWS, all omitted from this article&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;All test environments have been deleted. The figures are single measurements under a specific environment and conditions, and vary with data size and configuration. The configuration details are in the verification report above.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article is Part 2 of the series "The Data Foundation for AWS Modernization." The Japanese original is on &lt;a href="https://hakobiya.hatenablog.com/entry/fsxn-aws-transform-mgn-migration-target" rel="noopener noreferrer"&gt;hatenablog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>netapp</category>
      <category>migration</category>
      <category>storage</category>
    </item>
    <item>
      <title>Designing AWS Modernization with VMware Migration as the Entry Point — Why FSx for ONTAP as the Data Foundation</title>
      <dc:creator>Yoshiki Fujiwara(藤原 善基)@AWS Community Builder</dc:creator>
      <pubDate>Mon, 28 Sep 2026 13:42:08 +0000</pubDate>
      <link>https://dev.to/aws-builders/designing-aws-modernization-with-vmware-migration-as-the-entry-point-why-fsx-for-ontap-as-the-3k24</link>
      <guid>https://dev.to/aws-builders/designing-aws-modernization-with-vmware-migration-as-the-entry-point-why-fsx-for-ontap-as-the-3k24</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;What comes "next" for VMware workloads? Hardly a day goes by without the question coming up.&lt;/p&gt;

&lt;p&gt;The licensing changes that followed Broadcom's acquisition of VMware have pushed many organizations into a fundamental review of their virtualization strategy. But it is worth pausing here: &lt;strong&gt;"getting off VMware" is not the goal.&lt;/strong&gt; The real destination is the modernization beyond the migration — reworking applications and data, up to and including serverless and cloud-native architectures. The AWS Storage Blog, too, frames the migration not as mere license avoidance but as an opportunity to modernize infrastructure. &lt;a href="https://aws.amazon.com/blogs/storage/expedite-vmware-migration-to-amazon-ec2-and-amazon-fsx-for-netapp-ontap-using-bluexp-workload-factory-for-aws-migration-advisor/" rel="noopener noreferrer"&gt;(Reference: AWS Storage Blog — Expedite VMware migration to Amazon EC2 and Amazon FSx for NetApp ONTAP using the BlueXP workload factory migration advisor)&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Core of Modernization on AWS — AWS Transform
&lt;/h3&gt;

&lt;p&gt;What drives modernization on AWS today is &lt;strong&gt;AWS Transform&lt;/strong&gt;, an agentic-AI service. Beyond rehosting VMware workloads, it can containerize source code (replatforming to Amazon ECS / Amazon EKS) and take .NET Framework applications to cross-platform .NET on Linux — capabilities that &lt;strong&gt;run migration and modernization in parallel&lt;/strong&gt;. It lifts migration from "moving servers" to "reworking applications," and that is what modernization on AWS looks like now.&lt;/p&gt;

&lt;p&gt;However you rework the compute — EC2, containers, or serverless — one thing is always needed: &lt;strong&gt;a place for the data&lt;/strong&gt;. This is where &lt;strong&gt;Amazon FSx for NetApp ONTAP&lt;/strong&gt;, which I work with, comes alongside.&lt;/p&gt;

&lt;p&gt;I field questions about FSx for ONTAP every day, and one thing these migrations tend to overlook is &lt;strong&gt;the continuity and extensibility of the storage operations model&lt;/strong&gt;. Keep the storage operations built up over years in VMware and ONTAP environments — Snapshot, Clone, Replication, Storage Efficiency — on AWS too, and let a multi-protocol data hub support everything from migration through to cloud-native. Keep the data layer consistent and you can re-choose the compute as many times as you like. &lt;strong&gt;Designing modernization with AWS migration as the entry point and the data foundation at the core&lt;/strong&gt; — that is the lens of this series.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five Destinations for VMware Workloads
&lt;/h2&gt;

&lt;p&gt;AWS officially presents five pathways for VMware workloads. &lt;a href="https://aws.amazon.com/vmware/explore/" rel="noopener noreferrer"&gt;(Reference: AWS for VMware — Comprehensive Pathways)&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  AWS for VMware: The Pathways to Migrate and Modernize on AWS
&lt;/h3&gt;

&lt;p&gt;I drew the five official pathways as five rows, combining AWS services with ONTAP:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Row 1 is Migrate to Amazon EC2: Amazon EC2 reaches Amazon FSx for NetApp ONTAP over iSCSI.&lt;/li&gt;
&lt;li&gt;Row 2 is Modernize on AWS: Amazon Elastic Container Service, Amazon Elastic Kubernetes Service, AWS Fargate, Amazon WorkSpaces, AWS Lambda, AWS Batch and Amazon FSx for NetApp ONTAP.&lt;/li&gt;
&lt;li&gt;Row 3 is Run VMware on AWS: Amazon Elastic VMware Service reaches Amazon FSx for NetApp ONTAP over NFS / SMB.&lt;/li&gt;
&lt;li&gt;Row 4 is Run AWS on-premises: AWS Outposts and NetApp ONTAP.&lt;/li&gt;
&lt;li&gt;Row 5 is Run third-party hypervisors on AWS: Red Hat OpenShift Service on AWS, Amazon FSx for NetApp ONTAP, and Nutanix Cloud Clusters on AWS.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr3tsogarfy4g89hihu60.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr3tsogarfy4g89hihu60.png" alt="Figure 1: the five official AWS pathways to migrate and modernize on AWS. Amazon FSx for NetApp ONTAP can sit in the data layer of any of them" width="800" height="1204"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(&lt;a href="https://raw.githubusercontent.com/Yoshiki0705/VMware-Migration-EC2-ONTAP/471435e0e879bc08e3c844cc5b1ada122c7d2396/docs/_assets/images/png/atx-fsxn-vmware-pathways-en-dark%402x.png" rel="noopener noreferrer"&gt;dark theme&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Source: &lt;a href="https://aws.amazon.com/vmware/partner-offerings/" rel="noopener noreferrer"&gt;AWS for VMware Partner Offerings&lt;/a&gt;&lt;br&gt;
— "AWS offers the most comprehensive set of migration and modernization options for&lt;br&gt;
VMware-based workloads - from relocating to Amazon EVS, to rehosting on Amazon EC2,&lt;br&gt;
containerizing with Amazon EKS, or transitioning to running third-party hypervisors&lt;br&gt;
in the cloud like ROSA and NC2 on AWS." (Summarized; rephrased for licensing constraints.)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Each Pathway in Detail
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Rehost to Amazon EC2
&lt;/h4&gt;

&lt;p&gt;The most straightforward path: migrating VMware VMs as EC2 instances. AWS Transform for VMware (an agentic-AI-based migration service) became generally available in May 2025. &lt;a href="https://aws.amazon.com/about-aws/whats-new/2025/05/aws-transform-vmware-generally-available/" rel="noopener noreferrer"&gt;(Reference: AWS What's New)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Separately, on 30 August 2026, &lt;strong&gt;FSx for ONTAP became generally available as a target storage type for AWS Transform for migrations (MGN)&lt;/strong&gt;. The former is a service for migrating from VMware; the latter is a target-storage choice for server migration. They are different features, and Part 2 of this series covers the latter. &lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/09/aws-transform-fsx-netapp-ontap-support/" rel="noopener noreferrer"&gt;(Reference: AWS What's New)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Storage configuration options:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;EBS only&lt;/strong&gt;: Simple. Automated with MGN / AWS Transform&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EBS (OS) + FSx for ONTAP (Data)&lt;/strong&gt;: Keeps ONTAP capabilities. Supported by AWS Transform (MGN) / Shift Toolkit / Cirrus Migrate Cloud (&lt;strong&gt;the scope of this series&lt;/strong&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  2. Modernization (Containers / Serverless)
&lt;/h4&gt;

&lt;p&gt;As the next step after rehosting to EC2, you can modernize according to the characteristics of each workload.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;th&gt;Suitable workloads&lt;/th&gt;
&lt;th&gt;FSx for ONTAP integration&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ECS / EKS (EC2 mode)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Stateful containers (DB, middleware)&lt;/td&gt;
&lt;td&gt;✅ iSCSI / NFS mount available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ECS / EKS (Fargate)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Stateless microservices&lt;/td&gt;
&lt;td&gt;△ Through EFS only (no iSCSI)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AWS Lambda&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Event-driven, short-running processing&lt;/td&gt;
&lt;td&gt;△ EFS mount available, no iSCSI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AWS Batch&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Batch processing / HPC&lt;/td&gt;
&lt;td&gt;✅ iSCSI available in EC2 mode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Amazon WorkSpaces&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;VDI (virtual desktops)&lt;/td&gt;
&lt;td&gt;✅ FSx for ONTAP file shares&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is not about containerizing VMs as they are. The journey is "rehost to EC2 → containerize the application → move step by step to Fargate/Lambda".&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Amazon EVS (Keep VMware on AWS)
&lt;/h4&gt;

&lt;p&gt;With Amazon Elastic VMware Service, you can deploy VMware Cloud Foundation (VCF) directly on EC2 bare metal inside your VPC. You keep your existing vSphere skill set and can connect FSx for ONTAP as an external datastore.&lt;br&gt;
It suits cases with a large VMware-dependent application estate, where leaving VMware in a short time is impractical.&lt;/p&gt;

&lt;h4&gt;
  
  
  4. AWS Outposts (On-premises AWS + NetApp External Storage)
&lt;/h4&gt;

&lt;p&gt;AWS Outposts is a fully managed service that places AWS infrastructure on premises.&lt;br&gt;
Third-party block storage integration was announced in December 2024, and NetApp ONTAP and StorageGRID are available as partners validated through the AWS Service Ready Program. &lt;a href="https://aws.amazon.com/blogs/compute/new-simplifying-the-use-of-third-party-block-storage-with-aws-outposts/" rel="noopener noreferrer"&gt;(Reference: AWS Blog)&lt;/a&gt; &lt;a href="https://netapp.com/aws/outposts/" rel="noopener noreferrer"&gt;(Reference: NetApp)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;NetApp ONTAP iSCSI LUNs can be attached directly from the AWS console as data volumes for EC2 instances, and boot volume support was added in July 2025.&lt;br&gt;
This gives you &lt;strong&gt;separation of compute and storage&lt;/strong&gt;: "compute is AWS-managed, storage is your existing ONTAP."&lt;/p&gt;

&lt;h4&gt;
  
  
  5. Partner Solutions (ROSA / Expanding Nutanix + NetApp Integration)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Red Hat OpenShift Service on AWS (ROSA):&lt;/strong&gt; A fully managed environment based on OpenShift. With the NetApp Trident CSI driver, ROSA Pods can access FSx for ONTAP over NFS/iSCSI. &lt;a href="https://aws.amazon.com/rosa/" rel="noopener noreferrer"&gt;(Reference)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nutanix + NetApp ONTAP (on-premises Nutanix Cloud Platform):&lt;/strong&gt; At .NEXT Chicago in April 2026, a partnership was announced that lets the Nutanix Cloud Platform (NCP / AHV) use NetApp ONTAP (AFF / FAS) as &lt;strong&gt;external storage&lt;/strong&gt;. Over an NFS-based connection, compute (Nutanix AHV) and storage (ONTAP) scale independently. GA is planned for &lt;strong&gt;the second half of 2026 (NetApp targets Q3 2026, with Early Access underway)&lt;/strong&gt;. &lt;strong&gt;The partnership covers on-premises NetApp enterprise storage; integration with Nutanix Cloud Clusters (NC2) on AWS or with FSx for ONTAP is not announced or committed in public sources&lt;/strong&gt; (NetApp mentions "NetApp cloud storage services for Nutanix Cloud Platform" as a future direction, with the timing and the services undetermined). &lt;a href="https://www.netapp.com/blog/modernize-virtualization-nutanix-partnership/" rel="noopener noreferrer"&gt;(Reference: NetApp Blog, GA Q3 2026)&lt;/a&gt; &lt;a href="https://www.nutanix.com/press-releases/2026/nutanix-and-netapp-form-strategic-alliance" rel="noopener noreferrer"&gt;(Reference: Nutanix press release)&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The NetApp ecosystem as a whole:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqxbr1uqg63kihg2d9bpi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqxbr1uqg63kihg2d9bpi.png" alt="On-premises on the left, AWS on the right. On the left, a hypervisor (VMware ESXi, Nutanix AHV or Hyper-V) uses NetApp ONTAP (FAS / AFF) as its datastore, holding the VMDK, VHDX or LUN. On the right, Amazon EC2 uses Amazon FSx for NetApp ONTAP. The on-premises ONTAP and Amazon FSx for NetApp ONTAP are connected bidirectionally by SnapMirror. Snapshot, FlexClone, SnapMirror and Storage Efficiency are the same on both sides" width="800" height="426"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 2: the data layer stays the same when the compute changes. SnapMirror is what crosses the boundary (&lt;a href="https://raw.githubusercontent.com/Yoshiki0705/VMware-Migration-EC2-ONTAP/471435e0e879bc08e3c844cc5b1ada122c7d2396/docs/_assets/images/png/atx-fsxn-ontap-portability-en-dark%402x.png" rel="noopener noreferrer"&gt;dark theme&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;What this figure shows is that &lt;strong&gt;ONTAP is a data platform independent of the choice of hypervisor or cloud&lt;/strong&gt;. Whether you move the compute layer from VMware to EC2 or to Nutanix, the data layer stays consistently available, and SnapMirror moves and protects the data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where This Series Sits
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzntir72a71z6wvw6adx9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzntir72a71z6wvw6adx9.png" alt="From VMware ESXi as the current state, the diagram runs downwards through Phase 1 rehost, Phase 2 replatform and Phase 3 refactor. Phase 1 is Amazon EC2 reaching Amazon FSx for NetApp ONTAP over iSCSI multipath. Phase 2 holds Amazon Elastic Container Service, Amazon Elastic Kubernetes Service, AWS Batch, AWS Parallel Computing Service, AWS Deadline Cloud, Amazon CloudFront, AWS Transfer Family, S3 Access Points and Amazon FSx for NetApp ONTAP. Phase 3 holds AWS Fargate, AWS Lambda, Amazon Simple Storage Service, Amazon DynamoDB, S3 Access Points and Amazon FSx for NetApp ONTAP. A separate box above lists the alternatives to rehosting: Amazon Elastic VMware Service, Nutanix Cloud Clusters on AWS and Red Hat OpenShift Service on AWS" width="800" height="1149"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 3: the phases beyond rehosting, and the alternatives to it. This series covers Phase 1 (&lt;a href="https://raw.githubusercontent.com/Yoshiki0705/VMware-Migration-EC2-ONTAP/471435e0e879bc08e3c844cc5b1ada122c7d2396/docs/_assets/images/png/atx-fsxn-migration-journey-en-dark%402x.png" rel="noopener noreferrer"&gt;dark theme&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This series concentrates on &lt;strong&gt;Phase 1 (rehost)&lt;/strong&gt;, but the EC2 + FSx for ONTAP configuration is designed not to close off the path to Phase 2 and beyond. FSx for ONTAP is reachable over NFS/iSCSI from ECS/EKS as well as from EC2, so the data layer can stay as it is after containerization.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "EC2 + FSx for ONTAP"
&lt;/h2&gt;

&lt;p&gt;Among the five pathways, here is why this series looks at "EC2 + FSx for ONTAP" in particular.&lt;/p&gt;

&lt;h3&gt;
  
  
  An Entry Point for Rehosting and a Foundation for Modernization
&lt;/h3&gt;

&lt;p&gt;This configuration is not just a rehost destination; it is designed not to close off future modernization.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Now (Phase 1)&lt;/strong&gt;: Rehost VMs to EC2. Data disks on FSx for ONTAP iSCSI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Next (Phase 2)&lt;/strong&gt;: Containerize the application. Keep accessing FSx for ONTAP over NFS/iSCSI from ECS/EKS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Later (Phase 3)&lt;/strong&gt;: Parts that became stateless move to Fargate/Lambda. The FSx for ONTAP data layer stays as it is&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The multi-protocol access of FSx for ONTAP (NFS/SMB/iSCSI) is what supports this step-by-step migration. A volume used as an iSCSI LUN for EC2 can later be NFS-mounted by an EKS Pod.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Value of ONTAP Is Capability, Not Capacity
&lt;/h3&gt;

&lt;p&gt;Seen as just "large-capacity storage," FSx for ONTAP can look expensive next to EBS. But capacity is not where the value of ONTAP lies.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ONTAP feature&lt;/th&gt;
&lt;th&gt;Use on AWS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Snapshot&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Point-in-time copies in seconds. Instant test environments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;FlexClone&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Clones without copying data. Lower cost for dev/test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SnapMirror&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Block-level replication. Cross-Region DR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Compression / Dedup&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Lower effective capacity. Most effective for databases and logs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Thin Provisioning&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Pay for what is used. Avoid over-provisioning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Multi-protocol&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Access the same volume over NFS/SMB/iSCSI&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  FSx for ONTAP EC2 Integration Pattern
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnul5t8wk9s0zcdscj42b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnul5t8wk9s0zcdscj42b.png" alt="A table-shaped diagram pairing three Amazon EC2 instances with their boot / root volume and their data volume. The rows are, from the top, source, staging and after cutover. The columns are the boot / root volume and the data volume. The boot column is Amazon Elastic Block Store in all three rows. In the data column, only the source row is Amazon Elastic Block Store; staging and after cutover are Amazon FSx for NetApp ONTAP" width="800" height="793"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 4: the boot column is Amazon EBS in all three rows; only the data column changes to FSx for ONTAP from staging on (&lt;a href="https://raw.githubusercontent.com/Yoshiki0705/VMware-Migration-EC2-ONTAP/471435e0e879bc08e3c844cc5b1ada122c7d2396/docs/_assets/images/png/atx-fsxn-data-path-en-dark%402x.png" rel="noopener noreferrer"&gt;dark theme&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This configuration has the following advantages.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Storage and compute scale independently&lt;/strong&gt;: Change FSx for ONTAP capacity/throughput without stopping EC2&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The ONTAP operations model carries over&lt;/strong&gt;: Operate snapshots, clones and replication with the same CLI/API as on premises&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The limit moves&lt;/strong&gt;: The ceiling is no longer the per-instance Amazon EBS limit but the EC2 network bandwidth and the FSx for ONTAP throughput capacity.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Note: Performance Ceilings and Protocols
&lt;/h4&gt;

&lt;p&gt;"The limit moves" above does not mean the limit goes away. This series has not measured it yet, but verification in a sibling project (&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/a97a4ec/docs/ja/verification/perf-matrix-results.md#f-1-iscsi-%E3%81%AE%E5%AE%9F%E6%B8%AC" rel="noopener noreferrer"&gt;F-1 iSCSI measurement&lt;/a&gt; and others) has shown the following.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Simple arithmetic between session count and bandwidth misses&lt;/strong&gt;
Estimating "required bandwidth ÷ 625 MBps = number of sessions" misses in both directions. One TCP connection came out at 1.82x the prediction and sixteen at 0.20x, so the ceiling is not set by the capacity of a single flow alone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The performance gap between protocols (iSCSI vs NVMe/TCP) depends on the deployment&lt;/strong&gt;
Measured on the same file system, iSCSI and NVMe/TCP were 0.06% apart — practically the same — while on other configurations their order swapped. Quoting a past ratio as the basis for a protocol choice is unsafe.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ANA multipath is not available on Amazon Linux 2023&lt;/strong&gt;
&lt;code&gt;CONFIG_NVME_MULTIPATH&lt;/code&gt; is unset in all three AL2023 kernel series (&lt;a href="https://github.com/Yoshiki0705/S3-Burst-on-ONTAP-Files/blob/a248d47/docs/ja/verification/perf-matrix-results.md#ana-%E3%83%9E%E3%83%AB%E3%83%81%E3%83%91%E3%82%B9%E3%81%8C-amazon-linux-2023-%E3%81%A7%E4%BD%BF%E3%81%88%E3%81%AA%E3%81%84%E3%81%93%E3%81%A8" rel="noopener noreferrer"&gt;details&lt;/a&gt;). The ANA path makes a large difference to sequential reads, so verifying it enabled needs a different distribution (RHEL 9.3 or similar).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;These values come from tests against a raw device and include burst; they do not apply directly to this configuration, which puts a file system on the LUN.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing a Migration Tool
&lt;/h2&gt;

&lt;p&gt;Even when the destination is "EC2 + FSx for ONTAP," the tool depends on the environment.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Condition&lt;/th&gt;
&lt;th&gt;Candidate tool&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;You want the data volumes on FSx for ONTAP (whatever the source storage is)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;AWS Transform (MGN)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Set FSx for ONTAP as the target storage type. Generally available since 30 August 2026. Boot is always Amazon EBS. Verified hands-on in Part 2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Already on an ONTAP NFS datastore + small to medium scale&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;NetApp Shift Toolkit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Converts disks without copying them, using FlexClone. EC2 / FSx for ONTAP support is Early Preview. Verified hands-on in Part 4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Already on ONTAP + large scale (100+ VMs)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Cirrus Migrate Cloud&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;YAML-driven automation. Paid, through AWS Marketplace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Migration planning and sizing only&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;AWS Application Discovery Service&lt;/strong&gt; / &lt;strong&gt;AWS Migration Evaluator&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Inventory collection and cost estimation. AWS native&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Where NetApp Shift Toolkit Fits
&lt;/h3&gt;

&lt;p&gt;Shift Toolkit converts VMs on an ONTAP NFS datastore without copying their disks. Conversion is done with FlexClone and metadata operations, so the conversion itself is quick.&lt;br&gt;
Migrations from VMware ESXi to Hyper-V, OpenShift Virtualization, Proxmox VE and OLVM are GA today, and &lt;strong&gt;support for VMware ESXi → Amazon EC2 / FSx for ONTAP is at the Early Preview stage&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ Early Preview note: The current scope is configurations that place data disks on FSx for ONTAP. Specifications and constraints may change.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What This Series Covers
&lt;/h2&gt;

&lt;p&gt;Starting from migration as the entry point, the series runs four parts through to the modernization beyond it. Parts 2 and 4 are the two migration paths; Part 3 is the modernization that follows.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;Theme&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;Source storage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Part 2&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/aws-builders/aws-transform-now-supports-block-storage-migration-to-fsx-for-ontap-benefits-and-pitfalls-from-a-1hhe"&gt;AWS Transform Now Supports Block Storage Migration to FSx for ONTAP — Benefits and Pitfalls from a Hands-On EC2 Test&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;GA (30 August 2026) / verified hands-on&lt;/td&gt;
&lt;td&gt;Any&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Part 3&lt;/td&gt;
&lt;td&gt;Beyond the AWS migration of a VMware environment: serverless, cloud-native, and the data foundation (ECS / EKS, S3 Access Points, DR)&lt;/td&gt;
&lt;td&gt;Upcoming&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Part 4&lt;/td&gt;
&lt;td&gt;NetApp Shift Toolkit&lt;/td&gt;
&lt;td&gt;EC2 / FSx for ONTAP support is Early Preview / verified hands-on&lt;/td&gt;
&lt;td&gt;An ONTAP NFS datastore&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Of the two migration paths, AWS Transform is already GA and applies more widely, so the one a reader can try today (Part 2) comes first. Shift Toolkit (Part 4), which builds on existing ONTAP assets, comes later because its EC2 support is Early Preview. Part 3 sits between them and covers how to provide the data foundation when migrated workloads are reshaped into containers and serverless.&lt;/p&gt;

&lt;p&gt;Across the parts, we plan to cover:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Environment setup&lt;/strong&gt;: Design and build of VPC + FSx for ONTAP + VPN&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migration&lt;/strong&gt;: Migrating Linux / Windows VMs to EC2&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modernization&lt;/strong&gt;: Containerization on ECS / EKS, Fargate with S3 Access Points, DR and cyber resiliency&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operational continuity&lt;/strong&gt;: Snapshot / Clone / SnapMirror behavior after migration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost comparison&lt;/strong&gt;: TCO against an EBS-only configuration&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Migrating from VMware is not only about "where to go" but about "how to carry the storage operations model forward".&lt;/li&gt;
&lt;li&gt;For ONTAP users, FSx for ONTAP is a strong option for keeping, and making full use of, the value of ONTAP on AWS.&lt;/li&gt;
&lt;li&gt;Choose the tool by source storage and scale. If the source storage does not matter, AWS Transform (MGN); for ONTAP NFS datastores at small to medium scale, Shift Toolkit.&lt;/li&gt;
&lt;li&gt;Shift Toolkit's EC2 support is at the Early Preview stage, so those verification results may change at GA.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://dev.to/aws-builders/aws-transform-now-supports-block-storage-migration-to-fsx-for-ontap-benefits-and-pitfalls-from-a-1hhe"&gt;The next article (Part 2)&lt;/a&gt; covers the AWS Transform (MGN) path that sets FSx for ONTAP as the target storage, verified hands-on after GA. Part 3 covers the modernization beyond migration (serverless and cloud-native), and Part 4 explains how Shift Toolkit works — how FlexClone changes VM migration.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reference Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/Yoshiki0705/VMware-Migration-EC2-ONTAP" rel="noopener noreferrer"&gt;Verification repository (GitHub): VMware-Migration-EC2-ONTAP&lt;/a&gt; — procedures, configuration and figures for this series&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Yoshiki0705/FSx-for-ONTAP-Adoption-Playbook/blob/main/docs/en/reference/modernization-journey-map.md" rel="noopener noreferrer"&gt;Modernization journey map (FSx for ONTAP Adoption Playbook)&lt;/a&gt; — which material and implementation pattern to read at each stage beyond migration&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/vmware/explore/" rel="noopener noreferrer"&gt;AWS for VMware — Comprehensive Pathways&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/transform/vmware/" rel="noopener noreferrer"&gt;AWS Transform for VMware&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/09/aws-transform-fsx-netapp-ontap-support/" rel="noopener noreferrer"&gt;AWS What's New: AWS Transform announces general availability of Amazon FSx for NetApp ONTAP support&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/mgn/latest/ug/fsx-ontap.html" rel="noopener noreferrer"&gt;MGN: FSx for ONTAP configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.nutanix.com/blog/netapp-and-nutanix-announce-technical-partnership" rel="noopener noreferrer"&gt;Nutanix Blog: NetApp and Nutanix Announce Technical Partnership&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/vmware/partner-offerings/" rel="noopener noreferrer"&gt;AWS for VMware Partner Offerings (ROSA, NC2)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.netapp.com/us-en/netapp-solutions-virtualization/migration/shift-toolkit-overview.html" rel="noopener noreferrer"&gt;NetApp Shift Toolkit Overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/storage/seamless-migration-from-any-vmware-environment-to-amazon-fsx-for-netapp-ontap-and-amazon-ec2/" rel="noopener noreferrer"&gt;AWS Storage Blog: Seamless migration from VMware to FSx for ONTAP and EC2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/storage/expedite-vmware-migration-to-amazon-ec2-and-amazon-fsx-for-netapp-ontap-using-bluexp-workload-factory-for-aws-migration-advisor/" rel="noopener noreferrer"&gt;AWS Storage Blog: Expedite VMware migration to Amazon EC2 and Amazon FSx for NetApp ONTAP using the BlueXP workload factory migration advisor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/fsx/netapp-ontap/" rel="noopener noreferrer"&gt;Amazon FSx for NetApp ONTAP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/evs/" rel="noopener noreferrer"&gt;Amazon Elastic VMware Service (EVS)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/rosa/" rel="noopener noreferrer"&gt;Red Hat OpenShift Service on AWS (ROSA)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/apn/accelerate-vmware-migrations-to-aws-with-nutanix-nc2/" rel="noopener noreferrer"&gt;Nutanix Cloud Clusters on AWS (NC2)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/vmware/migrationaccelerator/" rel="noopener noreferrer"&gt;AWS VMware Migration Accelerator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.netapp.com/blog/simplify-vm-migration-shift-toolkit/" rel="noopener noreferrer"&gt;NetApp Blog: Simplify VM migration with Shift Toolkit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This article is Part 1 of the series "The Data Foundation for AWS Modernization." The Japanese original is on &lt;a href="https://hakobiya.hatenablog.com/entry/fsxn-vmware-migration-options-ec2" rel="noopener noreferrer"&gt;hatenablog&lt;/a&gt;. Shift Toolkit's EC2 support is Early Preview, and its specifications are subject to change.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>vmware</category>
      <category>netapp</category>
      <category>modernization</category>
    </item>
    <item>
      <title>What to build a file portal on — Nx Plugin for AWS 1.0, AWS Blocks and Amplify Gen 2, same four features (File Portal Part 7)</title>
      <dc:creator>Yoshiki Fujiwara(藤原 善基)@AWS Community Builder</dc:creator>
      <pubDate>Wed, 16 Sep 2026 14:34:33 +0000</pubDate>
      <link>https://dev.to/aws-builders/what-a-stack-deletion-leaves-behind-nx-plugin-for-aws-10-aws-blocks-and-amplify-gen-2-compared-10fg</link>
      <guid>https://dev.to/aws-builders/what-a-stack-deletion-leaves-behind-nx-plugin-for-aws-10-aws-blocks-and-amplify-gen-2-compared-10fg</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Nx Plugin for AWS reached 1.0 and AWS Blocks appeared in preview, so starting an application on AWS now has three front doors. I took all three to the same scope, counted the resources the same way, deployed them for real, implemented four features on each, and deleted everything at the end.&lt;/p&gt;

&lt;p&gt;This article stands on its own. It continues a series, but the only thing it assumes is that a file portal is running on an FSx for ONTAP S3 Access Point.&lt;/p&gt;

&lt;p&gt;What prompted it was reading &lt;a href="https://zenn.dev/aws_japan/articles/nx-plugin-for-aws-nx-explained" rel="noopener noreferrer"&gt;a Zenn article&lt;/a&gt; by an AWS SA that compared all three. Someone else's comparison is useful, but as a basis for a decision in my own repository it is thin: you have to deploy and delete before you can say you compared anything.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/aws-builders/adding-a-file-portal-to-fsx-for-ontap-s3-access-points-choosing-between-amplify-gen2-and-887"&gt;Part 1&lt;/a&gt; was about the application layer — what to build a file portal &lt;em&gt;with&lt;/em&gt;. This one is about &lt;strong&gt;the tooling underneath it&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;What the verification covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The resource count of each generated output, and the fixed cost its defaults imply&lt;/li&gt;
&lt;li&gt;Deploying each one for real and exercising it&lt;/li&gt;
&lt;li&gt;The differences that appear once the same four features are implemented on all three&lt;/li&gt;
&lt;li&gt;What survives a stack deletion, and the order in which to clean it up&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Out of scope: which tool is better, Nx or monorepos as concepts, and keeping generated code current over time.&lt;/p&gt;

&lt;p&gt;The conclusions first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Choosing a tool is a decision you can take back.&lt;/strong&gt; The data path is common to all three, and switching tools does not mean rebuilding the FSx for ONTAP side.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The difference is not "can it be built" but what gets fixed in place once you have chosen.&lt;/strong&gt; All three deployed without trouble.&lt;/li&gt;
&lt;li&gt;The generated defaults mean &lt;strong&gt;fixed monthly cost at zero requests.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;What a stack deletion leaves behind &lt;strong&gt;comes in two kinds&lt;/strong&gt;. &lt;code&gt;Retain&lt;/code&gt; and deletion protection are the generator's defaults and can be released on the generating side. Log groups that never appear in the template belong to CDK's internals, and there is no route to release those. The KMS waiting period is the only thing that cannot be avoided.&lt;/li&gt;
&lt;li&gt;For some resources, notably KMS keys, &lt;code&gt;DELETE_COMPLETE&lt;/code&gt; means "scheduled for deletion after a 30-day wait" rather than "gone".&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Switching tools does not touch the data foundation underneath
&lt;/h2&gt;

&lt;p&gt;The components side by side, matched to one scope: web, auth, API and a table. The data path below the S3 Access Point is this portal's real configuration and is identical in all three.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb6ye3ovtqvfi8ndmjbeo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb6ye3ovtqvfi8ndmjbeo.png" alt="Components of the file portal composed on Amplify Gen 2. From a web browser through AWS Amplify, AWS AppSync and AWS Lambda to an Amazon S3 Access Point, Amazon FSx for NetApp ONTAP and NFS / SMB clients. Amazon Cognito beside AWS Amplify, Amazon DynamoDB beside AWS Lambda. Zero AWS WAF web ACLs and zero customer managed KMS keys are created by default" width="800" height="1261"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 1: components of the file portal composed on Amplify Gen 2&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Amplify Gen 2 is what this portal actually runs on. No perimeter resources — AWS WAF, customer managed KMS keys — are created by default. Authentication is Amazon Cognito, on the default &lt;code&gt;ESSENTIALS&lt;/code&gt; tier.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo8ur6vib97ibttu831ez.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo8ur6vib97ibttu831ez.png" alt="Components of the file portal composed on the AWS Blocks production preset. From a web browser through Amazon CloudFront, Amazon API Gateway and 13 AWS Lambda functions to an Amazon S3 Access Point, Amazon FSx for NetApp ONTAP and NFS / SMB clients. There is no Amazon Cognito; two of the four Amazon DynamoDB tables carry authentication. One customer managed KMS key" width="800" height="1261"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 2: components of the file portal composed on the AWS Blocks production preset&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;What to read in the AWS Blocks figure is the absence of Amazon Cognito. Authentication is &lt;code&gt;AuthBasic&lt;/code&gt; (DynamoDB plus JWTs), so it moves out of a managed service and into the tables. Two of the four tables are for auth, and all four carry deletion protection.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgu31p67jnm49x0een3uq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgu31p67jnm49x0een3uq.png" alt="Components of the file portal composed on Nx Plugin for AWS. From a web browser through Amazon CloudFront, Amazon API Gateway (tRPC), AWS Lambda and Amazon DynamoDB to an Amazon S3 Access Point, Amazon FSx for NetApp ONTAP and NFS / SMB clients. Two AWS WAF web ACLs sit on the path (the CLOUDFRONT-scoped one in us-east-1, plus a REGIONAL one), Amazon Cognito is on PLUS with MFA required, and there are four AWS Key Management Service keys" width="800" height="1357"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 3: components of the file portal composed on Nx Plugin for AWS&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For Nx Plugin for AWS the difference is more perimeter in the figure: two AWS WAF web ACLs on the path (a third sits in a separate us-east-1 stack), Amazon Cognito on &lt;code&gt;PLUS&lt;/code&gt; with MFA required, and four KMS keys. Those three are the fixed-cost difference described later.&lt;/p&gt;

&lt;p&gt;That the three figures match below the access point matters: &lt;strong&gt;switching tools does not mean rebuilding the FSx for ONTAP side.&lt;/strong&gt; Nothing was created or deleted on the FSx for ONTAP side during this verification.&lt;/p&gt;

&lt;p&gt;Reaching it was also identical on all three. An Internet-origin access point is reached by passing the alias as a bucket name to the standard S3 SDK — no endpoint override, no VPC attachment. A file on the NAS can be found, opened and written from a browser with no VPN and no SMB mount, and through phase 3 the functions do not even need to sit in a VPC.&lt;/p&gt;


&lt;h2&gt;
  
  
  The same four features on all three tools (2026-09-14)
&lt;/h2&gt;

&lt;p&gt;Everything above compares the outputs as generated. What someone choosing a tool actually wants to know is the difference once their own application sits on it. So a few days later I implemented the same four features — sign in, list, read, upload (&lt;code&gt;listFiles&lt;/code&gt; / &lt;code&gt;readFile&lt;/code&gt; / &lt;code&gt;uploadFile&lt;/code&gt;) — on each and ran them against the same FSx for ONTAP S3 Access Point.&lt;/p&gt;

&lt;p&gt;The target was fixed too: the same prefix (13 objects), the same file (1,615 bytes, &lt;code&gt;text/markdown; charset=utf-8&lt;/code&gt;), the same 24-byte write. All three returned the same values.&lt;/p&gt;
&lt;h3&gt;
  
  
  The listing
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw16wvsiz7vadjhhc8ro7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw16wvsiz7vadjhhc8ro7.png" alt="The file listing on the AWS Blocks build. A prefix box holds reports/2026/05/10/, the count reads 13 objects, and thirteen compliance-report-&lt;uuid&gt;.md rows are listed with sizes and modification times" width="800" height="476"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The listing on the AWS Blocks build: thirteen files.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvyfdb6mt7d6swq98iei0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvyfdb6mt7d6swq98iei0.png" alt="The file listing on the Nx Plugin for AWS build. The generated shadcn sidebar is on the left, and the body shows the same prefix, the same 13 objects, and the same thirteen rows" width="800" height="476"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The listing on the Nx Plugin for AWS build, with the generated shadcn sidebar.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpqmfnktqewqigz68s73u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpqmfnktqewqigz68s73u.png" alt="The file listing on the Amplify Gen 2 portal. A breadcrumb reads / reports / 2026 / 05 / 10, and the same thirteen files appear alongside a .. entry for the parent, each row carrying a star and a document icon" width="800" height="476"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The listing on the Amplify Gen 2 portal. Being a portal in actual use, it carries extra features such as stars and download icons.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Reading
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0eg312g7aw8tv9d9dcqh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0eg312g7aw8tv9d9dcqh.png" alt="The read screen on the AWS Blocks build. Below a line reading 1615 bytes, text/markdown; charset=utf-8, the Japanese compliance report is rendered in the page" width="800" height="476"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz87n8ve5ugcevuzystq0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz87n8ve5ugcevuzystq0.png" alt="The read screen on the Nx build. The header reads portal-web; under compliance-report-&lt;uuid&gt;.md sit 1615 bytes and text/markdown; charset=utf-8, the same report is rendered in the page, and an Upload section follows below it" width="800" height="476"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The two above render the report in the page. The Amplify Gen 2 portal below does not: it hands &lt;code&gt;.md&lt;/code&gt; to an AI Assistant panel instead, because that is how I wrote it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy23gsibxsp4jjs2v7wu5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy23gsibxsp4jjs2v7wu5.png" alt="The same file opened on the Amplify Gen 2 portal. The contents are not rendered; an AI Assistant panel has opened on the right, naming the selected file and offering Analyze document" width="800" height="476"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An aside: a by-product of the capture pass was one accessibility defect in my own portal. The document icon on a file row announces itself as &lt;code&gt;aria-label="Download &amp;lt;filename&amp;gt;"&lt;/code&gt;, yet what it actually does is select the file for AI processing. The &lt;code&gt;title&lt;/code&gt; attribute mentions both, but a screen reader reads out only &lt;code&gt;Download&lt;/code&gt;, so &lt;strong&gt;someone using assistive technology is told the button downloads a file when it starts AI processing instead. It is unfixed as of writing, recorded as something for the portal side to correct.&lt;/strong&gt; Without lining up three builds in the same order, I probably would not have noticed.&lt;/p&gt;
&lt;h3&gt;
  
  
  Uploading
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp1wvp0f2c9lnhnw00f4x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp1wvp0f2c9lnhnw00f4x.png" alt="The upload screen on the AWS Blocks build. The heading reads File Portal — AWS Blocks; the prefix is portal-parity/blocks/ with 1 object, and the note-2026-09-14.txt that was just written is listed at 35 B. Below it are a filename field, a body field and an Upload button, with a note that writes cannot leave portal-parity/blocks/" width="800" height="476"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7cesuhml5dax436t67vz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7cesuhml5dax436t67vz.png" alt="The upload screen on the Nx build. The heading reads File Portal on Nx Plugin for AWS, subtitled Amazon FSx for NetApp ONTAP S3 Access Point, via tRPC procedures. The prefix is portal-parity/nx/ with 1 object, and the note-2026-09-14.txt that was just written is listed at 24 bytes" width="800" height="476"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The two above are listings right after a successful write. The Amplify screen below is a refusal: the same file name was sent twice and the server side declined to overwrite. That is a difference in what I implemented, not in the tools.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F43vn0l0mtpmj65c6z6l9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F43vn0l0mtpmj65c6z6l9.png" alt="The upload screen on the Amplify Gen 2 portal. The left sidebar lists All Files, Favorites, Recent, Upload, AI Processing, Job History, Snapshots, Lock, ARP/AI and Version Diff. In the upload queue, note-2026-09-14.txt is 24 B with a status of Overwrite prevented and 0% progress, and a warning below reads Overwrite prevented for all files." width="800" height="476"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  How the IAM permission differs by tool
&lt;/h3&gt;

&lt;p&gt;Reaching the S3 Access Point is the same on all three, but &lt;strong&gt;the IAM needs care&lt;/strong&gt;: all three require the access-point ARN form.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;arn&lt;/span&gt;:&lt;span class="n"&gt;aws&lt;/span&gt;:&lt;span class="n"&gt;s3&lt;/span&gt;:&amp;lt;&lt;span class="n"&gt;region&lt;/span&gt;&amp;gt;:&amp;lt;&lt;span class="n"&gt;account&lt;/span&gt;-&lt;span class="n"&gt;id&lt;/span&gt;&amp;gt;:&lt;span class="n"&gt;accesspoint&lt;/span&gt;/&amp;lt;&lt;span class="n"&gt;name&lt;/span&gt;&amp;gt;          &lt;span class="c"&gt;# ListBucket
&lt;/span&gt;&lt;span class="n"&gt;arn&lt;/span&gt;:&lt;span class="n"&gt;aws&lt;/span&gt;:&lt;span class="n"&gt;s3&lt;/span&gt;:&amp;lt;&lt;span class="n"&gt;region&lt;/span&gt;&amp;gt;:&amp;lt;&lt;span class="n"&gt;account&lt;/span&gt;-&lt;span class="n"&gt;id&lt;/span&gt;&amp;gt;:&lt;span class="n"&gt;accesspoint&lt;/span&gt;/&amp;lt;&lt;span class="n"&gt;name&lt;/span&gt;&amp;gt;/&lt;span class="n"&gt;object&lt;/span&gt;/* &lt;span class="c"&gt;# GetObject, PutObject
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Written in the bucket form (&lt;code&gt;arn:aws:s3:::&amp;lt;alias&amp;gt;&lt;/code&gt;), the deploy succeeds and list, get and put all return &lt;code&gt;AccessDenied&lt;/code&gt; at run time — a failure that is hard to read.&lt;/p&gt;

&lt;p&gt;AWS Blocks in particular has &lt;code&gt;FileBucket.fromExisting(alias)&lt;/code&gt;, which internally calls &lt;code&gt;s3.Bucket.fromBucketName(alias).grantReadWrite()&lt;/code&gt; and therefore generates the bucket form. Trust the block's grant and add nothing, and it passes locally and fails once deployed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which layer refuses a malformed input
&lt;/h3&gt;

&lt;p&gt;Trying an input that walks out of the prefix (&lt;code&gt;../escape.txt&lt;/code&gt;) showed that the refusing layer differs by tool.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Nx Plugin for AWS&lt;/th&gt;
&lt;th&gt;AWS Blocks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;../escape.txt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;HTTP 403 &lt;code&gt;{"message":"Forbidden"}&lt;/code&gt;&lt;/strong&gt; (blocked by WAF)&lt;/td&gt;
&lt;td&gt;The application's own domain error&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An empty file name&lt;/td&gt;
&lt;td&gt;HTTP 400 plus the application's error&lt;/td&gt;
&lt;td&gt;The application's own domain error&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On the Nx build, AWS WAF stopped the request before the Lambda. Defence in depth on by default is a good thing, but the refusal arrives as a generic HTTP 403, so &lt;strong&gt;the starting point of an investigation moves from your own code to the WAF metrics&lt;/strong&gt; — worth knowing before you are debugging at speed.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this round measured (deploy time and resource count)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Deploy time&lt;/th&gt;
&lt;th&gt;Resources&lt;/th&gt;
&lt;th&gt;Application Lambdas&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AWS Blocks (production preset)&lt;/td&gt;
&lt;td&gt;318 s&lt;/td&gt;
&lt;td&gt;82&lt;/td&gt;
&lt;td&gt;1 (the whole API)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nx Plugin for AWS (sandbox)&lt;/td&gt;
&lt;td&gt;307 s&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;5 (one per procedure)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Amplify Gen 2 is excluded from this round's figures (deploy time, resource count) because the comparison used the portal already in production rather than a fresh build.&lt;/p&gt;

&lt;p&gt;Worth noting: a green local e2e run on AWS Blocks says nothing about whether the access point is reachable, because the mocked storage closes the loop inside the mock.&lt;/p&gt;




&lt;h2&gt;
  
  
  Deploying the starters as generated (2026-09-13)
&lt;/h2&gt;

&lt;p&gt;Stopping at &lt;code&gt;synth&lt;/code&gt; leaves out everything you learn while deleting resources. So all three were deployed to a real AWS account in their generated state, before any features were added.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;th&gt;Resources&lt;/th&gt;
&lt;th&gt;Deploy&lt;/th&gt;
&lt;th&gt;Exercised&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AWS Blocks (sandbox preset)&lt;/td&gt;
&lt;td&gt;83&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;signUp and friends returned 200 over JSON-RPC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AWS Blocks (production preset)&lt;/td&gt;
&lt;td&gt;117&lt;/td&gt;
&lt;td&gt;1,228 s&lt;/td&gt;
&lt;td&gt;all five operations 200, CloudFront delivery included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nx Plugin for AWS&lt;/td&gt;
&lt;td&gt;86&lt;/td&gt;
&lt;td&gt;317 s&lt;/td&gt;
&lt;td&gt;TOTP enrolled, then 200 from the API via the identity pool&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All three deployed without trouble. The Blocks production preset takes longer because DynamoDB builds its GSIs one at a time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Fixed cost that accrues at zero requests
&lt;/h2&gt;

&lt;p&gt;A generator's defaults reach the monthly bill even when the template says nothing about them.&lt;br&gt;
&lt;strong&gt;The figure is settled at the moment the tooling is chosen&lt;/strong&gt;, which makes it a number to read&lt;br&gt;
before starting rather than after.&lt;/p&gt;

&lt;p&gt;With not a single request served, the floor for each tool looks like this (FSx for ONTAP's own cost is not included).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;th&gt;WAF&lt;/th&gt;
&lt;th&gt;KMS CMK&lt;/th&gt;
&lt;th&gt;Monthly floor&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Nx Plugin for AWS&lt;/td&gt;
&lt;td&gt;3 ACLs + 6 rules = $21&lt;/td&gt;
&lt;td&gt;4 keys, $4&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;from $25&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AWS Blocks (production preset)&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;1 key, $1&lt;/td&gt;
&lt;td&gt;$1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amplify Gen 2 (starter)&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Nx starts higher because what production needs is present from the start. It does bite when something is stood up for a test and left running.&lt;/p&gt;


&lt;h2&gt;
  
  
  What a production portal still needs
&lt;/h2&gt;

&lt;p&gt;The feature set beyond these four, arranged in phases. Which phase your requirements stop at also changes how you choose a tool.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;VPC attachment&lt;/th&gt;
&lt;th&gt;Newly required&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Paging, search, sorting&lt;/td&gt;
&lt;td&gt;not needed&lt;/td&gt;
&lt;td&gt;nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Downloads and share links&lt;/td&gt;
&lt;td&gt;not needed&lt;/td&gt;
&lt;td&gt;presigned URLs and an expiry policy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Large file uploads&lt;/td&gt;
&lt;td&gt;not needed&lt;/td&gt;
&lt;td&gt;multipart, or similar&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Showing ONTAP information&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;needed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;reaching the management LIF, splitting the functions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Operating ONTAP&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;needed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;audit logs, approval for irreversible operations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;AI processing&lt;/td&gt;
&lt;td&gt;depends&lt;/td&gt;
&lt;td&gt;confidence thresholds, data classification&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;From phase 4 the ONTAP management LIF has to be reachable, which is where &lt;strong&gt;the decision to put a Lambda in a VPC&lt;/strong&gt; arrives, and with it fixed cost such as a NAT gateway. If phase 5 or later is a possibility, splitting the functions into "touches the S3 Access Point" and "touches ONTAP" from the start is the design that pays off.&lt;/p&gt;


&lt;h2&gt;
  
  
  Tearing down the evaluation — what a stack deletion leaves
&lt;/h2&gt;

&lt;p&gt;This is about putting the evaluation environment back, not about running the portal. A portal that&lt;br&gt;
gets built runs for years; the deletion happens while you are still trying things out. Even so, how&lt;br&gt;
much is left behind is worth knowing before you start.&lt;/p&gt;

&lt;p&gt;Listing what remained after each stack deletion turned up resources that static analysis of the template (&lt;code&gt;synth&lt;/code&gt;) could not show.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;th&gt;Left after the stack deletion&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Blocks (sandbox preset)&lt;/td&gt;
&lt;td&gt;5 log groups&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blocks (production preset)&lt;/td&gt;
&lt;td&gt;4 DynamoDB tables (deletion protection), 8 log groups, 1 KMS key (waiting)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nx Plugin for AWS&lt;/td&gt;
&lt;td&gt;1 Cognito user pool, 1 DynamoDB table, 4 KMS keys, 2 IAM roles, 7 log groups&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Those eight and nine are the generator's defaults, not an AWS constraint.&lt;/strong&gt; The generated code&lt;br&gt;
is what applies &lt;code&gt;Retain&lt;/code&gt; and deletion protection, so anything that may go can be released on the&lt;br&gt;
generating side (&lt;code&gt;RemovalPolicy.DESTROY&lt;/code&gt; in CDK; the deletion-protection properties on DynamoDB&lt;br&gt;
and Cognito set to false). Defaults leaning towards keeping data is a production-shaped choice;&lt;br&gt;
it conflicts with intent only when the deployment is a test.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Log groups are the awkward ones.&lt;/strong&gt; The Lambda functions behind CDK and tooling custom resources create them on first execution with the default retention (never expire), which puts them outside CloudFormation's control and leaves them behind. One sweep at the end of the teardown catches them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws logs describe-log-groups &lt;span class="nt"&gt;--log-group-name-prefix&lt;/span&gt; &lt;span class="s2"&gt;"/aws/lambda/&amp;lt;stack-name&amp;gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s2"&gt;"logGroups[].[logGroupName,retentionInDays]"&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The removal order matters too. Nx's user pool cannot be deleted until deletion protection is off, and the SMS configuration needed to switch it off references one of the two IAM roles listed above as survivors. &lt;strong&gt;Delete those IAM roles first, while clearing up what the stack left behind, and the route to switching off deletion protection is gone.&lt;/strong&gt; Delete the user pool first, then the roles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your own functions are a different case from CDK's.&lt;/strong&gt; A Lambda you write can declare its log&lt;br&gt;
group in the template, so it does not survive — measured, the declared ones went away on all&lt;br&gt;
three. What survives belongs to CDK's internal custom resources, and there is no route to declare&lt;br&gt;
those from your own code: the &lt;code&gt;CustomResourceProvider&lt;/code&gt; base class declares an IAM role and a&lt;br&gt;
Lambda function and no log group (&lt;a href="https://github.com/aws/aws-cdk/blob/main/packages/aws-cdk-lib/core/lib/custom-resource-provider/custom-resource-provider-base.ts" rel="noopener noreferrer"&gt;custom-resource-provider-base.ts&lt;/a&gt;,&lt;br&gt;
read in full on &lt;code&gt;main&lt;/code&gt;, 2026-09-16).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No released version fixes this&lt;/strong&gt; (checked 2026-09-16). Two feature requests are open upstream&lt;br&gt;
(&lt;a href="https://github.com/aws/aws-cdk/issues/26553" rel="noopener noreferrer"&gt;aws-cdk #26553&lt;/a&gt;,&lt;br&gt;
&lt;a href="https://github.com/aws/aws-cdk/issues/23909" rel="noopener noreferrer"&gt;aws-cdk #23909&lt;/a&gt;). A third,&lt;br&gt;
&lt;a href="https://github.com/aws/aws-cdk/issues/24815" rel="noopener noreferrer"&gt;aws-cdk #24815&lt;/a&gt;, is closed as completed — but&lt;br&gt;
&lt;strong&gt;its closing comment, from an AWS maintainer, says the fixing PR was reverted, that the problem&lt;br&gt;
is general to all custom resources, and that tracking moved to #23909. Closed is not shipped.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AWS Support has not been asked. Lambda documents the implicit creation and the never-expire&lt;br&gt;
default, the CDK source shows what the provider declares, and &lt;strong&gt;CDK's primary channel is its&lt;br&gt;
GitHub repository rather than AWS Support.&lt;/strong&gt; What would be worth filing is the per-output residue&lt;br&gt;
count measured here, which appears in neither issue.&lt;/p&gt;




&lt;h2&gt;
  
  
  Two behaviours that are easy to misread in operation
&lt;/h2&gt;

&lt;p&gt;Both are documented, and both are still easy to read the wrong way.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trap 1: a deployment mode cannot be dropped later
&lt;/h3&gt;

&lt;p&gt;Nx generates &lt;code&gt;deploy-sandbox&lt;/code&gt; as &lt;code&gt;cdk deploy ... --express&lt;/code&gt; under the hood. If a deploy breaks, "drop &lt;code&gt;--express&lt;/code&gt; and run it again" does not work: &lt;strong&gt;a stack last updated in express mode requires its later operations to be express too.&lt;/strong&gt; The remedy is to pair &lt;code&gt;--express --rollback&lt;/code&gt; from the start.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trap 2: DELETE_COMPLETE does not always mean gone
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;DELETE_COMPLETE&lt;/code&gt; in CloudFormation does not always mean the resource is gone. For a KMS key it means deletion was scheduled — a 30-day wait by default — and that wait cannot be shortened afterwards. Confirm a teardown from the listed state rather than from the status.&lt;/p&gt;




&lt;h2&gt;
  
  
  Choosing between them
&lt;/h2&gt;

&lt;p&gt;The three are not mutually exclusive; they suit different purposes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nx Plugin for AWS&lt;/strong&gt;: production-leaning defaults and dependency management that holds up as projects multiply. Fixed cost is the highest of the three.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Blocks&lt;/strong&gt;: running the whole application locally with no AWS account (it is in preview, so treat a Block ID as immutable after deployment).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amplify Gen 2&lt;/strong&gt;: treating the backend definition, the sandbox and hosting as one thing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Part 1's conclusion stands unchanged — Nextcloud when file sharing is the centre, Amplify Gen 2 when AI processing and ONTAP operations are. What this part settled is which tool the "build" side sits on.&lt;/p&gt;




&lt;h2&gt;
  
  
  Test environment
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Measured&lt;/td&gt;
&lt;td&gt;2026-09-13 (starters, live), 2026-09-14 (live, with the four features implemented)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Region&lt;/td&gt;
&lt;td&gt;ap-northeast-1 (only Nx's CloudFront web ACL in us-east-1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Outputs&lt;/td&gt;
&lt;td&gt;Nx Plugin for AWS 1.0 (&lt;code&gt;@aws/nx-plugin@1.0.0&lt;/code&gt;), AWS Blocks (preview), Amplify Gen 2 (&lt;code&gt;npm create amplify&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Local&lt;/td&gt;
&lt;td&gt;Node.js v26.4.0, npm 11.17.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pricing&lt;/td&gt;
&lt;td&gt;AWS Price List API, ap-northeast-1, 2026-09-07&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Teardown&lt;/td&gt;
&lt;td&gt;everything deleted; only 5 KMS keys remain in their waiting period (&lt;code&gt;PendingDeletion&lt;/code&gt; is not billed)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The record in the repository
&lt;/h2&gt;

&lt;p&gt;The numbers, the sources and the teardown procedure live in the repository, in more detail than this article.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/Yoshiki0705/FSx-for-ONTAP-S3AccessPoints-Serverless-Patterns/blob/main/docs/en/scaffolding-and-backend-toolkit-choices.md" rel="noopener noreferrer"&gt;Choosing an application foundation&lt;/a&gt; — where the three sit, measurements up to synth, fixed cost, reproduction steps&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Yoshiki0705/FSx-for-ONTAP-S3AccessPoints-Serverless-Patterns/blob/main/docs/en/scaffolding-deploy-verification.md" rel="noopener noreferrer"&gt;Deploy verification and teardown&lt;/a&gt; — the 15-entry pitfall register with a source on every entry, the teardown order, and the live record&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Yoshiki0705/FSx-for-ONTAP-S3AccessPoints-Serverless-Patterns/blob/main/docs/en/portal-parity-four-features.md" rel="noopener noreferrer"&gt;The same four features on all three&lt;/a&gt; — twelve captures across three builds by four screens, how the permission differs, which layer refuses, 9 more pitfalls, reproduction steps&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Yoshiki0705/FSx-for-ONTAP-S3AccessPoints-Serverless-Patterns/blob/main/docs/en/portal-parity-next-steps.md" rel="noopener noreferrer"&gt;Beyond the four features&lt;/a&gt; — what each phase assumes, the nature of the cost, which phases can be skipped&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Yoshiki0705/FSx-for-ONTAP-S3AccessPoints-Serverless-Patterns/blob/main/docs/index.md" rel="noopener noreferrer"&gt;Documentation index&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;Everything in this part is about defaults. Choosing a tool fixes your monthly floor and your teardown procedure, and neither is written in the template.&lt;/p&gt;

&lt;p&gt;What worked as a method was &lt;strong&gt;writing the same four features three times&lt;/strong&gt;. The difference shows up not as how much code you write but as where you write it, and which layer refuses a bad request. Choosing a tool is, I think, choosing where you look first when something breaks.&lt;/p&gt;

&lt;p&gt;I hope this record helps anyone weighing the same choice.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Everything in the test environment has been deleted (only KMS keys remain in their waiting period). The figures come from one specific environment and configuration and will differ with workload and setup. Prices are the published ap-northeast-1 rates as of 2026-09-07.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cdk</category>
      <category>nx</category>
      <category>amplify</category>
    </item>
    <item>
      <title>How far ONTAP features reach through an S3 Access Point — measuring qtree, quota and FlexClone (File Portal Part 6)</title>
      <dc:creator>Yoshiki Fujiwara(藤原 善基)@AWS Community Builder</dc:creator>
      <pubDate>Fri, 04 Sep 2026 00:59:30 +0000</pubDate>
      <link>https://dev.to/aws-builders/what-i-left-off-the-file-portal-on-aws-how-far-fsx-for-ontap-features-reach-and-the-work-handed-hk8</link>
      <guid>https://dev.to/aws-builders/what-i-left-off-the-file-portal-on-aws-how-far-fsx-for-ontap-features-reach-and-the-work-handed-hk8</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;This is the sixth part of the series, and &lt;a href="https://dev.to/aws-builders/what-a-stack-deletion-leaves-behind-nx-plugin-for-aws-10-aws-blocks-and-amplify-gen-2-compared-10fg"&gt;Part 7&lt;/a&gt; continues it. &lt;a href="https://dev.to/aws-builders/adding-a-file-portal-to-fsx-for-ontap-s3-access-points-choosing-between-amplify-gen2-and-887"&gt;Part 1&lt;/a&gt; built a file portal on Amazon FSx for NetApp ONTAP (FSx for ONTAP) S3 access points, &lt;a href="https://dev.to/aws-builders/embedding-storage-operations-into-a-file-portal-from-arpai-incident-response-to-regulatory-1oih"&gt;Part 2&lt;/a&gt; added ransomware response and WORM retention, &lt;a href="https://dev.to/aws-builders/embedding-ai-agents-into-a-file-portal-from-agentcore-mcp-to-multi-agent-teams-part-3-19m1"&gt;Part 3&lt;/a&gt; put AI agents on it, Part 4 delegated 182 storage operations, and Part 5 collected what the running cluster refused.&lt;/p&gt;

&lt;p&gt;This part is &lt;strong&gt;what was left off the screen&lt;/strong&gt;. That is the difference from last time: where Parts 4 and 5 were about operations you run from a screen, this one has two subjects. How much of ONTAP stays usable without going through the portal at all, and the operational work that was handed to a schedule instead of a button.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What this part does not cover&lt;/strong&gt;: performance figures (this measures whether things are possible and under what condition, not throughput), running SnapLock or Object Lock (irreversible, so not enabled without an instruction naming the retention), and actual tiering into a capacity pool (needs a different aggregate configuration, which was not on this path).&lt;/p&gt;




&lt;h2&gt;
  
  
  The reach of ONTAP features on this path
&lt;/h2&gt;

&lt;p&gt;When you read and write files over the S3 API through an FSx for ONTAP S3 access point, how much of ONTAP stays usable alongside it? You want qtrees to carve up directories, quotas to stop a tenant running away with the space, FlexClone to spin a verification copy. Does any of that sound like your environment?&lt;/p&gt;

&lt;p&gt;Start looking into it and NetApp's documentation gives you pause. &lt;a href="https://docs.netapp.com/us-en/ontap/s3-config/ontap-s3-interoperability-concept.html" rel="noopener noreferrer"&gt;ONTAP S3 interoperability&lt;/a&gt; records qtrees, quotas, FlexClone, and a volume clone of a FlexGroup containing ONTAP S3 buckets as &lt;strong&gt;not supported&lt;/strong&gt; — for the &lt;strong&gt;ONTAP S3 server&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That is where it stalls. The FSx for ONTAP S3 access point is an AWS mechanism, and what it attaches to is a &lt;strong&gt;volume&lt;/strong&gt;, not a bucket. Attaching one does stand up an ONTAP S3 server on the SVM, and I/O through the access point goes through ONTAP's S3 protocol stack. So the two are not unrelated. Does the same restriction appear?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I measured it. Up front: everything I measured worked.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ONTAP feature&lt;/th&gt;
&lt;th&gt;Result on the S3 access point path&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Qtree&lt;/td&gt;
&lt;td&gt;Can be created. Appears as an S3 prefix, and an object PUT into it lands inside the qtree&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quota&lt;/td&gt;
&lt;td&gt;A tree quota refuses an S3 PUT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FlexClone (volume granularity)&lt;/td&gt;
&lt;td&gt;The clone can be made, and &lt;strong&gt;the clone itself can take an S3 access point&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FlexClone (file granularity)&lt;/td&gt;
&lt;td&gt;A file written over S3 can be cloned, and &lt;strong&gt;the clone is visible as an object through the access point&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FlexGroup volume&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;An S3 access point can be attached.&lt;/strong&gt; PUT / GET / LIST and multipart all work&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clone of a FlexGroup&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;The clone can be created&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The four items that table lists did not appear as restrictions on this path.&lt;/strong&gt; Reading that table as "not supported there, therefore not supported here" is wrong, and so is the reverse: a "supported" row is not evidence about this path either. &lt;strong&gt;The subjects differ, so nothing is known until it is measured.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What actually tripped me up was not in the table at all. Three things.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An &lt;strong&gt;NTFS volume with a UNIX identity on an SVM with no CIFS server&lt;/strong&gt; attaches, reaches &lt;code&gt;AVAILABLE&lt;/code&gt;, and then refuses every data operation with &lt;code&gt;AccessDenied&lt;/code&gt;. &lt;strong&gt;&lt;code&gt;AVAILABLE&lt;/code&gt; says nothing about the health of the file system layer&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;A volume created on the ONTAP side takes &lt;strong&gt;ten to twenty minutes&lt;/strong&gt; to appear in the AWS API. The documentation says "several minutes"&lt;/li&gt;
&lt;li&gt;A volume that has &lt;strong&gt;once carried an S3 access point cannot be deleted from the ONTAP side&lt;/strong&gt;, even after every access point is detached. Only &lt;code&gt;aws fsx delete-volume&lt;/code&gt; removes it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Below is the walkthrough, and how the controls were set up.&lt;/p&gt;




&lt;h2&gt;
  
  
  Test environment
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Date&lt;/td&gt;
&lt;td&gt;2026-08-26 (UTC)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Region&lt;/td&gt;
&lt;td&gt;ap-northeast-1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ONTAP version&lt;/td&gt;
&lt;td&gt;9.18.1P3D1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment type / throughput&lt;/td&gt;
&lt;td&gt;SINGLE_AZ_1 / 128 MBps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test volumes&lt;/td&gt;
&lt;td&gt;UNIX security style 1 GiB × 2 (one with an access point, one as the control), NTFS 1 GiB × 1, FlexGroup 400 GiB × 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Access point identity&lt;/td&gt;
&lt;td&gt;UNIX &lt;code&gt;root&lt;/code&gt;, internet origin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cleanup&lt;/td&gt;
&lt;td&gt;Throwaway volumes only. All deleted&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A note on the identity&lt;/strong&gt;: UNIX &lt;code&gt;root&lt;/code&gt; was used to keep the measurement simple. Every request through an access point is authorized as that one file system identity, so specifying &lt;code&gt;root&lt;/code&gt; removes any narrowing by file permissions. &lt;strong&gt;This is not a recommended configuration.&lt;/strong&gt; Use a dedicated user with only the permissions the writes need, and split access points per purpose (&lt;code&gt;FileSystemIdentity&lt;/code&gt; cannot be changed after creation).&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The distinction that matters most — different subjects
&lt;/h2&gt;

&lt;p&gt;The results below have to be read with these two kept apart.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mechanism&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;th&gt;Whose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ONTAP S3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The S3 server with which ONTAP serves buckets. This is what NetApp's table is about&lt;/td&gt;
&lt;td&gt;NetApp&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;FSx for ONTAP S3 access point&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;An AWS access point attached to an FSx for ONTAP &lt;strong&gt;volume&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;AWS&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;They are not unrelated. Attaching one stands up an ONTAP S3 server on the SVM, and the I/O goes through ONTAP's S3 protocol stack (measured). &lt;strong&gt;That is exactly why NetApp's table is the right place to start.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Being the right place to start is not the same as being the conclusion. The target is a volume rather than a bucket, and the namespace and the authorization path both differ. &lt;strong&gt;"Not supported there, therefore not supported here" turns something unchecked into something checked.&lt;/strong&gt; The reverse reading is the same error.&lt;/p&gt;




&lt;h2&gt;
  
  
  The walkthrough
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Qtree — usable
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Create a qtree in the volume with an access point&lt;/td&gt;
&lt;td&gt;Succeeded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Control: the same qtree in the volume with no access point&lt;/td&gt;
&lt;td&gt;Succeeded. &lt;strong&gt;No difference&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;list-objects-v2 --delimiter /&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The qtree appears in CommonPrefixes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PUT into the qtree prefix&lt;/td&gt;
&lt;td&gt;Succeeded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inspect the qtree from ONTAP&lt;/td&gt;
&lt;td&gt;The written file is inside the qtree directory&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;NetApp's "Qtrees: not supported" &lt;strong&gt;does not manifest as a refusal on this path.&lt;/strong&gt; The qtree is created, it is visible as a prefix in the S3 namespace, and an object written into that prefix lands inside it. Together with the quota result below, it behaves as a qtree.&lt;/p&gt;

&lt;p&gt;Incidentally, a volume with an access point attached carries an internal &lt;code&gt;____NTAP_S3_MAPPING&lt;/code&gt; directory at its root. It is visible over NFS and SMB, so anyone who inspects a collect-layer volume by hand will see it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quota — enforced against an S3 PUT
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Set a tree quota on the qtree (space 1 MiB, files 10) and enable quotas on the volume&lt;/td&gt;
&lt;td&gt;Succeeded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;quota report&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The rule is active, and &lt;strong&gt;the file written over S3 is counted in files used&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PUT 15 small objects&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;8 succeeded, 7 refused.&lt;/strong&gt; files used stopped at exactly 10/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What the S3 client received on refusal&lt;/td&gt;
&lt;td&gt;HTTP 507 &lt;code&gt;InsufficientCapacity&lt;/code&gt; / &lt;code&gt;Maximum storage capacity of file system has been reached.&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Control: raise the files limit 10 → 50 and re-PUT the refused key with the same body&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Succeeded&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The control carries the conclusion. Raising the limit alone made the identical PUT succeed, so the refusal was the quota, not a capacity shortage and not a permissions artefact.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That the response mis-describes its cause matters to the design.&lt;/strong&gt; The file system was not full; a qtree file-count quota was reached. An operator who sees a 507 and "maximum storage capacity of file system" will consider growing the file system. If you use quotas on this path, &lt;strong&gt;write down what that response actually means in the runbook.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  FlexClone — both volume and file granularity
&lt;/h3&gt;

&lt;p&gt;There are two granularities, and they behave differently.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;File granularity&lt;/th&gt;
&lt;th&gt;Volume granularity&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Where it lands&lt;/td&gt;
&lt;td&gt;The same volume. It appears as a new key on the same access point&lt;/td&gt;
&lt;td&gt;A separate volume&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Authorization boundary&lt;/td&gt;
&lt;td&gt;Same as the parent&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Its own access point, its own policy, its own identity&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time before it is usable&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;599 to 1,177 s measured, for a volume created through the ONTAP API to appear on the AWS side&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Visible over S3&lt;/td&gt;
&lt;td&gt;Yes. &lt;code&gt;StorageClass=FSX_ONTAP&lt;/code&gt;, sha256 matches the source&lt;/td&gt;
&lt;td&gt;Yes. LIST / GET / PUT through the clone's own access point&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;At either granularity the clone shares data blocks with its parent. Cloning a 256 MiB file four times:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Point&lt;/th&gt;
&lt;th&gt;Logical&lt;/th&gt;
&lt;th&gt;Physical&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Just after one 256 MiB PUT&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;253,534,208 B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;After four file-granularity clones&lt;/td&gt;
&lt;td&gt;1,350,942,720 B&lt;/td&gt;
&lt;td&gt;277,200,896 B&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Logical grew to five copies, physical by about 23 MB. &lt;strong&gt;FSx for ONTAP bills provisioned SSD capacity, so there is no capacity to provision per duplicate.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;NetApp states that copies consume no storage except what is required for metadata until changes are written to the copy (&lt;a href="https://docs.netapp.com/us-en/ontap/concepts/flexclone-volumes-files-luns-concept.html" rel="noopener noreferrer"&gt;FlexClone volumes, files, and LUNs&lt;/a&gt;). The operation that ends the sharing is a &lt;strong&gt;split&lt;/strong&gt; (&lt;code&gt;volume clone split start&lt;/code&gt;), which I did not measure. From ONTAP 9.4, on AFF systems where the volume guarantee is &lt;code&gt;none&lt;/code&gt;, the documentation states that the split shares the physical blocks rather than copying data (&lt;a href="https://docs.netapp.com/us-en/ontap/volumes/split-flexclone-from-parent-task.html" rel="noopener noreferrer"&gt;splitting procedure&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A file-granularity clone gives no observable failure, however.&lt;/strong&gt; &lt;code&gt;POST /api/storage/file/clone&lt;/code&gt; returns 202 and a job UUID, but the UUID resolved to &lt;code&gt;404 entry doesn't exist&lt;/code&gt; and appeared in no job listing. The same &lt;code&gt;fsxadmin&lt;/code&gt; retrieves volume-create and volume-clone jobs as &lt;code&gt;state=success&lt;/code&gt;, so this is not a permissions problem. A call naming a destination directory that does not exist also returned 202 and created nothing. &lt;strong&gt;Judge the outcome by inspecting the destination file.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  FlexGroup — access point attachment
&lt;/h3&gt;

&lt;p&gt;Creation has conditions, learned by failing in order.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Create with default parameters&lt;/td&gt;
&lt;td&gt;Failed. &lt;code&gt;Volumes of this type must be at least 50GB&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retry at 50 GiB&lt;/td&gt;
&lt;td&gt;Failed. &lt;code&gt;Aggregates not matching FabricPool requirements: aggr1&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retry with the aggregate named explicitly&lt;/td&gt;
&lt;td&gt;Failed. &lt;code&gt;Minimum size is "400GB"&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;400 GiB, explicit aggregate, &lt;code&gt;tiering.policy=none&lt;/code&gt;, thin&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Succeeded&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;FlexGroup volumes &lt;strong&gt;can&lt;/strong&gt; be created from the AWS Management Console, the AWS CLI and the Amazon FSx API (&lt;a href="https://docs.aws.amazon.com/fsx/latest/ONTAPGuide/managing-volumes.html" rel="noopener noreferrer"&gt;Managing volumes&lt;/a&gt;). Naming the aggregate explicitly was required because this one was created through the ONTAP REST API; that constraint does not appear on the AWS-side path.&lt;/p&gt;

&lt;p&gt;What follows is the point: whether an access point on the AWS side attaches to a FlexGroup created on the ONTAP side.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Appears on the FSx for ONTAP side as &lt;code&gt;VolumeStyle=FLEXGROUP&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;It does, with an &lt;code&gt;fsvol-&lt;/code&gt; identifier&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Attach an S3 access point to the FlexGroup&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Succeeded. &lt;code&gt;Lifecycle=AVAILABLE&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PUT / GET / LIST&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Succeeded. The GET content matched&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12 MiB multipart upload&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Succeeded. &lt;code&gt;StorageClass=FSX_ONTAP&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Snapshot the FlexGroup&lt;/td&gt;
&lt;td&gt;Succeeded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Clone that FlexGroup&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Succeeded. &lt;code&gt;style=flexgroup&lt;/code&gt;, &lt;code&gt;is_flexclone=true&lt;/code&gt;, online&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That last row is the item NetApp's table records as "volume clone of the FlexGroup volume containing ONTAP S3 buckets: not supported".&lt;/p&gt;




&lt;h2&gt;
  
  
  What actually failed on this path
&lt;/h2&gt;

&lt;p&gt;The constraints were not on NetApp's side of the table. Within what I measured, these three are the real traps.&lt;/p&gt;

&lt;h3&gt;
  
  
  A security-style and identity mismatch — failure after a successful attachment
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Attach with an NTFS-security-style volume + UNIX identity (&lt;code&gt;root&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Succeeded. &lt;code&gt;Lifecycle=AVAILABLE&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PUT through that access point&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Refused. &lt;code&gt;AccessDenied&lt;/code&gt;, body only &lt;code&gt;Access Denied&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Control: same identity, same caller, UNIX volume&lt;/td&gt;
&lt;td&gt;PUT / GET / LIST all succeeded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CIFS server on this SVM&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;None.&lt;/strong&gt; The UNIX-to-Windows mapping cannot resolve&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;AVAILABLE&lt;/code&gt; does not mean the file system layer is healthy.&lt;/strong&gt; Attachable and usable are different properties: IAM and the access point policy are both passed, and the file system layer refuses after that. The body is only &lt;code&gt;Access Denied&lt;/code&gt; and does not name the layer. A refusal at the identity layer says &lt;code&gt;no identity-based policy allows ...&lt;/code&gt; instead, so &lt;strong&gt;the difference in the body is the signal.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Trying WINDOWS identity on the same SVM does not even complete the attachment.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Attach with WINDOWS identity on an SVM with no CIFS server&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Failed. &lt;code&gt;did not stabilize&lt;/code&gt; (&lt;code&gt;NotStabilized&lt;/code&gt;), stack rolled back&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Whether an attachment survives the rollback&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;It does not.&lt;/strong&gt; Nothing was orphaned&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  The wait for something created in ONTAP to appear on the AWS side
&lt;/h3&gt;

&lt;p&gt;A volume created through the ONTAP API is not immediately in the AWS-side &lt;code&gt;describe-volumes&lt;/code&gt;. Without an &lt;code&gt;fsvol-&lt;/code&gt; identifier, neither &lt;code&gt;AWS::FSx::S3AccessPointAttachment&lt;/code&gt; nor &lt;code&gt;create-and-attach-s3-access-point&lt;/code&gt; can reference it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Measurement&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;FlexGroup, polled every 20 s with no gaps&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Appeared at 599 s (about 10 min)&lt;/strong&gt;, with an &lt;code&gt;fsvol-&lt;/code&gt; identifier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A FlexClone volume, polled every 20 s with no gaps&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Appeared at 1,177 s (about 19.6 min)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A separate run&lt;/td&gt;
&lt;td&gt;Still absent at 1,258 s (about 21 min)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The three observations disagree, so this is not an upper bound.&lt;/strong&gt; The order of magnitude is tens of minutes, which is not enough to bake a fixed wait into a design. AWS documents the following, and the measurement is longer than its "several minutes".&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Amazon FSx periodically syncs with ONTAP to ensure consistency. If you create or modify volumes using NetApp applications, it may take up to several minutes for these changes to be reflected in the AWS Management Console, AWS CLI, API and SDKs.&lt;/p&gt;

&lt;p&gt;— &lt;a href="https://docs.aws.amazon.com/fsx/latest/ONTAPGuide/managing-resources-ontap-apps.html" rel="noopener noreferrer"&gt;Managing FSx for ONTAP resources using NetApp applications&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;This delay has to be kept apart from absence.&lt;/strong&gt; In an observation of about 2.5 minutes, a volume created through the ONTAP API shows no &lt;code&gt;fsvol-&lt;/code&gt; identifier, and it would be easy to conclude from that that neither a clone nor a FlexGroup can take an access point. Wait, and it appears, and it attaches. 2.5 minutes sits inside the "a few minutes" AWS documents, so &lt;strong&gt;what is observable in that window is "not yet appeared".&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It lags in the other direction too. Setting a junction path on the ONTAP side and attaching immediately fails with &lt;code&gt;Amazon FSx is unable to attach S3access point because the volume is not mounted.&lt;/code&gt; The AWS-side &lt;code&gt;JunctionPath&lt;/code&gt; was &lt;code&gt;None&lt;/code&gt; at that moment and still was two minutes later. Setting it with &lt;code&gt;aws fsx update-volume&lt;/code&gt; made it visible on the AWS side in about 40 seconds, and the attachment then succeeded. &lt;strong&gt;The error message was telling the truth.&lt;/strong&gt; What was wrong was my assumption that setting it in ONTAP made it visible to AWS.&lt;/p&gt;

&lt;h3&gt;
  
  
  A residual NAS bucket refusing volume deletion on the ONTAP side
&lt;/h3&gt;

&lt;p&gt;First, the terms. An S3 access point is an AWS-side mechanism and an object store NAS bucket is an ONTAP-side object, and &lt;strong&gt;the two are different things.&lt;/strong&gt; Attaching an access point creates a corresponding NAS bucket on the ONTAP side.&lt;/p&gt;

&lt;p&gt;What happened here is residue of the latter. &lt;strong&gt;After every access point was detached, the NAS bucket remained on the ONTAP side, and ONTAP refused to delete the volume because of it.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cannot delete volume "..." in SVM "..." because it is associated with the following
object store NAS buckets: "amazon-fsx-&amp;lt;volume-id&amp;gt;"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What was checked&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Where the bucket name comes from&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;An exact match for the volume ID.&lt;/strong&gt; It is created per volume, not per access point&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;After deleting every access point&lt;/td&gt;
&lt;td&gt;Still refused, and still refused hours later&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bringing the volume back online and mounted&lt;/td&gt;
&lt;td&gt;Still refused&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Re-attaching an access point and detaching it in the correct order&lt;/td&gt;
&lt;td&gt;Still refused. &lt;strong&gt;It is independent of the access point lifecycle&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;aws fsx delete-volume&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Succeeded.&lt;/strong&gt; Both the volume and the bucket went&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;NetApp's documented &lt;a href="https://docs.netapp.com/us-en/ontap/revert/remove-nas-bucket-task.html" rel="noopener noreferrer"&gt;procedure for removing a NAS bucket configuration&lt;/a&gt; uses &lt;code&gt;vserver object-store-server bucket delete&lt;/code&gt;, which &lt;strong&gt;could not be applied here because the target could not be listed.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Whether this residue is by design or a defect is unconfirmed.&lt;/strong&gt; No AWS documentation I could find describes a NAS bucket outliving its access points and blocking a volume delete; the documented removal path is &lt;a href="https://docs.aws.amazon.com/fsx/latest/ONTAPGuide/delete-access-point.html" rel="noopener noreferrer"&gt;&lt;code&gt;detach-and-delete-s3-access-point&lt;/code&gt;&lt;/a&gt;. If you hit the same state, &lt;code&gt;delete-volume&lt;/code&gt; on the AWS side will complete the teardown, but ask support to establish the cause.&lt;/p&gt;

&lt;p&gt;Not being listed is not the same as not existing. Laid out by reader:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Reader&lt;/th&gt;
&lt;th&gt;How it shows this bucket / S3 server&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ONTAP REST &lt;code&gt;/protocols/s3/buckets&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Does not list it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ONTAP CLI &lt;code&gt;vserver object-store-server bucket show&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Does not list it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ONTAP REST &lt;code&gt;/svm/svms&lt;/code&gt; (the &lt;code&gt;s3&lt;/code&gt; field)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Reports&lt;/strong&gt; the S3 server as &lt;code&gt;enabled&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ONTAP REST &lt;code&gt;/protocols/s3/services&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Does not list&lt;/strong&gt; the same SVM&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Objects that AWS manages are hidden from the standard ONTAP S3 views.&lt;/strong&gt; I confirmed the bucket's absence across two readers, and both shared the same blind spot.&lt;/p&gt;

&lt;p&gt;If you have made clones, the teardown order needs one more step of care. &lt;code&gt;delete-volume&lt;/code&gt; returned &lt;code&gt;DELETING&lt;/code&gt; and then silently went back to &lt;code&gt;CREATED&lt;/code&gt; (observed twice). The reason appears only in &lt;code&gt;LifecycleTransitionReason&lt;/code&gt; on &lt;code&gt;describe-volumes&lt;/code&gt;: &lt;code&gt;Failed to delete volume because it has one or more clones.&lt;/code&gt; The clones had been deleted, and were sitting in ONTAP's &lt;strong&gt;volume recovery queue&lt;/strong&gt;. The parent kept &lt;code&gt;clone.has_flexclone&lt;/code&gt; at &lt;code&gt;true&lt;/code&gt;, and the AWS-side delete reads that flag and refuses. Purging the recovery queue cleared the flag and the same &lt;code&gt;delete-volume&lt;/code&gt; worked. &lt;strong&gt;That queue appears in neither the console nor the FSx for ONTAP API.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Putting it in a runbook
&lt;/h2&gt;

&lt;p&gt;Translated into operational terms, the measurements come to five points.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;What to write in the runbook&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Match the security style to the identity.&lt;/strong&gt; UNIX identity for UNIX volumes, WINDOWS identity for NTFS ones. Avoid a UNIX identity on NTFS even where the SVM does have a CIFS server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Do not use &lt;code&gt;AVAILABLE&lt;/code&gt; as a connectivity check.&lt;/strong&gt; Make the acceptance criterion a PUT and a read-back after attaching&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Budget tens of minutes into automation that creates a resource in ONTAP and then handles it through the AWS API.&lt;/strong&gt; Poll for the &lt;code&gt;fsvol-&lt;/code&gt; identifier rather than sleeping a fixed interval&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Write through the AWS management plane.&lt;/strong&gt; Where a setting can be made from either side, as with the junction path, the AWS side reflects faster (about 40 seconds against more than two minutes)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Tear down through the AWS-side API.&lt;/strong&gt; A volume that has carried an access point can keep a NAS bucket on the ONTAP side, so a procedure built on ONTAP's &lt;code&gt;volume delete&lt;/code&gt; gets stuck there (&lt;code&gt;aws fsx delete-volume&lt;/code&gt; completes)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  What the schedule took over
&lt;/h2&gt;

&lt;p&gt;Everything so far was about whether an operation is possible. Operations has another kind of work: checking capacity, taking stock of snapshots, revisiting tiering policy, confirming throughput is still right-sized. &lt;strong&gt;All of it is daily or weekly, and a button for it does not get pressed.&lt;/strong&gt; That work suits a schedule better than a screen, so six patterns live in &lt;code&gt;operations/&lt;/code&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ID&lt;/th&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OPS1&lt;/td&gt;
&lt;td&gt;capacity-rightsizing&lt;/td&gt;
&lt;td&gt;Capacity and throughput monitoring, tier recommendation, what-if cost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OPS2&lt;/td&gt;
&lt;td&gt;storage-efficiency&lt;/td&gt;
&lt;td&gt;Tracks deduplication and compression efficiency, recommends enabling them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OPS3&lt;/td&gt;
&lt;td&gt;tiering-optimizer&lt;/td&gt;
&lt;td&gt;Cold-data analysis, tiering policy recommendation and savings estimate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OPS4&lt;/td&gt;
&lt;td&gt;snapshot-lifecycle&lt;/td&gt;
&lt;td&gt;Retention policy compliance check and drift detection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OPS5&lt;/td&gt;
&lt;td&gt;cost-optimization&lt;/td&gt;
&lt;td&gt;Cost breakdown, unit economics ($/GB), growth projection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OPS6&lt;/td&gt;
&lt;td&gt;qos-monitoring&lt;/td&gt;
&lt;td&gt;QoS policy adherence, bandwidth contention risk&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The shape is the same in all six. An EventBridge schedule starts a Step Functions workflow, a VPC Lambda collects from the ONTAP REST API and CloudWatch, another analyses, and a third writes the report. Unit tests total 74 across the six (&lt;code&gt;capacity-rightsizing&lt;/code&gt; 27, &lt;code&gt;snapshot-lifecycle&lt;/code&gt; 19, &lt;code&gt;tiering-optimizer&lt;/code&gt; 11, &lt;code&gt;storage-efficiency&lt;/code&gt; 6, &lt;code&gt;cost-optimization&lt;/code&gt; 6, &lt;code&gt;qos-monitoring&lt;/code&gt; 5).&lt;/p&gt;

&lt;p&gt;The report has two destinations. With &lt;code&gt;OutputDestination=FSXN_S3AP&lt;/code&gt; it is written to an FSx for ONTAP volume through an S3 access point, which means &lt;strong&gt;anyone with the NFS or SMB mount reads it in the file explorer they already use.&lt;/strong&gt; On the real cluster the same 4,240-byte JSON appeared on the ONTAP volume as in the S3 bucket, the Step Functions execution took around 15 seconds, and four metrics were published to the &lt;code&gt;FSxOps&lt;/code&gt; namespace.&lt;/p&gt;

&lt;p&gt;A recommendation comes out like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"recommendation_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"downsize"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vol_backup_temp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"current_value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"10.0% (1024 GB allocated, 102 GB used)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"recommended_value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Shrink to 205 GB or enable autosize (grow_shrink)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"monthly_cost_delta_usd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;-102.4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;DemoMode=true&lt;/code&gt; runs the whole workflow on mock data with no file system, so the report layout and the shape of the recommendations can be reviewed first. Running cost itself — Lambda, Step Functions and S3 storage — came to roughly $1-5 a month for a daily run over a handful of file systems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Progressive automation — four levels by design, two in the code
&lt;/h3&gt;

&lt;p&gt;Honestly, here. The template's &lt;code&gt;AutomationLevel&lt;/code&gt; accepts 0 through 3.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;Intended behaviour&lt;/th&gt;
&lt;th&gt;Implemented&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Report only&lt;/td&gt;
&lt;td&gt;Yes (default)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Notify over SNS&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Execute after human review&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;No&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Auto-execute within guardrails&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;No&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The code branches in exactly one place, &lt;code&gt;automation_level &amp;gt;= 1&lt;/code&gt;, deciding whether to send SNS. &lt;strong&gt;So 2 or 3 deploys successfully and behaves identically to Level 1.&lt;/strong&gt; The values the parameter accepts and the values the code distinguishes do not match.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The values listed in &lt;code&gt;AllowedValues&lt;/code&gt; are not a declaration of what is implemented — accepting a value and honouring it are different things.&lt;/strong&gt; It is the same point Part 4 made about not shipping buttons that cannot be pressed, showing up on the parameter side instead. Until the approval flow exists, narrowing &lt;code&gt;AllowedValues&lt;/code&gt; to 0 and 1 would be the accurate move.&lt;/p&gt;

&lt;p&gt;None of this replaces existing tooling. The vendor's CloudWatch dashboards and auto-resize solutions, and the SSD capacity auto-expansion AWS documents, differ in immediacy and in scope. The six here suit a flow where analysis and a recommendation come first and a person decides. Choose on the use case.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;On how recommendations are treated&lt;/strong&gt;: OPS4's snapshot deletion recommendations exclude anything below the minimum retention. A recommendation is an assistive signal, not a final decision. Which retention period satisfies your regulatory requirements is a legal and compliance judgement, and neither this article nor the pattern stands in for it.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What I did not measure
&lt;/h2&gt;

&lt;p&gt;"Unverified" is not "cannot be done". Every result above turned out that way, so on this path &lt;strong&gt;nothing is known until it is measured.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;FlexClone at LUN granularity&lt;/td&gt;
&lt;td&gt;Unverified. Creating a LUN needs an iSCSI configuration, which is not on this path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The FlexClone split&lt;/td&gt;
&lt;td&gt;Unverified. Documentation only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An upper bound on the ONTAP-to-AWS reflection time&lt;/td&gt;
&lt;td&gt;Unverified. The three observations disagree&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FabricPool tiering&lt;/td&gt;
&lt;td&gt;Unverified. Needs a different aggregate configuration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;QoS / deduplication and compression / SnapMirror / Vscan&lt;/td&gt;
&lt;td&gt;Unverified&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SnapLock / Object Lock&lt;/td&gt;
&lt;td&gt;Unverified. &lt;strong&gt;Irreversible&lt;/strong&gt;, so not enabled without an instruction naming the retention value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Attaching on a FlexCache cache volume&lt;/td&gt;
&lt;td&gt;Unverified. Requires a cluster peer and an SVM peer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OPS Levels 2 and 3&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Not implemented.&lt;/strong&gt; The approval flow and auto-execution are not in the code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Whether the OPS recommendations are sound&lt;/td&gt;
&lt;td&gt;Not evaluated. They are produced; none was applied and measured&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OPS across many file systems&lt;/td&gt;
&lt;td&gt;Unmeasured. Only a handful were run&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Two things were left off the screen, and both were written up here.&lt;/p&gt;

&lt;p&gt;On the ONTAP side I took NetApp's table as the starting point and measured on the S3 access point path. &lt;strong&gt;The four items it lists as unsupported did not appear as restrictions here.&lt;/strong&gt; Qtrees work, a quota refuses an S3 PUT, FlexClone works at both volume and file granularity, a FlexGroup takes an access point, and a clone of that FlexGroup can be created. The constraints were elsewhere: &lt;code&gt;AVAILABLE&lt;/code&gt; says nothing about the file system layer, something created in ONTAP takes tens of minutes to appear on the AWS side, and a volume that has carried an access point can only be deleted from the AWS side.&lt;/p&gt;

&lt;p&gt;On the operations side, the daily work went to a schedule rather than a screen. There is one mismatch between the declaration and the code here: &lt;strong&gt;&lt;code&gt;AutomationLevel&lt;/code&gt; accepts 0 through 3 while the code distinguishes only up to 1.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every failure in these six parts looked like a correct conclusion on its own. &lt;strong&gt;A conclusion is not settled until one more control, or a second reader, is added.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Thank you for reading these six parts. It started with showing files in a browser and ended outside the screen entirely. &lt;a href="https://dev.to/aws-builders/what-a-stack-deletion-leaves-behind-nx-plugin-for-aws-10-aws-blocks-and-amplify-gen-2-compared-10fg"&gt;Part 7&lt;/a&gt; picks up the layer underneath the portal: which tooling to build it on, and what a stack deletion leaves behind. I hope the series is useful to anyone operating FSx for ONTAP, or building this kind of admin screen.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;All test resources were deleted. The figures come from one specific environment and configuration and will differ with workload and setup. This article does not stand in for governance or regulatory judgement.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>fsxforontap</category>
      <category>ontap</category>
      <category>s3</category>
    </item>
    <item>
      <title>FlexGroup creation and capacity rebalancing failed on the defaults — what the error codes said (File Portal Part 5)</title>
      <dc:creator>Yoshiki Fujiwara(藤原 善基)@AWS Community Builder</dc:creator>
      <pubDate>Fri, 04 Sep 2026 00:59:05 +0000</pubDate>
      <link>https://dev.to/aws-builders/what-i-learned-driving-fsx-for-ontap-from-a-file-portal-on-aws-flexgroup-creation-capacity-3gkd</link>
      <guid>https://dev.to/aws-builders/what-i-learned-driving-fsx-for-ontap-from-a-file-portal-on-aws-flexgroup-creation-capacity-3gkd</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Part 4 described putting 182 ONTAP operations on a file portal: the design of delegation and record, and the treatment of buttons that cannot be pressed and buttons that were never built.&lt;/p&gt;

&lt;p&gt;This part is what the running cluster refused while those 182 were being built. None of it was visible while reading the documentation; each one arrived as an error code. That is the difference from last time: Part 4 was about design, this one is about measurement.&lt;/p&gt;

&lt;p&gt;The readers I have in mind are people writing code against the FSx for ONTAP ONTAP REST API. You do not need to be building a portal — if you call the same API, you stop in the same places.&lt;/p&gt;

&lt;p&gt;What this part covers: FlexGroup creation, FlexGroup capacity rebalance, how to read the capacity numbers, deletes that refuse, and four pitfalls in the S3-compatible API. Each with the response the system actually returned.&lt;/p&gt;

&lt;p&gt;What this part does not cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ONTAP internals&lt;/li&gt;
&lt;li&gt;Absolute throughput figures (the subject here is whether something is possible and under what condition — but the defaults that decide whether a measured figure can be trusted are covered in a section below)&lt;/li&gt;
&lt;li&gt;Running SnapLock compliance (irreversible, and it would make a test file system undeletable for months, so it was deliberately not done)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Up front:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating a FlexGroup through the ONTAP REST API requires naming the target aggregate. FSx for ONTAP aggregates have tiering enabled, so ONTAP's automatic placement skips them. Creating one from the AWS console or the Amazon FSx API does not need this step&lt;/li&gt;
&lt;li&gt;The same root cause is documented on the FlexCache side as a flag (&lt;code&gt;use_tiered_aggregate&lt;/code&gt;), and it had not reached the volume-creation side&lt;/li&gt;
&lt;li&gt;The capacity rebalance &lt;code&gt;max_runtime&lt;/code&gt; has a lower and an upper bound that are not in the API reference. With ONTAP's defaults it never starts&lt;/li&gt;
&lt;li&gt;Starting a rebalance enables an irreversible setting. Stopping it does not undo it&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;space.used&lt;/code&gt; means a different quantity depending on the volume. Snapshots inside the reserve are not counted&lt;/li&gt;
&lt;li&gt;A deleted FlexClone child volume stays invisible to the API while still blocking the parent volume's delete. There are two ways out, and both were measured&lt;/li&gt;
&lt;li&gt;Two places the documentation and the running system disagreed, and both changed the screen's wording&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ten combinations of defaults return a plausible figure while measuring something other than what you intended.&lt;/strong&gt; Three of them come from reads being served out of cache, two from when you measured, and one is not a setting at all but a way of measuring (a single client at low concurrency)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Test environment
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Dates&lt;/td&gt;
&lt;td&gt;mid-August 2026 to 2026-09-02 (JST)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Region&lt;/td&gt;
&lt;td&gt;ap-northeast-1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ONTAP version&lt;/td&gt;
&lt;td&gt;9.18.1P3D1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment type&lt;/td&gt;
&lt;td&gt;SINGLE_AZ_1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Caller&lt;/td&gt;
&lt;td&gt;ONTAP REST API from a VPC Lambda (Python 3.13)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Privilege&lt;/td&gt;
&lt;td&gt;&lt;code&gt;fsxadmin&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cleanup&lt;/td&gt;
&lt;td&gt;All test volumes deleted&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The distinction that matters most — ONTAP's words and AWS's words
&lt;/h2&gt;

&lt;p&gt;Mix these up and searching for the error text will not find the answer.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Term&lt;/th&gt;
&lt;th&gt;Where it appears&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;FabricPool, &lt;code&gt;use_tiered_aggregate&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;ONTAP error text and REST field names&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tiering, capacity pool tier&lt;/td&gt;
&lt;td&gt;AWS documentation and console&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;They refer to the same mechanism. But the error arrives in ONTAP's vocabulary while the documentation explains it in AWS's, so searching the AWS documentation for the error string returns nothing. The portal's wording follows the AWS side; only API strings are quoted as-is.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to watch when creating a FlexGroup
&lt;/h2&gt;

&lt;p&gt;A FlexGroup presents several "constituents" — internally split real volumes — as one namespace.&lt;/p&gt;

&lt;p&gt;To set the scope: FlexGroup volumes can be created from the AWS console, the AWS CLI and the Amazon FSx API (Managing volumes). What follows is the ONTAP REST API path, which is the one the portal uses.&lt;/p&gt;

&lt;p&gt;ONTAP normally picks the aggregates if you do not name them. On FSx for ONTAP that automatic selection does not resolve.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Aggregates not matching FabricPool requirements: aggr1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An FSx for ONTAP file system has a primary storage tier (SSD) and a capacity pool storage tier, and infrequently accessed data is tiered to the latter (AWS documentation). Aggregates in that configuration fall outside ONTAP's automatic placement.&lt;/p&gt;

&lt;p&gt;The conditions came out one failure at a time.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Create with default parameters&lt;/td&gt;
&lt;td&gt;Failed. &lt;code&gt;Volumes of this type must be at least 50GB&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retry at 50 GiB&lt;/td&gt;
&lt;td&gt;Failed. &lt;code&gt;Aggregates not matching FabricPool requirements: aggr1&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retry naming the aggregate&lt;/td&gt;
&lt;td&gt;Failed. &lt;code&gt;Minimum size is "400GB"&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;400 GiB, aggregate named, &lt;code&gt;tiering.policy=none&lt;/code&gt;, thin&lt;/td&gt;
&lt;td&gt;Succeeded&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Naming the aggregate works. But that aggregate name appears in neither the AWS console nor the Amazon FSx API. It has to be fetched with &lt;code&gt;GET /storage/aggregates&lt;/code&gt;. Nobody hands the tenant an aggregate name, so that extra call is mandatory.&lt;/p&gt;

&lt;p&gt;The 400 GB floor is configuration-dependent: the documented minimum is 100 GB per constituent, so the floor moves with the constituent count (the default is 8 per HA pair). And this extra step is only needed on the ONTAP REST API path — if you do not need to choose the aggregate yourself, creating the volume from the Amazon FSx API is shorter.&lt;/p&gt;

&lt;p&gt;The same root cause was already known on the FlexCache side. There is a flag, &lt;code&gt;use_tiered_aggregate&lt;/code&gt;, defaulting to &lt;code&gt;false&lt;/code&gt;. It had not reached the volume-creation side. The same cause surfacing elsewhere with a different face is a familiar shape in a managed service that wraps upstream software.&lt;/p&gt;

&lt;p&gt;The screen carries this note:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When creating a FlexGroup from this screen, the aggregate is named explicitly rather than placed automatically. FSx for ONTAP aggregates have tiering enabled and are excluded from automatic placement (measured: without it, creation fails with "Aggregates not matching FabricPool requirements"). With the default four constituents the minimum is 400 GB.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The capacity rebalance that never starts with the defaults
&lt;/h2&gt;

&lt;p&gt;A FlexGroup places files across constituents by hash, so they drift apart over time. When one fills, the whole volume returns "no space" even though the others have room. A capacity rebalance is what corrects that.&lt;/p&gt;

&lt;p&gt;Running it against the real cluster produced two constraints on &lt;code&gt;max_runtime&lt;/code&gt; that are not in the API reference.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Constraint&lt;/th&gt;
&lt;th&gt;Detail&lt;/th&gt;
&lt;th&gt;Error code&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Lower bound&lt;/td&gt;
&lt;td&gt;Under 30 minutes is refused&lt;/td&gt;
&lt;td&gt;&lt;code&gt;144182221&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Upper bound&lt;/td&gt;
&lt;td&gt;Must be shorter than the time remaining until the next snapshot&lt;/td&gt;
&lt;td&gt;&lt;code&gt;13107433&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Together, the window to start is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;30 min &amp;lt;= max_runtime &amp;lt; (time from start until the next snapshot)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ONTAP's default &lt;code&gt;max_runtime&lt;/code&gt; is 6 hours. On a volume with the default snapshot policy (hourly at :05), 6 hours always violates the upper bound. With the value the portal offered as its default, it never started once. With the default policy the only window is the 30 minutes from :05 to :35 each hour. On a frequent schedule such as &lt;code&gt;5min&lt;/code&gt;, nothing starts until the policy is detached.&lt;/p&gt;

&lt;p&gt;The boundary was pinned with a one-second A/B.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;&lt;code&gt;maxRuntime&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Until next snapshot&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;16:06:12&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;PT1H&lt;/code&gt; (60 min)&lt;/td&gt;
&lt;td&gt;58m47s&lt;/td&gt;
&lt;td&gt;Refused (&lt;code&gt;13107433&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16:06:13&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;PT30M&lt;/code&gt; (30 min)&lt;/td&gt;
&lt;td&gt;58m46s&lt;/td&gt;
&lt;td&gt;Succeeded&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;60 minutes &amp;gt; 58m47s refused; 30 minutes &amp;lt; 58m46s succeeded. Because the two calls are one second apart, the deciding factor is the &lt;code&gt;max_runtime&lt;/code&gt; value and not elapsed time. ONTAP's own message names both remedies — "reduce the &lt;code&gt;-max-runtime&lt;/code&gt; or disable the snapshot policy" — because of this structure.&lt;/p&gt;

&lt;p&gt;The full observation log is in the FlexGroup capacity rebalance record. State values absent from the reference's volume-side list turned up there too: &lt;code&gt;idle&lt;/code&gt; means running with nothing to move, &lt;code&gt;scheduled&lt;/code&gt; means a reservation exists. The portal had been displaying "running" as "unknown".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe5nry6b00fg0ezu4gyqx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe5nry6b00fg0ezu4gyqx.png" alt="The rebalance panel: state, volume-wide skew and the most skewed constituent, per-constituent usage, and the maximum runtime rounded to 30 minutes" width="800" height="476"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A note on irreversibility&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Starting a rebalance enables &lt;code&gt;granular data&lt;/code&gt; on that volume. It is required for the rebalance, and it creates two multi-part inodes per moved file (ONTAP documentation). Stopping does not undo it. The vendor's documentation lists no way back other than deleting the volume or restoring from a snapshot taken before it was enabled.&lt;/p&gt;

&lt;p&gt;Stopping is not a rollback either: files already moved stay where they are. Both were measured (&lt;code&gt;granular_data&lt;/code&gt; remains &lt;code&gt;true&lt;/code&gt; after a stop). The screen's confirmation says both.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The space that does not come back after a delete
&lt;/h2&gt;

&lt;p&gt;Read the usage figure in a volume list naively and you will get it wrong, because &lt;code&gt;space.used&lt;/code&gt; means a different quantity depending on the volume.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Snapshots that fit inside the snapshot reserve (5% by default) are not counted in &lt;code&gt;space.used&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Anything past the reserve consumes the same space as live data and is counted&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The measurements. A 100 GiB volume reported &lt;code&gt;used&lt;/code&gt; of 18.1 MiB while holding 77.3 MiB of snapshots. That sits inside the 5% reserve (5 GiB), so the usage bar does not move. On a different volume with a 0% reserve, &lt;code&gt;used&lt;/code&gt; was 83,677 MiB: 81,934 MiB of live data and 1,743 MiB of snapshots. On 8 of 11 volumes, snapshots exceeded live data.&lt;/p&gt;

&lt;p&gt;So the list was split from one usage figure into three: live data, snapshots, and reserve spill. Whether the space is not coming back because of snapshots or because live data fills it can now be read off the screen.&lt;/p&gt;

&lt;p&gt;Sometimes the cause is on another volume and its own row does not show it. That is FlexClone. A clone is based on one of the parent's snapshots, and that snapshot stays locked while the clone exists. Deleting files on the parent frees nothing while the locked snapshot still references the blocks. Reading the parent's row alone cannot tell you that, so it is written on the FlexClone panel instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to check when a FlexClone volume will not delete
&lt;/h2&gt;

&lt;p&gt;After deleting a child volume created with FlexClone, deleting the parent volume is refused with &lt;code&gt;has one or more clones&lt;/code&gt;. I reproduced that on real hardware. The awkward part is that the API cannot see that child volume. It is not in the volume listing, fetching it by name returns &lt;code&gt;entry doesn't exist&lt;/code&gt;, and yet only the parent's delete refuses, saying a clone exists.&lt;/p&gt;

&lt;p&gt;The cause is ONTAP's volume recovery queue, which retains deleted volumes for 12 hours by default. An A/B in the same environment, varying only whether the clone was split first:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Steps&lt;/th&gt;
&lt;th&gt;Parent delete&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Delete an unsplit clone, then delete the parent&lt;/td&gt;
&lt;td&gt;Failed (still failing after 7 and 15 minutes)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Split the clone, delete it, then delete the parent&lt;/td&gt;
&lt;td&gt;Succeeded within seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The exit was measured too. &lt;code&gt;GET /api/private/cli/volume/recovery-queue&lt;/code&gt; reads the queue. &lt;code&gt;POST .../purge&lt;/code&gt; goes through with &lt;code&gt;fsxadmin&lt;/code&gt; privilege, the entry left the queue in about 20 seconds, and the parent delete succeeded immediately afterwards.&lt;/p&gt;

&lt;p&gt;This is a place where I published something wrong and have corrected it. The earlier text said purge needs diag privilege, is unreachable as &lt;code&gt;fsxadmin&lt;/code&gt;, and therefore the only option is to wait 12 hours. That was wrong. I blamed privilege without trying it.&lt;/p&gt;

&lt;p&gt;The screen's error carries the causation and both exits (split first, or purge). Purge cannot be undone, so it also says to confirm the volume was yours before running it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnniem4z55s56ii6cta4h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnniem4z55s56ii6cta4h.png" alt="The FlexClone panel: the clone list and creation form, when to split, and the effect on the parent's capacity" width="800" height="476"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Four pitfalls in the S3-compatible API
&lt;/h2&gt;

&lt;p&gt;Behaviours worth knowing before you call an FSx for ONTAP S3 access point from a browser or an SDK. Knowing them changes how long the investigation takes.&lt;/p&gt;

&lt;h3&gt;
  
  
  The 501 failure of a "write only if absent" request
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;PutObject&lt;/code&gt; with &lt;code&gt;if-none-match: *&lt;/code&gt; returns &lt;code&gt;501 NotImplemented&lt;/code&gt;, with the body &lt;code&gt;A header you provided implies functionality that is not implemented&lt;/code&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Request&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;PUT&lt;/code&gt; + &lt;code&gt;if-none-match: *&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;501 NotImplemented&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;PUT&lt;/code&gt; + &lt;code&gt;x-amz-checksum-crc32&lt;/code&gt; (no &lt;code&gt;if-none-match&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;200 OK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;GET&lt;/code&gt; / &lt;code&gt;ListObjectsV2&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;200 (neither header is sent)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;CRC32 flexible checksums pass. Only &lt;code&gt;if-none-match&lt;/code&gt; fails. Reads are unaffected, so the symptom presents as "the listing works but every write fails."&lt;/p&gt;

&lt;p&gt;This is not somebody else's problem. Amplify's Storage Browser translates its "do not overwrite" option into that header, so dropping it in unchanged breaks every upload and folder creation. If you need overwrite protection, look the key up before writing instead. That does not give you &lt;code&gt;if-none-match&lt;/code&gt;'s atomicity, and two concurrent writes to the same key can both decide it is absent.&lt;/p&gt;

&lt;h3&gt;
  
  
  GOVERNANCE and COMPLIANCE, indistinguishable from the error text
&lt;/h3&gt;

&lt;p&gt;S3 Object Lock has two retention modes. GOVERNANCE lets a user holding a specific permission (&lt;code&gt;s3:BypassGovernanceRetention&lt;/code&gt;) delete inside the retention period; COMPLIANCE lets nobody delete until the retention expires. Deleting an object written with one day of retention, compared across both modes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;GOVERNANCE&lt;/th&gt;
&lt;th&gt;COMPLIANCE&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Delete without bypass&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AccessDenied ... object protected by object lock&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Identical text&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delete with &lt;code&gt;BypassGovernanceRetention&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Succeeds&lt;/td&gt;
&lt;td&gt;Refused with identical text&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Because the text is identical, the response does not tell you why the bypass did not work. The first things to read are &lt;code&gt;get-object-lock-configuration&lt;/code&gt; and &lt;code&gt;head-object&lt;/code&gt;'s &lt;code&gt;ObjectLockMode&lt;/code&gt; rather than the IAM policy, because the same text covers both a missing permission and a mode that has no bypass.&lt;/p&gt;

&lt;h3&gt;
  
  
  The lock indicator that does not change under automatic locking
&lt;/h3&gt;

&lt;p&gt;Assigning a policy whose schedules carry a &lt;code&gt;retentionPeriod&lt;/code&gt; leaves the locking feature flag at &lt;code&gt;false&lt;/code&gt;; only the assigned policy name changes. Reading the state alone suggests nothing will be locked, while every snapshot that policy takes is in fact locked. The judgement needs the assigned policy's &lt;code&gt;retentionPeriod&lt;/code&gt; as well.&lt;/p&gt;

&lt;h3&gt;
  
  
  The usable endpoints, derived from the API rather than a config file
&lt;/h3&gt;

&lt;p&gt;An alias copied into a config file looks equally correct for an access point that has been deleted and for one that is &lt;code&gt;MISCONFIGURED&lt;/code&gt;. &lt;code&gt;fsx describe-s3-access-point-attachments&lt;/code&gt; returns &lt;code&gt;Lifecycle&lt;/code&gt; and the Internet or VPC origin, so derive the inventory from there. Always follow the pagination: read only the first page and an access point that exists goes missing in a way indistinguishable from one that does not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two places the documentation and the running system disagreed
&lt;/h2&gt;

&lt;p&gt;Two things behaved differently from what I read before implementing them. Both changed the screen's wording.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Subject&lt;/th&gt;
&lt;th&gt;Expected from the documentation&lt;/th&gt;
&lt;th&gt;Measured (ONTAP 9.18.1P3D1)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Deleting a QoS policy while assigned&lt;/td&gt;
&lt;td&gt;The CLI reference says it is refused without &lt;code&gt;-force&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;REST accepts it, and the volume's assignment is silently removed (every ceiling becomes 0, meaning unlimited)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enforcement after deleting a quota rule&lt;/td&gt;
&lt;td&gt;The REST reference says it stays in effect until enforcement is cycled off and on&lt;/td&gt;
&lt;td&gt;The deleted rule's ceiling disappeared from the usage report immediately&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The QoS confirmation text was rewritten. The correct warning is not "it cannot be deleted" but "deleting it removes the ceilings from every volume using that policy". A note was added that releasing a single volume means assigning &lt;code&gt;none&lt;/code&gt;, not deleting the policy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ten defaults that can invalidate a measurement
&lt;/h2&gt;

&lt;p&gt;Up to here the defaults decided whether an operation goes through. Measurement has defaults of the same shape. None of them returns an error: each returns a plausible figure while measuring something other than what you intended.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Where these figures come from&lt;/strong&gt;: a different measurement from the environment above. 2026-09-01 to 02, ap-northeast-1, ONTAP 9.18.1P3D1, SINGLE_AZ_1, throughput tiers 128 MBps and 2048 MBps, client c5n.9xlarge. Repeatability across repeat runs of the same measurement was 0.02–0.14%.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The sixth is from a different measurement.&lt;/strong&gt; 2026-09-06, ap-northeast-1, ONTAP 9.18.1P3D1, SINGLE_AZ_2, throughput 6,144 MBps, an SMB SVM joined to AWS Managed AD, clients c5n.2xlarge and c5n.9xlarge. A different generation and a different tier from the five above, so do not put the figures side by side.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The sixth does not record the NVMe read cache state.&lt;/strong&gt; The second generation at 6,144 MBps has 1,900 GB of NVMe read cache and it ships enabled (confirmed on a live file system on 2026-09-10). The difference Multichannel makes is a channel count and is unaffected by this gap, but do not cite 942 and 1,824 as disk-path figures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The seventh and eighth are from a third measurement.&lt;/strong&gt; 2026-09-10, ap-northeast-1, ONTAP 9.18.1P6, SINGLE_AZ_2, 1,536 MBps and 6,144 MBps, 8,192 GiB of SSD, client c5n.9xlarge. The NVMe read cache state is part of the recorded conditions. It ships enabled at 6,144, so it was disabled on both nodes and read back before measuring.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The ninth and tenth are from a fourth measurement.&lt;/strong&gt; 2026-09-19/20, ap-northeast-1, ONTAP 9.18.1P6, SINGLE_AZ_1, provisioned 2,048 MBps, SSD IOPS 40,000, NVMe read cache disabled, an 800 GiB volume, 300 GiB read once, client c5n.9xlarge. It differs from every item above in generation, provisioned value, or version, so do not put these figures side by side with the others either.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;th&gt;How to notice&lt;/th&gt;
&lt;th&gt;What to do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Linux NFS uses one TCP connection&lt;/td&gt;
&lt;td&gt;Stops around 590 MB/s and does not respond to stream count&lt;/td&gt;
&lt;td&gt;1 / 4 / 8 give the same figure&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nconnect=16&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dd if=/dev/zero&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Zero blocks skip disk and return at 4x the published ceiling&lt;/td&gt;
&lt;td&gt;Exceeds the published ceiling&lt;/td&gt;
&lt;td&gt;Use incompressible data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Volume inline efficiency is on&lt;/td&gt;
&lt;td&gt;Compressible or identical payloads collapse (4x on reads, under 5% on writes)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;space_savings.dedupe_percent&lt;/code&gt; is high&lt;/td&gt;
&lt;td&gt;Turn it off before writing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DiskIopsConfiguration: AUTOMATIC&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3 IOPS/GiB, so IOPS becomes the limit&lt;/td&gt;
&lt;td&gt;MB/s divided by IOPS is implausibly small&lt;/td&gt;
&lt;td&gt;&lt;code&gt;USER_PROVISIONED&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A read only slightly over the cache size&lt;/td&gt;
&lt;td&gt;The cache-served share dominates and the disk path is not measured&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;DiskReadBytes&lt;/code&gt; divided by &lt;code&gt;DataReadBytes&lt;/code&gt; is a few per cent or less&lt;/td&gt;
&lt;td&gt;Read at least twice the cache in one pass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SMB Multichannel ships disabled&lt;/td&gt;
&lt;td&gt;One channel, and it holds while the session reports &lt;code&gt;dialect=3.1.1&lt;/code&gt; (1 MiB sequential read 942 against 1,824 MB/s)&lt;/td&gt;
&lt;td&gt;One established TCP connection to the SVM; &lt;code&gt;Get-SmbMultichannelConnection&lt;/code&gt; returns nothing&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;vserver cifs options modify -is-multichannel-enabled true&lt;/code&gt;. It does not reach existing connections — restart the client's SMB service and remap&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No window length decided&lt;/td&gt;
&lt;td&gt;Measures while the burst allowance is still there. 2.0x on the same environment and the same shape (2,882 MB/s for 27 minutes, then 1,439 MB/s)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;FileServerDiskThroughputUtilization&lt;/code&gt; is above 100%. It is a ratio against baseline, so it can be (median 203.5% measured)&lt;/td&gt;
&lt;td&gt;Run until the allowance is gone. Read &lt;code&gt;FileServerDiskThroughputBalance&lt;/code&gt; over the same window and record the balance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reading straight after a fill&lt;/td&gt;
&lt;td&gt;Competes with the fill's write-back and runs about 20% low for 90 seconds&lt;/td&gt;
&lt;td&gt;Disagrees with the same shape measured a second time (39% measured)&lt;/td&gt;
&lt;td&gt;Leave two minutes after the write finishes. Put the wait in the parameter file, not in a note in the runbook&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;tcp-max-xfer-size&lt;/code&gt; ships at 64 KiB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Measures at a truncated transfer size. A mount that requested &lt;code&gt;rsize=1048576&lt;/code&gt; succeeds but still runs at 64 KiB&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;findmnt -no OPTIONS &amp;lt;mnt&amp;gt;&lt;/code&gt; still shows &lt;code&gt;rsize&lt;/code&gt; at 65536&lt;/td&gt;
&lt;td&gt;Raise it to 1,048,576 on the SVM side, then remount (it is fixed at connect time). The same measurement gained +21%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;A single client at low concurrency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Measures that setting's value, not a ceiling (measured 1,195.27 → 1,446.66 at 1 MiB transfer size → 1,646.17 at 16 threads)&lt;/td&gt;
&lt;td&gt;None of the utilization metrics are saturated (disk throughput 63–77%)&lt;/td&gt;
&lt;td&gt;Raise transfer size and concurrency, or add clients. Both reached the same ceiling in this measurement (1 client at 16 threads: 1,646.17; 2 clients at 8 threads: 1,901.48; both at 102.6% disk throughput)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The tenth is different in kind from the rest.&lt;/strong&gt; The other nine go away once you change a setting; this one is a procedure. The fix is either "raise transfer size and concurrency" or "add clients", and in this configuration both reached the same ceiling (1 client at 16 threads: 1,646.17; 2 clients at 8 threads: 1,901.48; both at 102.6% disk throughput utilization).&lt;/p&gt;

&lt;p&gt;Do not write "raising concurrency is enough", though. On a different configuration (second generation, 6,144 MBps / 200,000 IOPS, 4 KiB random reads), doubling threads moved the figure by only +8.9%, adding SVMs moved it +26.3%, and adding clients moved it +52.7%. Which lever works depends on the configuration. What is common is only this: do not cite a value measured on a single client as a ceiling (measured 2026-09-20).&lt;/p&gt;

&lt;p&gt;How the fifth one surfaced. I assumed 280 GiB would not fit in cache. Read through Amazon CloudWatch's &lt;code&gt;AWS/FSx&lt;/code&gt; namespace, &lt;code&gt;DiskReadBytes&lt;/code&gt; divided by &lt;code&gt;DataReadBytes&lt;/code&gt; came to 1.4 / 0.9 / 1.5%. 98.5-99.9% of the bytes read never touched disk.&lt;/p&gt;

&lt;p&gt;The 2048 MBps tier's in-memory cache is 256 GB, that is 238 GiB (FSx for ONTAP performance; ap-northeast-1 falls under the first-generation Single-AZ "all other Regions" table). 280 GiB exceeds it by 18%, not by a factor of two or three. "Past the cache" and "not served from the cache" are different things.&lt;/p&gt;

&lt;p&gt;And clearing the cache by a wide margin does not help if the window is short. In the seventh measurement the working set was 30 times the in-memory cache, and the figure still came back 2.0x high for the 27 minutes the burst allowance lasted. "Read at least twice the cache" answers the fifth, not the seventh.&lt;/p&gt;

&lt;p&gt;Inline efficiency can be restored, provided you wait.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Rejected unless efficiency.op_state is idle
PATCH /api/storage/volumes/{uuid}  {"efficiency": {"compression": "inline"}}
PATCH /api/storage/volumes/{uuid}  {"efficiency": {"dedupe": "both"}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But only turn it off on a volume you can discard. Immediately after writing, a background efficiency operation is running and the wait is not predictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it in a runbook
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;What to write down&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;To create a FlexGroup through the ONTAP REST API, fetch the aggregate with &lt;code&gt;GET /storage/aggregates&lt;/code&gt; and name it. If you do not need to choose the aggregate, create the volume from the Amazon FSx API instead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Set the rebalance &lt;code&gt;max_runtime&lt;/code&gt; to at least 30 minutes and below the time to the next snapshot. Do not pass the default through&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Do not treat starting a rebalance as an operation you can press at any time. &lt;code&gt;granular data&lt;/code&gt; is enabled irreversibly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Do not show usage as a single number. Separate live data from snapshots, or nobody can reach the cause of "deleting files did not free space"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;A teardown procedure for an environment with clones must include the split, or the recovery queue purge. "The clone was deleted, so the parent can go" does not hold&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Errors arrive in ONTAP's vocabulary. Keep the AWS-to-ONTAP term mapping at hand before searching&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;A runbook for measuring performance states how to drop the defaults first. Leave any one of zero-filled data, inline efficiency, an unspecified &lt;code&gt;nconnect&lt;/code&gt; or &lt;code&gt;AUTOMATIC&lt;/code&gt; IOPS in place, and the figure produced is measuring something else&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What I did not measure
&lt;/h2&gt;

&lt;p&gt;"Unverified" is not "impossible". As last time, the boundary stated as a boundary:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The actual effect of a rebalance (how far the skew is evened out)&lt;/td&gt;
&lt;td&gt;Unmeasured. Only the conditions to start were measured&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance impact after &lt;code&gt;granular data&lt;/code&gt; is enabled&lt;/td&gt;
&lt;td&gt;Unmeasured&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A recovery queue with a changed retention&lt;/td&gt;
&lt;td&gt;Unverified. Measured at the 12-hour default&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Actual tiering into the capacity pool&lt;/td&gt;
&lt;td&gt;Unverified. Needs a different aggregate configuration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Running SnapLock compliance&lt;/td&gt;
&lt;td&gt;Deliberately not done. Irreversible, up to the file system&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An alternative path for copies over 5 GiB (NFS or SMB)&lt;/td&gt;
&lt;td&gt;Unmeasured. The portal stops at refusing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Part 6 covers the other side: what was left off the screen. How much of ONTAP stays usable alongside the S3 access point path, and the operational work that was handed to a schedule instead of a button.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Nothing in this part was visible while reading the documentation. Creating a FlexGroup through the ONTAP REST API needs the aggregate named, a capacity rebalance never starts with the defaults, &lt;code&gt;space.used&lt;/code&gt; means a different quantity per volume, and a deleted FlexClone volume blocks the parent's delete while invisible through the API.&lt;/p&gt;

&lt;p&gt;What they share is that the error text does not name the cause. That is why the causation and the exits live on the screen. As with the one-second A/B on the rebalance, another explanation looked just as correct until one control was added.&lt;/p&gt;

&lt;p&gt;There is also one correction. I published "purge needs diag privilege, so wait 12 hours" and later found it goes through. I blamed privilege without trying it.&lt;/p&gt;

&lt;p&gt;The figures come from one specific environment and configuration and will differ with workload and setup. The error codes were observed on ONTAP 9.18.1P3D1.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>fsxforontap</category>
      <category>ontap</category>
      <category>storage</category>
    </item>
    <item>
      <title>Putting FSx for ONTAP operations in a browser — the buttons I disabled, and the ones I never built (File Portal Part 4)</title>
      <dc:creator>Yoshiki Fujiwara(藤原 善基)@AWS Community Builder</dc:creator>
      <pubDate>Fri, 04 Sep 2026 00:57:42 +0000</pubDate>
      <link>https://dev.to/aws-builders/putting-fsx-for-ontap-operations-on-a-file-portal-on-aws-182-actions-and-the-design-of-425g</link>
      <guid>https://dev.to/aws-builders/putting-fsx-for-ontap-operations-on-a-file-portal-on-aws-182-actions-and-the-design-of-425g</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/aws-builders/adding-a-file-portal-to-fsx-for-ontap-s3-access-points-choosing-between-amplify-gen2-and-887"&gt;Part 1&lt;/a&gt; built the foundation of a file portal on top of Amazon FSx for NetApp ONTAP (FSx for ONTAP) S3 access points. &lt;a href="https://dev.to/aws-builders/embedding-storage-operations-into-a-file-portal-from-arpai-incident-response-to-regulatory-1oih"&gt;Part 2&lt;/a&gt; added ransomware response and WORM retention. &lt;a href="https://dev.to/aws-builders/embedding-ai-agents-into-a-file-portal-from-agentcore-mcp-to-multi-agent-teams-part-3-19m1"&gt;Part 3&lt;/a&gt; put AI agents on it.&lt;/p&gt;

&lt;p&gt;Those parts were screens for people who handle files. From here the subject is the people who operate the storage itself. Volume create and resize, FlexCache, FlexClone, SnapMirror, snapshot policies, QoS, quotas, SMB shares. The portal now reaches 182 operations.&lt;/p&gt;

&lt;p&gt;The readers I have in mind are infrastructure owners running FSx for ONTAP, and developers building this kind of admin screen with Amplify Gen2. ONTAP terms get a one-line gloss on first use, so ONTAP operations experience is not assumed. You can start here without Parts 1-3.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What this part covers&lt;/strong&gt; is what had to be decided to put operations on a screen: the design of delegation and record, and the treatment of buttons that cannot be pressed and buttons that were never built.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What this part does not cover&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The ONTAP features themselves (ONTAP's own documentation is more accurate)&lt;/li&gt;
&lt;li&gt;The failures the real cluster produced (that is an article's worth on its own, so it became Part 5)&lt;/li&gt;
&lt;li&gt;Which retention period satisfies your regulatory requirements (a legal and compliance judgement that neither I nor the portal can stand in for)&lt;/li&gt;
&lt;li&gt;Performance and scale limits of the screen (unmeasured; no concurrent-user test was run)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Up front:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Three management interfaces reach the file system, and two of them are for administrators inside the VPC. &lt;strong&gt;What the portal adds is not a path but delegation and record&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;The portal does not remove the path into the VPC. &lt;strong&gt;The path did not disappear; what holds it moved from a person to a Lambda&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;The number 182 is not maintained by hand. &lt;strong&gt;A script counts it and CI fails when it disagrees&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;A button that cannot be pressed says what is missing. &lt;strong&gt;A button that does nothing is indistinguishable from a broken one&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;The confirmation screen for an irreversible operation states &lt;strong&gt;what becomes undeletable, for how long, and what it costs meanwhile&lt;/strong&gt;, because the effect does not stop at the resource named in the call&lt;/li&gt;
&lt;li&gt;After writing this article I ran the screen and found &lt;strong&gt;the lock indicator was broken.&lt;/strong&gt; While only one panel was being read, it looked correct&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The repository is &lt;a href="https://github.com/Yoshiki0705/FSx-for-ONTAP-S3AccessPoints-Serverless-Patterns/tree/main/solutions/amplify-portal" rel="noopener noreferrer"&gt;solutions/amplify-portal/&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Test environment
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Date&lt;/td&gt;
&lt;td&gt;2026-09-02 (JST)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Region&lt;/td&gt;
&lt;td&gt;ap-northeast-1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ONTAP version&lt;/td&gt;
&lt;td&gt;9.18.1P3D1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Portal&lt;/td&gt;
&lt;td&gt;Amplify Gen2 sandbox: AppSync + Cognito + VPC Lambdas&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lambda runtime&lt;/td&gt;
&lt;td&gt;Python 3.13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How the action count was taken&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;scripts/portal_action_types.py --check&lt;/code&gt; and &lt;code&gt;scripts/check_portal_action_params.py&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cleanup&lt;/td&gt;
&lt;td&gt;Test sandboxes deleted. One working stack retained&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The distinction that matters most — a path that persists, a holder that moved
&lt;/h2&gt;

&lt;p&gt;First, the premise. Without routing through an additional third-party SaaS, three management interfaces reach FSx for ONTAP.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Interface&lt;/th&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Reach&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AWS Management Console / FSx API&lt;/td&gt;
&lt;td&gt;IAM&lt;/td&gt;
&lt;td&gt;The AWS-side operations on file systems, SVMs and volumes; backups&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ONTAP CLI (SSH)&lt;/td&gt;
&lt;td&gt;SSH to the management endpoint&lt;/td&gt;
&lt;td&gt;Roughly an ONTAP cluster administrator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ONTAP REST API&lt;/td&gt;
&lt;td&gt;HTTPS to the management endpoint&lt;/td&gt;
&lt;td&gt;The CLI's operations, programmatically&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The sources, and how anything outside this list is treated, are in &lt;a href="https://github.com/Yoshiki0705/FSx-for-ONTAP-S3AccessPoints-Serverless-Patterns/blob/main/docs/en/fsx-ontap-management-interfaces.md" rel="noopener noreferrer"&gt;Management interfaces&lt;/a&gt;. The CLI and REST management endpoints are reachable &lt;strong&gt;only from inside the VPC or through a Transit Gateway peered network&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;One consequence follows directly. Consider an everyday request: add a volume, or raise one quota. Answering it needs &lt;code&gt;fsxadmin&lt;/code&gt;-equivalent credentials and a path into the VPC. So every request lands on the infrastructure owner, and none of it can be handed to anyone else.&lt;/p&gt;

&lt;p&gt;That separation is what the portal does.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Delegation.&lt;/strong&gt; A Cognito group (&lt;code&gt;storage-admin&lt;/code&gt;) decides who may run what, per operation. No SSH key and no ONTAP credential is handed out. The credential lives in Secrets Manager and only the Lambda reads it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A record.&lt;/strong&gt; Who ran which operation is retained with the Cognito principal. Unlike an SSH session on a shared account, it attaches to a person&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The portal does not remove the path into the VPC: the Lambda that calls the ONTAP REST API sits inside it. &lt;strong&gt;The path did not disappear; what holds it moved from a person to a Lambda.&lt;/strong&gt; That distinction affects operational design, so the repository documentation words it the same way.&lt;/p&gt;




&lt;h2&gt;
  
  
  The reach of the screen
&lt;/h2&gt;

&lt;p&gt;The portal reaches 182 actions across 12 endpoints. That number is not maintained by hand. A script reconciles the generated types against the handlers and prints it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;python3 scripts/portal_action_types.py &lt;span class="nt"&gt;--check&lt;/span&gt;
DISPATCH ACTION TYPES: PASS &lt;span class="o"&gt;(&lt;/span&gt;182 actions declared, matching the handlers&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;python3 scripts/check_portal_action_params.py
PORTAL ACTION PARAMS: PASS &lt;span class="o"&gt;(&lt;/span&gt;187 literal call sites, 182 actions
across 12 endpoints, 1 call&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt; not statically readable&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reason it is not counted by hand is not that the number grew. &lt;strong&gt;Types do not cross the boundary between the screen and the Lambda.&lt;/strong&gt; The portal's dispatch takes an untyped &lt;code&gt;params&lt;/code&gt;, so code that sends &lt;code&gt;{snapshotName, retentionDays}&lt;/code&gt; to an action reading &lt;code&gt;snapshotId&lt;/code&gt; and &lt;code&gt;expiryTime&lt;/code&gt; compiles, lints, renders a button, and fails on every click. CI now fails until the reconciliation agrees.&lt;/p&gt;

&lt;p&gt;By domain.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Domain&lt;/th&gt;
&lt;th&gt;Operations&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Volume lifecycle&lt;/td&gt;
&lt;td&gt;Create / resize / delete / bring online, for both FlexVol and FlexGroup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FlexGroup capacity rebalance&lt;/td&gt;
&lt;td&gt;Read state / start / stop / schedule&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FlexCache&lt;/td&gt;
&lt;td&gt;Create / list / resize / write-back toggle / delete&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FlexClone&lt;/td&gt;
&lt;td&gt;Create / list / split&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SnapMirror&lt;/td&gt;
&lt;td&gt;Create / list / transfer history / update now / quiesce / resume / break / resync / abort / delete&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Snapshots&lt;/td&gt;
&lt;td&gt;List / create / delete / lock / policy create / assign / enable locking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SnapLock and WORM&lt;/td&gt;
&lt;td&gt;Read configuration / change retention, S3 Object Lock retention&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ARP/AI (ransomware protection)&lt;/td&gt;
&lt;td&gt;State change / bulk enable / suspect list and clear / surge parameters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Containment&lt;/td&gt;
&lt;td&gt;Block NFS IP / block SMB user / disconnect sessions / list active blocks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;QoS&lt;/td&gt;
&lt;td&gt;Policy create / update / delete / assign to and release from a volume&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quotas&lt;/td&gt;
&lt;td&gt;Rule create / update / delete / usage report / per-volume enforcement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SMB&lt;/td&gt;
&lt;td&gt;Share create, update, delete; local users and groups; members; name mapping&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qtrees&lt;/td&gt;
&lt;td&gt;Create / update / rename / delete&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Export policies&lt;/td&gt;
&lt;td&gt;Policy and rule create / delete&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FPolicy and Vscan&lt;/td&gt;
&lt;td&gt;Policy and event create / enable / delete&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Peering&lt;/td&gt;
&lt;td&gt;Cluster peer and SVM peer create / accept / delete, intercluster LIFs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cluster inventory&lt;/td&gt;
&lt;td&gt;Nodes / licenses / LIFs / protocols / DNS / jobs, EMS events&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S3&lt;/td&gt;
&lt;td&gt;Bucket list, Object Lock status and retention&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;182 is not every ONTAP operation. Operations that need advanced privilege are absent from the REST API, so they cannot appear on this screen.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnmtablhvkzuzfah8kk0d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnmtablhvkzuzfah8kk0d.png" alt="The volume list, showing live data, snapshot usage and reserve spill separately, and the FlexVol or FlexGroup style" width="800" height="476"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The plain statement of what cannot be done
&lt;/h2&gt;

&lt;p&gt;The copy limit is the clearest case. It is &lt;strong&gt;easy to confuse with the upload limit&lt;/strong&gt;, so the measured values are split out.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Limit&lt;/th&gt;
&lt;th&gt;Measured&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Upload (single &lt;code&gt;PutObject&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;5 GiB&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;MaxSizeAllowed&lt;/code&gt; = 5,368,709,120&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Upload (multipart, whole object)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;50 GiB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;53,687,091,200. Checked only at &lt;code&gt;CompleteMultipartUpload&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Download (&lt;code&gt;GetObject&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;No limit&lt;/td&gt;
&lt;td&gt;Objects over 50 GiB were retrieved&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Copy (single &lt;code&gt;CopyObject&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;5 GiB&lt;/td&gt;
&lt;td&gt;As above&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Copy (&lt;code&gt;UploadPartCopy&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Unusable&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Documented as Supported; every form returned &lt;code&gt;NoSuchKey&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Uploads do reach 50 GiB (measured with multipart). Copies, though, have no path past 5 GiB. &lt;code&gt;UploadPartCopy&lt;/code&gt; is listed as Supported in &lt;a href="https://docs.aws.amazon.com/fsx/latest/ONTAPGuide/access-points-for-fsxn-object-api-support.html" rel="noopener noreferrer"&gt;Access point compatibility&lt;/a&gt; and returned &lt;code&gt;NoSuchKey&lt;/code&gt; in every form measured. Rename, move, copy, trash and restore in the portal are all &lt;code&gt;CopyObject&lt;/code&gt;, so that 5 GiB becomes the screen's limit.&lt;/p&gt;

&lt;p&gt;The units caught me out as well. The AWS documentation's "5 GB" and "50 GB" both turned out to be binary when measured. &lt;code&gt;MaxSizeAllowed&lt;/code&gt; from &lt;code&gt;PutObject&lt;/code&gt; is 5,368,709,120 = 5 × 1024³, not the decimal 5,000,000,000. The detail is in &lt;a href="https://github.com/Yoshiki0705/FSx-for-ONTAP-S3AccessPoints-Serverless-Patterns/blob/main/docs/s3ap-object-size-limits-verification.md" rel="noopener noreferrer"&gt;the object size limit verification&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Rather than surfacing an S3 error halfway through, the portal checks the size before the copy and refuses with a reason.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;{key}&lt;/code&gt; is 6.2 GiB. A single copy is limited to 5 GiB, and the multipart copy that would lift the limit is not usable on this Access Point, so this operation cannot be completed from the portal. Move the file over NFS or SMB instead.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The guard's 5 GiB threshold is the same value as the measured &lt;code&gt;MaxSizeAllowed&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;On the same reasoning, no button was built for converting a FlexVol to a FlexGroup. The conversion is ONTAP CLI advanced-privilege only, with no REST equivalent. Instead the screen carries the prerequisites, the irreversibility, the treatment of existing snapshots, and what AWS recommends — a copy into a new FlexGroup with AWS DataSync rather than an in-place conversion. &lt;strong&gt;Writing down that something is absent, and why, is more useful than a button that cannot be pressed.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The free-space estimate for a clone split (&lt;code&gt;volume clone split estimate&lt;/code&gt;) is the same. It exists only in the CLI, so it cannot appear here. Rather than omitting it silently, the screen says why it is missing.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the confirmation screen states for an irreversible operation
&lt;/h2&gt;

&lt;p&gt;SnapLock and snapshot locking cannot be undone. "Are you sure?" on its own does not tell the person pressing it what becomes undeletable, so the screen states this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;The SnapLock type (compliance) can only be set at creation and cannot be changed or removed afterwards&lt;/li&gt;
&lt;li&gt;Under compliance, nobody can delete until retention expires. Not the account administrator, and not AWS&lt;/li&gt;
&lt;li&gt;While one unexpired WORM file remains, this volume, its SVM, and the &lt;strong&gt;file system&lt;/strong&gt; cannot be deleted&lt;/li&gt;
&lt;li&gt;While the file system cannot be deleted, it keeps billing&lt;/li&gt;
&lt;li&gt;A newly created empty volume can still be deleted. It stops being deletable when the first file is committed to WORM&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;The last two lines are the point. The effect of an irreversible operation does not stop at the resource; it propagates to the parent and to the bill. &lt;strong&gt;A test environment is the worst place to put a file system you cannot delete for six months.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Snapshot locking is written up with its asymmetry intact. A lock can only be extended, never shortened or released. The policy side is reversible: removing the retention, switching policies, or detaching stops new locks. But &lt;strong&gt;snapshots already locked stay until they expire.&lt;/strong&gt; Compressing that asymmetry into one sentence makes one half of it wrong.&lt;/p&gt;

&lt;p&gt;A retention count does not act as a ceiling either. Locked snapshots cannot be rotated out, so they accumulate past the count until expiry and consume the space. That is on the screen too.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3sm6phi7lmobhdzbnv9g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3sm6phi7lmobhdzbnv9g.png" alt="The snapshot lock confirmation, with the retention period and the date until which deletion is impossible" width="800" height="476"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Irreversible operations carry a type-to-confirm (the keyword typed in capitals). The API also requires &lt;code&gt;acknowledgeIrreversible&lt;/code&gt;, so a call that bypasses the UI passes the same check.&lt;/p&gt;

&lt;p&gt;What is written here is the technical blast radius of an operation. Which retention period satisfies your regulatory requirements is a legal and compliance judgement, and neither this article nor the portal stands in for it.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I did not measure
&lt;/h2&gt;

&lt;p&gt;"Unverified" is not "impossible". The boundary of this article, stated as a boundary:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Behaviour under concurrent users&lt;/td&gt;
&lt;td&gt;Unmeasured. Only single-operation latency was observed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Operations needing advanced privilege&lt;/td&gt;
&lt;td&gt;Out of scope. Absent from the REST API, so they cannot be on the screen&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enabling SnapLock compliance from the screen&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Deliberately not done.&lt;/strong&gt; Irreversible, and it would make a test file system undeletable for months&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-tier delegation with more Cognito groups&lt;/td&gt;
&lt;td&gt;Unverified. There is one tier, &lt;code&gt;storage-admin&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Long-term retention and search of the audit record&lt;/td&gt;
&lt;td&gt;Unverified. The record is kept; no retention design was done&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Part 5 covers what the real cluster refused while these 182 actions were being built: why creating a FlexGroup through the ONTAP REST API needs the target aggregate named, why a capacity rebalance never starts with ONTAP's defaults, and the two places the documentation and the running system disagreed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Putting operations on a screen was not a matter of listing features. &lt;strong&gt;Three paths already reach the file system and none of them went away, so what a screen adds is delegation and record, not reach.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Half the screen's job turned out to be saying what cannot be done rather than showing what can. Name the missing input on a button that cannot be pressed, write down absent features with the reason, and give irreversible operations their blast radius. Where a limit is measurable — the 5 GiB copy — using the measured value directly in the guard keeps the screen and the storage saying the same thing.&lt;/p&gt;

&lt;p&gt;And one display defect surfaced after the article was written. &lt;strong&gt;Until two panels were reconciled, the broken indicator looked correct.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The figures come from one specific environment and configuration and will differ with workload and setup. This article does not stand in for governance or regulatory judgement.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>fsxforontap</category>
      <category>amplify</category>
      <category>storage</category>
    </item>
    <item>
      <title>AWS Backup now copies FSx for ONTAP backups across Regions and accounts — a new option, measured Tokyo to Osaka</title>
      <dc:creator>Yoshiki Fujiwara(藤原 善基)@AWS Community Builder</dc:creator>
      <pubDate>Sat, 29 Aug 2026 05:02:38 +0000</pubDate>
      <link>https://dev.to/aws-builders/aws-backup-now-copies-fsx-for-ontap-backups-across-regions-and-accounts-a-new-option-measured-3f60</link>
      <guid>https://dev.to/aws-builders/aws-backup-now-copies-fsx-for-ontap-backups-across-regions-and-accounts-a-new-option-measured-3f60</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Update (2026-10)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Since September 2026, AWS Backup logically air-gapped vaults support Amazon FSx for NetApp ONTAP volume backups (&lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/09/aws-backup-air-gapped-vault-fsx-ontap/" rel="noopener noreferrer"&gt;What's New&lt;/a&gt;). The vault lives in an AWS Backup service-owned account, is always locked in Vault Lock compliance mode, can be shared with a recovery account through AWS RAM, and can be combined with multi-party approval. That gives recovery points a place outside your own account's management boundary, which the "As a ransomware layer" section below did not cover.&lt;/li&gt;
&lt;li&gt;Two prerequisites connect to this post. Backups of a file system encrypted with the AWS managed key (&lt;code&gt;aws/fsx&lt;/code&gt;) are not copied into the vault; the backup job does not fail but ends with the status "Completed with issues", and the recovery point stays in the standard vault only. So the customer managed key decision, made before the file system exists, applies here too. Only RW volumes are backed up.&lt;/li&gt;
&lt;li&gt;I have not tried the vault yet. The prerequisites and how to choose between isolation options are collected from AWS documentation in the &lt;a href="https://github.com/Yoshiki0705/FSx-for-ONTAP-Cyber-Resilience-Patterns/blob/main/docs/data-protection/aws-backup-logically-air-gapped-vault.md" rel="noopener noreferrer"&gt;logically air-gapped vault guide&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The Tokyo-to-Osaka copy measured in this post (within one account) is unchanged. If you plan data protection before a migration, AWS Storage Assessments treat isolation level (standard vault or logically air-gapped vault) as one of the cost dimensions (&lt;a href="https://aws.amazon.com/blogs/storage/planning-data-protection-before-migration-how-aws-storage-assessments-model-backup-and-disaster-recovery-costs/" rel="noopener noreferrer"&gt;AWS Storage Blog&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;How do you approach disaster recovery and backup for your file systems on AWS? Take FSx for ONTAP and a pair of Japanese Regions as the example: if Tokyo is primary, standing up a file system in Osaka and replicating into it continuously with SnapMirror is probably the first shape that comes to mind for anyone who has run ONTAP, or is about to evaluate it. Plenty of AWS users run exactly that in production, and as a way to hold availability it is still the best answer. RPO comes down from the 60 minutes AWS Backup offers at its shortest to SnapMirror's 5-minute minimum interval, switching between active and standby is breaking the relationship and promoting the destination, and cutting data back the other way is a &lt;code&gt;snapmirror resync&lt;/code&gt; in the reverse direction.&lt;/p&gt;

&lt;p&gt;The trouble was that "I just want the data sitting on the DR side, without operating a file system there until I actually restore" required the same shape. Amazon FSx for NetApp ONTAP volume backups were created and restored inside the file system's own Region and account, and could not cross a Region boundary. AWS Backup could already copy backups across Regions and accounts for FSx for Windows File Server, FSx for OpenZFS, and FSx for Lustre (&lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/04/backup-extends-fsx-support/" rel="noopener noreferrer"&gt;AWS Backup extends Amazon FSx support to 5 additional AWS Regions and expands cross-Region and cross-account copy to 14 AWS Regions&lt;/a&gt;, April 2026), but FSx for ONTAP was not in that set at the time.&lt;/p&gt;

&lt;p&gt;Then on 27 August 2026 that gap closed along the two paths below. &lt;strong&gt;One more shape became available to choose from in a backup strategy.&lt;/strong&gt; The two were announced separately.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/08/fsx-ontap-cross-region-backup-copy/" rel="noopener noreferrer"&gt;Amazon FSx for NetApp ONTAP now supports copying backups across AWS Regions and accounts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/08/aws-backup-amazon-fsx-netapp-cross-account-region/" rel="noopener noreferrer"&gt;AWS Backup adds cross-Region and cross-account backup support for Amazon FSx for NetApp ONTAP&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Scheduling&lt;/th&gt;
&lt;th&gt;Cross-account&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;FSx for ONTAP backup copy&lt;/strong&gt; (&lt;code&gt;CopyBackup&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Copies a volume backup to another Region&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;None.&lt;/strong&gt; Run it each time&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;No.&lt;/strong&gt; Same account only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;AWS Backup&lt;/strong&gt; (policy-based backup management service)&lt;/td&gt;
&lt;td&gt;Copies FSx for ONTAP backups to another Region and another account&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Yes.&lt;/strong&gt; Copy rules in a backup plan&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Yes.&lt;/strong&gt; Requires AWS Organizations&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So I copied a backup from Tokyo to Osaka, restored it at the destination, and confirmed the content matched. This post covers that procedure, along with the design and operational considerations that came with it.&lt;/p&gt;

&lt;p&gt;Up front:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AWS Backup now copies FSx for ONTAP backups to another Region and another account&lt;/strong&gt; (27 August 2026). The FSx for ONTAP &lt;code&gt;CopyBackup&lt;/code&gt; also reaches another Region, but it is &lt;strong&gt;same-account and manual&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Moving them to another account needs a CMK.&lt;/strong&gt; AWS managed keys cannot be used for a cross-account copy, and a file system's KMS key is set at creation — so it is &lt;strong&gt;a decision made before the file system exists&lt;/strong&gt; (below).&lt;/li&gt;
&lt;li&gt;What matters is that &lt;strong&gt;you no longer need a file system at the destination until you recover&lt;/strong&gt;. The restore target is still the Region the backup lives in, so &lt;strong&gt;creating that file system at recovery time lands on your RTO&lt;/strong&gt; — 20 minutes, measured (below).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CopyBackup&lt;/code&gt; has no scheduler. Recurring copies mean &lt;strong&gt;an AWS Backup backup plan&lt;/strong&gt; or your own automation. &lt;strong&gt;The AWS Backup copy and restore were measured too&lt;/strong&gt; — 6 m 31 s to 8 m 35 s to copy, 16 m 16 s to restore, at 9 MB.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;This does not replace SnapMirror.&lt;/strong&gt; If a minutes-level RPO and a failback procedure are requirements, SnapMirror still is the answer. &lt;strong&gt;Replication and copy are different operations&lt;/strong&gt; and leave different things at the destination (below).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Restore time is proportional to capacity.&lt;/strong&gt; My 13 minutes was a figure for 9.4 MiB. Restoring 10 TB on a minimum configuration is 22 hours at the floor, calculated from AWS published rates. I could not find a measured figure at 10 TB or above in public sources (below).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost inverts with scale.&lt;/strong&gt; A Single-AZ capacity pool is 47.6% of backup storage ($0.0238 against $0.050, retrieved from the Price List API), so past roughly 4.8 TB a standing destination file system is &lt;em&gt;lower&lt;/em&gt; monthly. Choosing to keep one running is not choosing the more expensive option (below).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Scope&lt;/strong&gt;: within one account, copying a volume backup from &lt;code&gt;ap-northeast-1&lt;/code&gt; to &lt;code&gt;ap-northeast-3&lt;/code&gt;, restoring it into a file system there, and verifying the content. &lt;strong&gt;Both &lt;code&gt;CopyBackup&lt;/code&gt; and AWS Backup were measured.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Out of scope&lt;/strong&gt;: cross-account copies (they require AWS Backup with AWS Organizations — I show the shape but &lt;strong&gt;did not measure it&lt;/strong&gt;), building or failing back a SnapMirror relationship, durations at production data volumes, and reconciling inter-Region transfer charges against a bill.&lt;/p&gt;




&lt;h2&gt;
  
  
  Shape of the setup
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp821j3l4dd2vmzwph7wb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp821j3l4dd2vmzwph7wb.png" alt="Copying a backup from Tokyo to Osaka. The FSx for ONTAP CopyBackup and an AWS Backup copy rule are separate paths, both restores need an existing file system and SVM in Osaka, and the Osaka side holds no file system until a recovery starts" width="800" height="490"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(Dark theme: &lt;a href="https://raw.githubusercontent.com/Yoshiki0705/FSx-for-ONTAP-Adoption-Playbook/00d596de51233a9238ed89d80d9604da242b3f3b/docs/_assets/images/png/backup-copy-cross-region-en-dark@2x.png" rel="noopener noreferrer"&gt;backup-copy-cross-region-en-dark@2x.png&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The right-hand side being empty in normal operation is the whole point.&lt;/strong&gt; With SnapMirror, that right-hand side holds an equivalent file system at all times.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Day to day&lt;/th&gt;
&lt;th&gt;When recovering&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tokyo (production)&lt;/td&gt;
&lt;td&gt;file system + RW volume + backups&lt;/td&gt;
&lt;td&gt;unchanged, or lost if the Region is affected&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Osaka (destination)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;the backup copy only.&lt;/strong&gt; No file system&lt;/td&gt;
&lt;td&gt;file system → SVM → restore into a new volume&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Billing&lt;/td&gt;
&lt;td&gt;Osaka pays backup storage only&lt;/td&gt;
&lt;td&gt;Osaka's file system starts billing once it exists&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Running the copy&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;CopyBackup&lt;/code&gt; each time, or an AWS Backup copy rule&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;On vendor neutrality&lt;/strong&gt;: this post evaluates FSx for ONTAP backup copies on technical grounds only. The section on existing backup products touches other options, but not in a framing where one is better than another. Each suits a different context, and the goal here is to record what actually works and what does not.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Test environment
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Date&lt;/td&gt;
&lt;td&gt;2026-08-28 (&lt;code&gt;CopyBackup&lt;/code&gt; path), 2026-08-29 (AWS Backup path)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runs&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;One per step (n=1).&lt;/strong&gt; Variance was not measured&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Interface&lt;/td&gt;
&lt;td&gt;AWS CLI v2 and &lt;strong&gt;the AWS Management Console&lt;/strong&gt;. Japanese locale for the &lt;code&gt;CopyBackup&lt;/code&gt; path, &lt;strong&gt;English locale for the AWS Backup path&lt;/strong&gt; (the setting at capture time). &lt;strong&gt;CloudFormation untested&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ap-northeast-1&lt;/code&gt;, first-generation &lt;code&gt;SINGLE_AZ_1&lt;/code&gt;, 1,024 GiB SSD, 128 MBps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Destination&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ap-northeast-3&lt;/code&gt;, same shape, created for this test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ONTAP version&lt;/td&gt;
&lt;td&gt;&lt;code&gt;9.17.1P7D1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source volume&lt;/td&gt;
&lt;td&gt;FlexVol RW, 1 GiB, tiering &lt;code&gt;NONE&lt;/code&gt;, storage efficiency off&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dataset&lt;/td&gt;
&lt;td&gt;5 files / 9.4 MiB (nested directory, symlink, UTF-8 filename, one file at &lt;code&gt;0640&lt;/code&gt;). The AWS Backup run used an equivalent 9,458,747 B set&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AWS Backup&lt;/td&gt;
&lt;td&gt;one vault per Region, one backup plan (one rule, one copy rule), selection scoped to the single verification volume ARN, existing &lt;code&gt;AWSBackupDefaultServiceRole&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KMS&lt;/td&gt;
&lt;td&gt;Defaults in each Region: &lt;code&gt;aws/fsx&lt;/code&gt; for the source file system, &lt;code&gt;alias/aws/backup&lt;/code&gt; (&lt;code&gt;KeyManager: AWS&lt;/code&gt;) for the AWS Backup vaults. &lt;strong&gt;CMKs untested, and this configuration cannot do a cross-account copy&lt;/strong&gt; (below)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clients&lt;/td&gt;
&lt;td&gt;one &lt;code&gt;t3.micro&lt;/code&gt; (AL2023) per Region, NFS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Actual spend&lt;/td&gt;
&lt;td&gt;~$0.55 for the &lt;code&gt;CopyBackup&lt;/code&gt; run (destination file system ran 1 h 24 m); under $2 for the AWS Backup run (destination file system ran ~1 h 21 m, two &lt;code&gt;t3.micro&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data used&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Synthetic data from &lt;code&gt;/dev/urandom&lt;/code&gt;.&lt;/strong&gt; No real or personal data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Not verified&lt;/td&gt;
&lt;td&gt;Cross-account copies (covered by the &lt;a href="https://community.netapp.com/community/discussion/468353/cross-region-and-cross-account-backup-for-amazon-fsx-for-netapp-ontap" rel="noopener noreferrer"&gt;earlier post&lt;/a&gt;), CMKs, copying &lt;code&gt;AUTOMATIC&lt;/code&gt; backups, incrementality, residue from a failed copy, deploying the CloudFormation template, restoring into an AD-joined SVM, reconciling inter-Region transfer charges, AWS Backup restore testing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Do not use these durations as an RTO basis for your environment.&lt;/strong&gt; They are measured against 9.4 MiB, where fixed overhead dominates.&lt;/p&gt;




&lt;h2&gt;
  
  
  When this fits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;You want a copy of your data in another Region but &lt;strong&gt;want to avoid running a file system there&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Compliance requires retaining a copy that is logically separated from production&lt;/li&gt;
&lt;li&gt;You want an isolated-account layer against credential compromise or a KMS key compromise&lt;/li&gt;
&lt;li&gt;Recovery measured in tens of minutes to hours is acceptable&lt;/li&gt;
&lt;li&gt;You already take volume backups and only want to add a destination for them&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SnapMirror already covers availability, and you want a separate retention layer detached from production&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When to look at something else for now
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RPO in minutes is required&lt;/strong&gt; (the guidance figure for backups is 60 minutes)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RTO in single-digit minutes is required&lt;/strong&gt; (creating the destination file system took 20 minutes)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need a documented path back to production&lt;/strong&gt; (backup copies have none — see below)&lt;/li&gt;
&lt;li&gt;FlexGroup volumes are in scope (copy is unsupported, and creation failed in my environment — see below)&lt;/li&gt;
&lt;li&gt;You need copies spanning China Regions, or cross-account copies in the China Regions (unsupported)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any of those apply, a SnapMirror-based design fits the requirement better. Running both together is also coherent, covered below.&lt;/p&gt;




&lt;h2&gt;
  
  
  The distinction that matters most — what changed is where a backup can live
&lt;/h2&gt;

&lt;p&gt;Blur this and the runbook comes out wrong.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Claim&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A backup can be stored in another Region&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;now possible&lt;/strong&gt; (&lt;code&gt;CopyBackup&lt;/code&gt; or AWS Backup)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A backup can be stored in another account&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;now possible, but only through AWS Backup.&lt;/strong&gt; The FSx for ONTAP &lt;code&gt;CopyBackup&lt;/code&gt; stays inside one account&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A backup can be restored to a file system in a &lt;strong&gt;different&lt;/strong&gt; Region than the backup&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;no&lt;/strong&gt; (unchanged)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A restore produces a new volume&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;unchanged.&lt;/strong&gt; There is no restore-in-place path&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;using-backups.html&lt;/code&gt; still states that a backup can only be restored to a file system in the Region where the backup is stored. That is not a contradiction — copying &lt;strong&gt;increases the set of Regions where a backup is stored&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So recovery in Osaka is three steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A copied backup is already in Osaka (this is what you prepare in advance)&lt;/li&gt;
&lt;li&gt;Create a file system and an SVM in Osaka&lt;/li&gt;
&lt;li&gt;Restore as a new volume&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Step 2 lands on your RTO.&lt;/strong&gt; Not paying for a standby file system means paying for its creation when you need it. That is the trade you are choosing.&lt;/p&gt;




&lt;h2&gt;
  
  
  How &lt;code&gt;CopyBackup&lt;/code&gt; and AWS Backup relate
&lt;/h2&gt;

&lt;p&gt;Two different things are called "copy a backup to another Region" here, so it is worth separating them first. &lt;strong&gt;They are separate mechanisms.&lt;/strong&gt; Neither calls the other, and their control planes and storage locations differ.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;FSx for ONTAP &lt;code&gt;CopyBackup&lt;/code&gt;
&lt;/th&gt;
&lt;th&gt;AWS Backup&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Whose API&lt;/td&gt;
&lt;td&gt;Amazon FSx (&lt;code&gt;fsx:CopyBackup&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;AWS Backup (backup plans / copy jobs)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What it copies&lt;/td&gt;
&lt;td&gt;An existing FSx for ONTAP volume backup, which must be &lt;code&gt;AVAILABLE&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;A recovery point AWS Backup took itself&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where the copy lands&lt;/td&gt;
&lt;td&gt;As an FSx for ONTAP backup in the destination Region&lt;/td&gt;
&lt;td&gt;An AWS Backup &lt;strong&gt;backup vault&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BackupType&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;USER_INITIATED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AWS_BACKUP&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scheduling&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;None.&lt;/strong&gt; Called each time&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Yes.&lt;/strong&gt; Backup rules and copy rules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-account&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;No&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Yes&lt;/strong&gt;, with AWS Organizations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Link back to the source&lt;/td&gt;
&lt;td&gt;Keeps &lt;code&gt;SourceBackupId&lt;/code&gt; and &lt;code&gt;SourceBackupRegion&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Followed through the recovery point ARN&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Here is where the two get conflated.&lt;/strong&gt; You cannot point &lt;code&gt;CopyBackup&lt;/code&gt; at an AWS Backup backup vault, and an AWS Backup copy rule does not call &lt;code&gt;fsx:CopyBackup&lt;/code&gt; on your behalf. Answering "I want &lt;code&gt;CopyBackup&lt;/code&gt; on a schedule" with "use AWS Backup" is right &lt;strong&gt;not because the same call can be scheduled, but because the same goal is reachable through a different mechanism.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The restore constraint, on the other hand, is shared.&lt;/strong&gt; Both paths ask you to pick an &lt;strong&gt;existing file system and SVM&lt;/strong&gt; in the destination Region and both produce a &lt;strong&gt;new volume&lt;/strong&gt;. The AWS Backup restore screen asks for a File system and a Storage virtual machine too (&lt;code&gt;documented&lt;/code&gt;). So the benefit — no standby file system while nothing is wrong — holds for AWS Backup as well, and so does the cost: &lt;strong&gt;file system creation lands on the RTO&lt;/strong&gt; either way.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;On terminology&lt;/strong&gt;: AWS Backup's own word is copy. How that differs from replication is the next section.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Replication and copy are different words for different things
&lt;/h2&gt;

&lt;p&gt;Two words get used for "keep the data in another Region": replication and copy. &lt;strong&gt;They are different operations, and what sits at the destination differs.&lt;/strong&gt; Without agreeing on that, a requirements conversation goes past itself.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;SnapMirror &lt;strong&gt;replication&lt;/strong&gt;
&lt;/th&gt;
&lt;th&gt;AWS Backup / &lt;code&gt;CopyBackup&lt;/code&gt; &lt;strong&gt;copy&lt;/strong&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What sits at the destination&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;A volume.&lt;/strong&gt; A &lt;code&gt;DP&lt;/code&gt; volume exists on the destination SVM&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;A backup (recovery point).&lt;/strong&gt; No volume&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;When the source changes&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;It follows.&lt;/strong&gt; Deltas are sent each schedule&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;It does not.&lt;/strong&gt; A point-in-time image; later changes wait for the next backup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;To use the destination&lt;/td&gt;
&lt;td&gt;Break the relationship and promote it to become writable&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;A restore is required, and what you get is a new volume&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Does the relationship persist&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Yes.&lt;/strong&gt; Having it in place is the normal state&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;No.&lt;/strong&gt; Each copy is an independent artefact&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What sets the RPO&lt;/td&gt;
&lt;td&gt;The replication schedule (down to &lt;strong&gt;5 minutes&lt;/strong&gt;)&lt;/td&gt;
&lt;td&gt;The backup interval (guidance: &lt;strong&gt;60 minutes&lt;/strong&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What the destination costs while idle&lt;/td&gt;
&lt;td&gt;File system capacity and throughput&lt;/td&gt;
&lt;td&gt;Backup storage only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Path back to production&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;snapmirror resync&lt;/code&gt; in the other direction&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;There is none&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;AWS's own wording splits the same way.&lt;/strong&gt; The AWS Backup console and documentation call this operation copy throughout — the screens read Copy jobs, Copy rule, &lt;code&gt;Copy type: Manual&lt;/code&gt; (screenshots above). The FSx for ONTAP API is named &lt;code&gt;CopyBackup&lt;/code&gt;. &lt;strong&gt;Replication is the word for mechanisms where the destination exists and tracks the source, as in Amazon S3 Cross-Region Replication.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So "replicate cross-Region with AWS Backup" reads as though &lt;strong&gt;a volume at the destination is tracking the source.&lt;/strong&gt; There isn't one. What is at the destination is a recovery point, and using it takes a restore. The difference lands directly on the RTO: replication promotes a volume that is already there, while a copy starts from &lt;strong&gt;creating a file system and an SVM and then restoring.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The reverse mistake is calling SnapMirror a backup. The destination tracks the source, so &lt;strong&gt;a file deleted on the source is gone from the destination's current state after the next transfer&lt;/strong&gt; — what remains is whatever the destination's snapshots still hold. Retaining generations is the job of snapshots or SnapVault, not of the replication itself.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;SnapVault is a third mechanism.&lt;/strong&gt; ONTAP's SnapVault is a relationship that accumulates generations at the destination. It rides the same transport as SnapMirror with a different purpose. &lt;strong&gt;The backup copies in this post do not create an ONTAP SnapVault relationship.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Which one suits which requirement is in choosing between this and SnapMirror.&lt;/p&gt;




&lt;h2&gt;
  
  
  A short glossary
&lt;/h2&gt;

&lt;p&gt;Only the terms this post uses. Skip if you already know them.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Term&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;th&gt;Why it matters here&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;File system&lt;/td&gt;
&lt;td&gt;The billing and performance unit. SSD capacity and throughput are provisioned here&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;This is what you no longer need at the destination&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SVM (Storage Virtual Machine)&lt;/td&gt;
&lt;td&gt;A logical server inside a file system, holding the NFS / SMB endpoints and auth config&lt;/td&gt;
&lt;td&gt;Required to restore. &lt;strong&gt;A file system alone is not enough&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Volume&lt;/td&gt;
&lt;td&gt;Where data lives, and the unit of backup and restore&lt;/td&gt;
&lt;td&gt;A restore produces a &lt;strong&gt;new&lt;/strong&gt; volume&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Junction path&lt;/td&gt;
&lt;td&gt;Where a volume attaches in the SVM namespace (e.g. &lt;code&gt;/dr_restored&lt;/code&gt;); the NFS mount path&lt;/td&gt;
&lt;td&gt;Specified at restore&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FlexVol / FlexGroup&lt;/td&gt;
&lt;td&gt;Volume styles. FlexVol sits on one aggregate; FlexGroup spreads across several for capacity and parallelism&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;FlexGroup backup copies are unsupported&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;RW&lt;/code&gt; / &lt;code&gt;DP&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Volume type. &lt;code&gt;RW&lt;/code&gt; is read-write, &lt;code&gt;DP&lt;/code&gt; is read-only and used for SnapMirror destinations&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;DP&lt;/code&gt; cannot be backed up. &lt;strong&gt;A restoring volume transiently reads as &lt;code&gt;DP&lt;/code&gt;&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tiering&lt;/td&gt;
&lt;td&gt;Moving cold data from SSD to the cheaper capacity pool. &lt;code&gt;NONE&lt;/code&gt; / &lt;code&gt;AUTO&lt;/code&gt; / &lt;code&gt;SNAPSHOT_ONLY&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Affects read latency right after a restore&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Snapshot vs backup&lt;/td&gt;
&lt;td&gt;A snapshot lives inside the file system; a backup is an independent copy outside it&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Only the backup can cross a Region boundary&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  If you run ONTAP on-premises
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ONTAP concept&lt;/th&gt;
&lt;th&gt;Relationship to this capability&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SnapMirror&lt;/td&gt;
&lt;td&gt;A different mechanism. Needs cluster peering and a standing destination file system&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SnapVault&lt;/td&gt;
&lt;td&gt;A different mechanism. Backup copies do not create a SnapVault relationship&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cluster peering&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Not required for backup copies.&lt;/strong&gt; Operationally this is the largest difference from SnapMirror&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Driving it from ONTAP CLI / REST&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Backup copies are an AWS-side feature with no corresponding ONTAP CLI command.&lt;/strong&gt; Use the AWS CLI, API, or console&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trace left on the volume&lt;/td&gt;
&lt;td&gt;A backup leaves a snapshot named &lt;code&gt;backup-&amp;lt;backup-id&amp;gt;&lt;/code&gt; on the volume&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Prerequisites and permissions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Condition&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Source backup&lt;/td&gt;
&lt;td&gt;must be &lt;code&gt;AVAILABLE&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source volume type&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;RW&lt;/code&gt; (&lt;code&gt;DP&lt;/code&gt;, LSM, and FlexCache destinations cannot be backed up at all)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Volume style&lt;/td&gt;
&lt;td&gt;FlexVol. &lt;strong&gt;FlexGroup copies are unsupported&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Partition&lt;/td&gt;
&lt;td&gt;Between commercial Regions, between the two China Regions, and between the two GovCloud (US) Regions — &lt;strong&gt;but not across those sets&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KMS&lt;/td&gt;
&lt;td&gt;Encrypted with a key in the destination Region. No parameter needed if the default key is fine&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;A backup whose source volume has already been deleted can still be copied&lt;/strong&gt; (measured). That is useful after an accidental deletion.&lt;/p&gt;

&lt;h3&gt;
  
  
  IAM
&lt;/h3&gt;

&lt;p&gt;The requester &lt;strong&gt;must be able to reach the backup in the source Region&lt;/strong&gt;. Following the shape in the AWS documentation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CopyBackupAcrossRegions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fsx:CopyBackup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:fsx:*:&amp;lt;your-account-id&amp;gt;:backup/*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DescribeAndRestore"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"fsx:DescribeBackups"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"fsx:DescribeFileSystems"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"fsx:DescribeStorageVirtualMachines"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"fsx:DescribeVolumes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"fsx:CreateVolumeFromBackup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"fsx:TagResource"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;"Resource": "*"&lt;/code&gt; on the second statement is a simplification for readability. &lt;strong&gt;Narrow it in production.&lt;/strong&gt; Some &lt;code&gt;Describe*&lt;/code&gt; actions do not support resource-level scoping, but &lt;code&gt;fsx:CreateVolumeFromBackup&lt;/code&gt; can be limited to the destination file system and SVM ARNs. To pin the destination Region, use the &lt;code&gt;aws:RequestedRegion&lt;/code&gt; condition key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Sid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"RestrictCopyDestination"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Deny"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fsx:CopyBackup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"StringNotEquals"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"aws:RequestedRegion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ap-northeast-3"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;If you have a data residency requirement, put this guard in IAM or an SCP.&lt;/strong&gt; "We have a rule that says not to" does not hold.&lt;/p&gt;

&lt;p&gt;With a customer managed key (CMK), you will additionally need permissions such as &lt;code&gt;kms:CreateGrant&lt;/code&gt; and &lt;code&gt;kms:DescribeKey&lt;/code&gt; on the destination-Region key. &lt;strong&gt;I used the default key, so I have not verified this.&lt;/strong&gt; If you are building on a CMK, prove it with a small volume first.&lt;/p&gt;




&lt;h2&gt;
  
  
  Values to replace with your own
&lt;/h2&gt;

&lt;p&gt;Every command below works once you substitute these.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Placeholder&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;How to get it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;SRC_REGION&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Source Region&lt;/td&gt;
&lt;td&gt;e.g. &lt;code&gt;ap-northeast-1&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;DST_REGION&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Destination Region&lt;/td&gt;
&lt;td&gt;e.g. &lt;code&gt;ap-northeast-3&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;SRC_VOLUME_ID&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The RW volume to back up&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;aws fsx describe-volumes --region &amp;lt;SRC_REGION&amp;gt; --query 'Volumes[?OntapConfiguration.OntapVolumeType==&lt;/code&gt;RW&lt;code&gt;].[VolumeId,Name]' --output table&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;SRC_BACKUP_ID&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The backup to copy&lt;/td&gt;
&lt;td&gt;output of step 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;DST_SUBNET_ID&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Subnet for the destination file system&lt;/td&gt;
&lt;td&gt;&lt;code&gt;aws ec2 describe-subnets --region &amp;lt;DST_REGION&amp;gt; --query 'Subnets[].[SubnetId,AvailabilityZone,CidrBlock]' --output table&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;DST_SG_ID&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Security group for the destination file system&lt;/td&gt;
&lt;td&gt;Must allow NFS / SMB. Sharing one SG with the client plus a self-referencing rule is enough&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;DST_FS_ID&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Destination file system&lt;/td&gt;
&lt;td&gt;output of step 4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;DST_SVM_ID&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Destination SVM&lt;/td&gt;
&lt;td&gt;output of step 5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;DST_VOLUME_SIZE_BYTES&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Restored volume size, in bytes&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Must be at least the data in the backup.&lt;/strong&gt; Reusing the source &lt;code&gt;SizeInBytes&lt;/code&gt; is safest. 1 GiB = &lt;code&gt;1073741824&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two notes on sizing. &lt;strong&gt;The destination file system's SSD capacity must exceed the data being restored&lt;/strong&gt; — restored data is written to SSD first. And &lt;strong&gt;a FlexVol cannot span more than one HA pair.&lt;/strong&gt; For a large volume, settle the destination SSD capacity and generation (second-generation Single-AZ can add HA pairs) before you start.&lt;/p&gt;




&lt;h2&gt;
  
  
  Walkthrough — Tokyo to Osaka
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Create a backup (skip if you already have one)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws fsx create-backup &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; &amp;lt;SRC_REGION&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--volume-id&lt;/span&gt; &amp;lt;SRC_VOLUME_ID&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tags&lt;/span&gt; &lt;span class="nv"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Name,Value&lt;span class="o"&gt;=&lt;/span&gt;dr-source &lt;span class="nv"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Purpose,Value&lt;span class="o"&gt;=&lt;/span&gt;cross-region-copy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Backup.{Id:BackupId,Life:Lifecycle}'&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait for &lt;code&gt;AVAILABLE&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws fsx describe-backups &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; &amp;lt;SRC_REGION&amp;gt; &lt;span class="nt"&gt;--backup-ids&lt;/span&gt; &amp;lt;SRC_BACKUP_ID&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Backups[0].{Life:Lifecycle,Pct:ProgressPercent}'&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Copy it
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;--region&lt;/code&gt; is the destination; &lt;code&gt;--source-region&lt;/code&gt; is where the backup is now.&lt;/strong&gt; This reads backwards at first — the copy is requested against the destination Region.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws fsx copy-backup &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; &amp;lt;DST_REGION&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--source-backup-id&lt;/span&gt; &amp;lt;SRC_BACKUP_ID&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--source-region&lt;/span&gt; &amp;lt;SRC_REGION&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--copy-tags&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tags&lt;/span&gt; &lt;span class="nv"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Name,Value&lt;span class="o"&gt;=&lt;/span&gt;dr-copy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Backup.{Id:BackupId,Life:Lifecycle,Src:SourceBackupId,SrcRegion:SourceBackupRegion}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add &lt;code&gt;--kms-key-id &amp;lt;destination-region-key-arn&amp;gt;&lt;/code&gt; for a CMK. &lt;strong&gt;Incrementality is conditional on using the same KMS key, so changing keys mid-stream makes the next copy full.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Watch progress:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws fsx describe-backups &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; &amp;lt;DST_REGION&amp;gt; &lt;span class="nt"&gt;--backup-ids&lt;/span&gt; &amp;lt;DST_BACKUP_ID&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Backups[0].{Life:Lifecycle,Pct:ProgressPercent}'&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;COPYING&lt;/code&gt; → &lt;code&gt;AVAILABLE&lt;/code&gt; means done. Mine took 7 m 15 s for a 9.4 MiB volume.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day-to-day operation ends here.&lt;/strong&gt; Steps 3 to 6 run only when you are recovering.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. [Recovery] Check which generation the destination Region offers
&lt;/h3&gt;

&lt;p&gt;Check the available deployment types &lt;strong&gt;before&lt;/strong&gt; you need them. If first generation (&lt;code&gt;SINGLE_AZ_1&lt;/code&gt;) is available, the minimum throughput is 128 MBps; where only second generation exists it is 384 MBps, which makes standby cost roughly 1.9× higher.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SINGLE_AZ_1&lt;/code&gt; was accepted in Osaka (measured 2026-08-28). &lt;strong&gt;This varies by Region — confirm it for your destination before writing the runbook.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. [Recovery] Create the destination file system
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Billing starts here.&lt;/strong&gt; Even at the minimum, Osaka costs roughly $0.37 per hour, and forgetting it runs about $270 a month. If you are doing this as a drill, &lt;strong&gt;open the teardown commands from step 8 first.&lt;/strong&gt; During my own run I mistyped an option on &lt;code&gt;delete-file-system&lt;/code&gt; and polled for 8 minutes while the command had already failed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;This is the 20 minutes that lands on RTO.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws fsx create-file-system &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; &amp;lt;DST_REGION&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--file-system-type&lt;/span&gt; ONTAP &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--storage-capacity&lt;/span&gt; 1024 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--storage-type&lt;/span&gt; SSD &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--subnet-ids&lt;/span&gt; &amp;lt;DST_SUBNET_ID&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--security-group-ids&lt;/span&gt; &amp;lt;DST_SG_ID&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--ontap-configuration&lt;/span&gt; &lt;span class="s1"&gt;'{
    "DeploymentType": "SINGLE_AZ_1",
    "ThroughputCapacity": 128,
    "PreferredSubnetId": "&amp;lt;DST_SUBNET_ID&amp;gt;"
  }'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tags&lt;/span&gt; &lt;span class="nv"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Name,Value&lt;span class="o"&gt;=&lt;/span&gt;dr-restore-target &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'FileSystem.{Id:FileSystemId,Life:Lifecycle}'&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;1,024 GiB and 128 MBps are the &lt;strong&gt;first-generation minimums&lt;/strong&gt;. Raise them if your restore exceeds that. &lt;strong&gt;A restore pauses when SSD space runs out&lt;/strong&gt; and resumes automatically once space is available.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. [Recovery] Create the SVM
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws fsx create-storage-virtual-machine &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; &amp;lt;DST_REGION&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--file-system-id&lt;/span&gt; &amp;lt;DST_FS_ID&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; dr_svm &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--root-volume-security-style&lt;/span&gt; UNIX &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'StorageVirtualMachine.{Id:StorageVirtualMachineId,Life:Lifecycle}'&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For SMB you also need the Active Directory join here. &lt;strong&gt;If AD join is part of your recovery, verify separately that the destination Region can reach your domain controllers, and budget the time.&lt;/strong&gt; My verification was NFS only.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. [Recovery] Restore
&lt;/h3&gt;

&lt;p&gt;A restore always produces a &lt;strong&gt;new volume&lt;/strong&gt;. There is no in-place path.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws fsx create-volume-from-backup &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; &amp;lt;DST_REGION&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--backup-id&lt;/span&gt; &amp;lt;DST_BACKUP_ID&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; dr_restored &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--ontap-configuration&lt;/span&gt; &lt;span class="s1"&gt;'{
    "StorageVirtualMachineId": "&amp;lt;DST_SVM_ID&amp;gt;",
    "JunctionPath": "/dr_restored",
    "SizeInBytes": &amp;lt;DST_VOLUME_SIZE_BYTES&amp;gt;,
    "OntapVolumeType": "RW",
    "TieringPolicy": { "Name": "NONE" }
  }'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Volume.{Id:VolumeId,Life:Lifecycle}'&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;TieringPolicy: NONE&lt;/code&gt; follows the AWS recommendation for consistent read latency after a restore — it assumes you wait for the data to land on SSD before accessing it. Change it if you want the capacity pool.&lt;/p&gt;

&lt;p&gt;Mine reached &lt;code&gt;CREATED&lt;/code&gt; in 13 m 21 s for 9.4 MiB.&lt;/p&gt;

&lt;p&gt;Three conditions drive restore and post-restore performance. &lt;strong&gt;Check these when it feels slow:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Condition&lt;/th&gt;
&lt;th&gt;Detail&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SSD capacity&lt;/td&gt;
&lt;td&gt;Restored data is &lt;strong&gt;written to SSD first&lt;/strong&gt;. The restore pauses when space runs out and resumes when it frees up&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Generation&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Second generation is readable during a restore&lt;/strong&gt; (after metadata loads). &lt;strong&gt;First generation makes you wait&lt;/strong&gt; for completion — mine was first generation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Background priority&lt;/td&gt;
&lt;td&gt;Backup and restore are &lt;strong&gt;lower priority than client I/O&lt;/strong&gt; and consume unused throughput capacity. They progress slowly while production I/O is saturated&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The third one has an operational consequence: &lt;strong&gt;overlapping backups with a nightly batch window slows both.&lt;/strong&gt; Separate the backup window from your peak.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Verify the content
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Skip this and you cannot claim you restored anything.&lt;/strong&gt; Order matters: &lt;strong&gt;take the hashes before you create the backup.&lt;/strong&gt; Taking them afterwards mixes in every change since, and you can no longer separate that from a copy problem.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# On the source, BEFORE creating the backup&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; /mnt/src &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; find &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="nt"&gt;-exec&lt;/span&gt; &lt;span class="nb"&gt;sha256sum&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt; + | &lt;span class="nv"&gt;LC_ALL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;C &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-k2&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/before.txt

&lt;span class="c"&gt;# On the restored volume&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; /mnt/restored &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; find &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="nt"&gt;-exec&lt;/span&gt; &lt;span class="nb"&gt;sha256sum&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt; + | &lt;span class="nv"&gt;LC_ALL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;C &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-k2&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/after.txt
diff /tmp/before.txt /tmp/after.txt &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"MATCH"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check permissions and symlinks too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-mindepth&lt;/span&gt; 1 &lt;span class="nt"&gt;-printf&lt;/span&gt; &lt;span class="s2"&gt;"%y %m %s %p&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nv"&gt;LC_ALL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;C &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-k4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All five sha256 digests matched in my run, and mode &lt;code&gt;0640&lt;/code&gt;, the symlink target, and a UTF-8 (Japanese) filename all survived. I also used the ordering deliberately: &lt;strong&gt;a 2 MiB file added after the backup was absent from the restored volume&lt;/strong&gt;, which is what makes it a point-in-time image rather than a mirror.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Delete what the test created
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Forgetting the destination file system costs roughly $270 a month.&lt;/strong&gt; The order matters.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. volume (do not let it create a final backup)&lt;/span&gt;
aws fsx delete-volume &lt;span class="nt"&gt;--region&lt;/span&gt; &amp;lt;DST_REGION&amp;gt; &lt;span class="nt"&gt;--volume-id&lt;/span&gt; &amp;lt;DST_VOLUME_ID&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--ontap-configuration&lt;/span&gt; &lt;span class="s1"&gt;'{"SkipFinalBackup":true}'&lt;/span&gt;

&lt;span class="c"&gt;# 2. SVM (after the volumes are gone)&lt;/span&gt;
aws fsx delete-storage-virtual-machine &lt;span class="nt"&gt;--region&lt;/span&gt; &amp;lt;DST_REGION&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--storage-virtual-machine-id&lt;/span&gt; &amp;lt;DST_SVM_ID&amp;gt;

&lt;span class="c"&gt;# 3. file system (after the SVM is gone)&lt;/span&gt;
aws fsx delete-file-system &lt;span class="nt"&gt;--region&lt;/span&gt; &amp;lt;DST_REGION&amp;gt; &lt;span class="nt"&gt;--file-system-id&lt;/span&gt; &amp;lt;DST_FS_ID&amp;gt;

&lt;span class="c"&gt;# 4. the copied backup, if you no longer need it&lt;/span&gt;
aws fsx delete-backup &lt;span class="nt"&gt;--region&lt;/span&gt; &amp;lt;DST_REGION&amp;gt; &lt;span class="nt"&gt;--backup-id&lt;/span&gt; &amp;lt;DST_BACKUP_ID&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without &lt;code&gt;SkipFinalBackup&lt;/code&gt; a final backup is created, and &lt;strong&gt;that backup then blocks the next deletion.&lt;/strong&gt; I have been stuck on this before.&lt;/p&gt;

&lt;p&gt;And &lt;strong&gt;confirm the deletion through the API.&lt;/strong&gt; During this run I passed an unsupported option to &lt;code&gt;delete-file-system&lt;/code&gt; and polled for 8 minutes while the command had already failed. Waiting without reading the response only advances the bill.&lt;/p&gt;




&lt;h2&gt;
  
  
  Measured through AWS Backup as well
&lt;/h2&gt;

&lt;p&gt;Everything above is the &lt;code&gt;CopyBackup&lt;/code&gt; path. &lt;strong&gt;The same route, Tokyo to Osaka, was also measured through AWS Backup.&lt;/strong&gt; That is the path that carries scheduling and cross-account copies, so its behaviour is what matters for production.&lt;/p&gt;

&lt;p&gt;Two ways round. An on-demand backup job followed by an on-demand copy job, and a backup plan with a copy rule left to fire on its schedule. Both used the same source volume (1 GiB, 9,458,747 bytes, 5 files).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Elapsed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;On-demand backup job&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4 m 02 s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;On-demand copy job, Tokyo to Osaka&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8 m 35 s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plan-triggered backup job&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;30 m 06 s&lt;/strong&gt;, of which about 24 minutes was the start window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Copy rule copy job (automatic)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;6 m 31 s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Restore from the AWS Backup console&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;16 m 16 s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The copy rule fired without being touched.&lt;/strong&gt; The schedule came round, a job was created, the backup completed, and a copy job followed on its own to put a recovery point in the Osaka backup vault.&lt;/p&gt;

&lt;h3&gt;
  
  
  A plan-triggered job waits in &lt;code&gt;CREATED&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;This changes an operational assumption.&lt;/strong&gt; The on-demand job went to &lt;code&gt;RUNNING&lt;/code&gt; immediately. The plan-triggered one stayed &lt;strong&gt;&lt;code&gt;CREATED&lt;/code&gt; for about 24 minutes.&lt;/strong&gt; When it starts inside the start window — 60 minutes in this configuration — is AWS Backup's decision.&lt;/p&gt;

&lt;p&gt;So a runbook that reads "the scheduled time has passed, therefore the backup exists" is wrong by the length of that window. &lt;strong&gt;Confirm a generation by job state or by the recovery point, not by the clock.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The recovery point in Osaka points at the source
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyopifun6mf1w8fhkjz86.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyopifun6mf1w8fhkjz86.png" alt="The Osaka backup vault. One recovery point, Completed, and the resource ID refers to the source file system and volume" width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Resource ID column names the &lt;strong&gt;Tokyo&lt;/strong&gt; file system and volume. It sits in the Osaka vault and points at where it came from.&lt;/p&gt;

&lt;p&gt;Open the recovery point and the copy is visible as such.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fem6xguxrqfprktl1fxzn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fem6xguxrqfprktl1fxzn.png" alt="Recovery point detail. Copy type Manual, Source region Asia Pacific (Tokyo), Storage tier Warm" width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Source region reads Asia Pacific (Tokyo).&lt;/strong&gt; That is the evidence this artefact came from a cross-Region copy. &lt;code&gt;Copy type&lt;/code&gt; is &lt;code&gt;Manual&lt;/code&gt; because this one was run on demand; the copy-rule one arrives as a separate recovery point.&lt;/p&gt;

&lt;h3&gt;
  
  
  The restore form makes you pick a destination file system and SVM
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0zd3fp3nqjjy3ay4xnoy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0zd3fp3nqjjy3ay4xnoy.png" alt="The AWS Backup restore form. File system and Storage virtual machine are unselected, and the File system ID at the top is the source file system" width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The "File system ID" at the top is the source.&lt;/strong&gt; The destination is chosen separately, below. Read the two as the same thing and the restore target looks already decided. It is not.&lt;/p&gt;

&lt;p&gt;Open the dropdown and &lt;strong&gt;only file systems in the destination Region are listed.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz0yiunow7eccg2nnbh41.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz0yiunow7eccg2nnbh41.png" alt="The File system dropdown. Only the Osaka file system is offered" width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With no file system in Osaka this list is empty. &lt;strong&gt;Exactly the same constraint as the &lt;code&gt;CopyBackup&lt;/code&gt; path: not holding a destination day to day is paid for here.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Storage efficiency is checked by default
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frxxecy4cvtol2s3eo8j4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frxxecy4cvtol2s3eo8j4.png" alt="The filled restore form. Volume size is 1024 MiB and " width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Volume size is pre-filled from the source (1024 MiB).&lt;/strong&gt; The FSx for ONTAP console defaults the same field to 1 TiB (below), so AWS Backup is the more predictable of the two here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Enable storage efficiency" is checked by default.&lt;/strong&gt; The source volume had it off, and the restored volume came back with &lt;code&gt;StorageEfficiencyEnabled: true&lt;/code&gt;. The FSx for ONTAP console restore form &lt;strong&gt;pre-selects the source value&lt;/strong&gt;, so &lt;strong&gt;the two consoles behave differently.&lt;/strong&gt; Uncheck it to match the source.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Attribute&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;After the AWS Backup restore&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OntapVolumeType&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;RW&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;RW&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SizeInBytes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1 GiB&lt;/td&gt;
&lt;td&gt;1 GiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TieringPolicy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;NONE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;NONE&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;StorageEfficiencyEnabled&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;code&gt;true&lt;/code&gt;&lt;/strong&gt; (the form's default)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SecurityStyle&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;UNIX&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;empty&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;SecurityStyle&lt;/code&gt; coming back empty had been seen once on the &lt;code&gt;CopyBackup&lt;/code&gt; path. &lt;strong&gt;It reproduced through AWS Backup, so it is no longer a single-path observation.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The content matched
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgxjl2zc7g1jbb0ubjth8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgxjl2zc7g1jbb0ubjth8.png" alt="The restore job. Completed, Restore time 16 minutes" width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Mounted from an EC2 instance in Osaka and compared. &lt;strong&gt;All five sha256 values matched&lt;/strong&gt;, and the symlink target, the &lt;code&gt;0640&lt;/code&gt; mode, the UTF-8 filename, the nested directories and the mtimes were preserved.&lt;/p&gt;

&lt;p&gt;The restored volume carries &lt;code&gt;.snapshot/backup-&amp;lt;id&amp;gt;/&lt;/code&gt;. That &lt;code&gt;&amp;lt;id&amp;gt;&lt;/code&gt; is the &lt;strong&gt;source&lt;/strong&gt; FSx for ONTAP backup, not the Osaka recovery point ID. The destination volume's default snapshot policy starts running too, so &lt;code&gt;hourly.*&lt;/code&gt; snapshots appear beside it.&lt;/p&gt;

&lt;h3&gt;
  
  
  What cannot be monitored, and what catches you on teardown
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;BackupSizeInBytes&lt;/code&gt; was 0&lt;/strong&gt; on both backup jobs. Not a size to alert on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Restore progress read &lt;code&gt;0.00%&lt;/code&gt; for the whole 16 minutes, then &lt;code&gt;100%&lt;/code&gt;.&lt;/strong&gt; Anything that estimates remaining time from the percentage will not work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbl7x59l833d1g0ubrmj9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbl7x59l833d1g0ubrmj9.png" alt="The copy jobs list. Two jobs, one on-demand and one from the copy rule, both Completed" width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One thing caught me on teardown. &lt;strong&gt;A vault cannot be deleted until an &lt;code&gt;EXPIRED&lt;/code&gt; recovery point is fully gone.&lt;/strong&gt; After &lt;code&gt;delete-recovery-point&lt;/code&gt; the entry stayed listed as &lt;code&gt;EXPIRED&lt;/code&gt; for about six minutes, and &lt;code&gt;delete-backup-vault&lt;/code&gt; refused with "contains recovery points" throughout. The underlying FSx for ONTAP backup was still &lt;code&gt;AVAILABLE&lt;/code&gt; at that point. &lt;strong&gt;Teardown automation has to poll; taking the delete request's success as done leaves you stuck.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;On the console language&lt;/strong&gt;: the screens in this section are in English. That was the setting at capture time; the other sections show the FSx for ONTAP console in Japanese. Field names are quoted in English either way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cross-account copies were not measured.&lt;/strong&gt; They require AWS Organizations, so this verification stayed inside one account.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  For the cross-account half — an earlier post, and what this one adds
&lt;/h2&gt;

&lt;p&gt;There is a post on the NetApp Community that goes further on cross-account copies than this one does.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://community.netapp.com/community/discussion/468353/cross-region-and-cross-account-backup-for-amazon-fsx-for-netapp-ontap" rel="noopener noreferrer"&gt;Cross-Region and Cross-Account Backup for Amazon FSx for NetApp ONTAP&lt;/a&gt; (Jordan Roth, NetApp Community)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;It walks the cross-account path I did not measure, having actually run it.&lt;/strong&gt; It sets out the protection layers (Snapshot copies, Multi-AZ HA, SnapMirror, AWS Backup copies), four architecture patterns — fan-in into an isolated vault account, fan-out to several, crossing Region and account in one hop, and a clean room for incident work — and the organization, key and IAM prerequisites behind them. &lt;strong&gt;If cross-account is going to production, read that first.&lt;/strong&gt; What follows here is only where it meets my own measurements.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this post was missing — cross-account needs a customer managed key
&lt;/h3&gt;

&lt;p&gt;This is the one that matters. &lt;strong&gt;A cross-account copy requires a customer managed key (CMK).&lt;/strong&gt; The AWS Backup documentation states that for resource types not fully managed by AWS Backup, &lt;strong&gt;cross-account copy with AWS managed keys is not supported&lt;/strong&gt;, because an AWS managed key's key policy is immutable and cannot be shared across accounts (&lt;a href="https://docs.aws.amazon.com/aws-backup/latest/devguide/encryption.html" rel="noopener noreferrer"&gt;Encryption for backups in AWS Backup&lt;/a&gt;). FSx for ONTAP sits on the "not fully managed" side.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My verification did not meet that condition.&lt;/strong&gt; The destination vault was created with defaults, so its key was &lt;code&gt;alias/aws/backup&lt;/code&gt; (&lt;code&gt;KeyManager: AWS&lt;/code&gt;). &lt;strong&gt;The cross-Region copy still succeeded.&lt;/strong&gt; That is looser than the documentation's general statement, and it is the reason for measuring rather than assuming.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Key used&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cross-Region copy (Tokyo to Osaka, one account)&lt;/td&gt;
&lt;td&gt;Defaults on both the source file system and the vaults (&lt;code&gt;aws/fsx&lt;/code&gt; / &lt;code&gt;aws/backup&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Succeeded&lt;/strong&gt; (measured)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-account copy&lt;/td&gt;
&lt;td&gt;AWS managed keys cannot be used&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Not measured.&lt;/strong&gt; Unsupported per the documentation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;There is a design consequence.&lt;/strong&gt; A file system's KMS key is chosen at creation, and &lt;code&gt;update-file-system&lt;/code&gt; has no parameter for changing it (checked in the AWS CLI). So &lt;strong&gt;if a cross-account copy might ever be needed, the CMK decision happens before the file system exists.&lt;/strong&gt; It is not the kind of thing to switch on later. The destination vault has its own constraint: the default vault cannot be used, because its key cannot be shared.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Region exceptions are listed individually
&lt;/h3&gt;

&lt;p&gt;Earlier I wrote "commercial Regions where both services are available". &lt;a href="https://docs.aws.amazon.com/aws-backup/latest/devguide/backup-feature-availability.html" rel="noopener noreferrer"&gt;AWS Backup feature availability&lt;/a&gt; lists the exceptions one by one.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Exception&lt;/th&gt;
&lt;th&gt;Scope&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cross-Region copy not supported&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Middle East (Bahrain), Middle East (UAE) — for all four of FSx for ONTAP, Lustre, Windows File Server and OpenZFS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cross-Region and cross-account copy not supported&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Asia Pacific (New Zealand), China (Beijing), China (Ningxia) — FSx for ONTAP&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The China Regions need reading carefully.&lt;/strong&gt; The AWS Backup document history carries an entry dated the same 27 August 2026 saying cross-account backup copy and cross-account management are now supported in the China Regions. That is about AWS Backup generally; &lt;strong&gt;FSx for ONTAP is excluded there&lt;/strong&gt;, per the table above. Merge the two same-day announcements and you reach the opposite conclusion.&lt;/p&gt;

&lt;h3&gt;
  
  
  On "what was possible before", that post and I read it differently
&lt;/h3&gt;

&lt;p&gt;Its table of changes records &lt;strong&gt;cross-Region &lt;code&gt;CopyBackup&lt;/code&gt; within one account as already available&lt;/strong&gt; before the launch. This post has been saying a backup could not cross a Region boundary before. Rather than assert, here is what I checked.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What I checked&lt;/th&gt;
&lt;th&gt;What it says&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ONTAP User Guide, "Protecting your data with volume backups" (&lt;a href="https://web.archive.org/web/20260705094204/https://docs.aws.amazon.com/fsx/latest/ONTAPGuide/using-backups.html" rel="noopener noreferrer"&gt;archived 2026-07-05&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;The sub-page list has &lt;strong&gt;no "Copying backups"&lt;/strong&gt; — only "Copying tags to backups". The body says a backup restores to a file system in the same Region where it is stored&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;copy-backups.html&lt;/code&gt; in the ONTAP User Guide&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;No snapshot at all&lt;/strong&gt; in the Internet Archive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The FSx for ONTAP What's New&lt;/td&gt;
&lt;td&gt;"Previously, you could create and restore backups in the same Region and account as your file system"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;What I cannot test now is whether the &lt;code&gt;CopyBackup&lt;/code&gt; API would have accepted an ONTAP backup before the launch.&lt;/strong&gt; That API is not specific to FSx for ONTAP, and has supported cross-Region copies for FSx for Windows File Server and FSx for Lustre for years, so reading the API reference alone and concluding it was already possible is a reasonable reading. &lt;strong&gt;Documentation not describing something is not the same as an API refusing it&lt;/strong&gt;, so I am leaving that unverified.&lt;/p&gt;

&lt;p&gt;For a reader today the practical answer is the same either way. &lt;strong&gt;What is unambiguously new is the AWS Backup half&lt;/strong&gt;: cross-Region copy through AWS Backup, and any cross-account copy, from 27 August 2026.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this post adds
&lt;/h3&gt;

&lt;p&gt;The parts that do not overlap.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Here&lt;/th&gt;
&lt;th&gt;What&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Measured durations&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Backup, copy and restore on both paths, to the second — including a plan-triggered job waiting about 24 minutes in the start window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Two consoles behaving differently&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The AWS Backup restore form checks storage efficiency by default; the FSx for ONTAP one pre-selects the source value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;A single-path observation reproduced&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;SecurityStyle&lt;/code&gt; coming back empty&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;What cannot be monitored&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;BackupSizeInBytes&lt;/code&gt; reads 0; restore progress sits at &lt;code&gt;0.00%&lt;/code&gt; for 16 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Where teardown sticks&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;An &lt;code&gt;EXPIRED&lt;/code&gt; recovery point blocks vault deletion for minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cross-Region copy on default keys&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;It works with &lt;code&gt;aws/backup&lt;/code&gt; (above)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Replication and copy as terms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Covered in its own section
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Regional DR inside one country&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tokyo to Osaka, with the cost of keeping data in-country&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The two things that caught me out
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. A restoring volume reads as &lt;code&gt;DP&lt;/code&gt;, not &lt;code&gt;RW&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;I read the attributes right after the restore reached &lt;code&gt;CREATED&lt;/code&gt; and got &lt;code&gt;OntapVolumeType: DP&lt;/code&gt;. DP volumes are read-only and cannot be backed up. I started writing "a restore produces a read-only volume".&lt;/p&gt;

&lt;p&gt;Then I tried &lt;code&gt;touch&lt;/code&gt; from the client. &lt;strong&gt;It succeeded.&lt;/strong&gt; A re-read returned &lt;code&gt;RW&lt;/code&gt;. To be sure, I ran a second restore passing &lt;code&gt;OntapVolumeType: RW&lt;/code&gt; explicitly — and it &lt;em&gt;also&lt;/em&gt; reported &lt;code&gt;DP&lt;/code&gt; while &lt;code&gt;CREATING&lt;/code&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;th&gt;&lt;code&gt;OntapVolumeType&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Restoring (&lt;code&gt;CREATING&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DP&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Restore complete (&lt;code&gt;CREATED&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;RW&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;It is transient. &lt;strong&gt;If monitoring or automation reads this field, it sees &lt;code&gt;DP&lt;/code&gt; during a restore.&lt;/strong&gt; Since DP volumes cannot be backed up, a naive "back up as soon as the restore finishes" step behaves differently depending on when it reads. Gate on &lt;code&gt;Lifecycle&lt;/code&gt; reaching &lt;code&gt;CREATED&lt;/code&gt; first.&lt;/p&gt;

&lt;p&gt;Without the write probe I would have published the wrong claim.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. FlexGroup backup creation failed asynchronously
&lt;/h3&gt;

&lt;p&gt;The documented limitation is that &lt;strong&gt;copying&lt;/strong&gt; backups of FlexGroup volumes is unsupported. It attaches to the copy and says nothing about creation. I misread it and built the test on the creation side.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CreateBackup&lt;/code&gt; against a FlexGroup volume &lt;strong&gt;was accepted and returned &lt;code&gt;CREATING&lt;/code&gt;&lt;/strong&gt;, then went &lt;code&gt;FAILED&lt;/code&gt; about 30 seconds later. The message names no cause:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Backup failed. Please delete the backup and try again.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a control, a FlexVol RW backup created in the same session with the same credentials succeeded — so this is not permissions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But this is one observation on one volume.&lt;/strong&gt; I am not claiming FlexGroup backups cannot be created. AWS documentation describes the &lt;em&gt;restore&lt;/em&gt; behaviour of FlexGroup backups (adding constituents when the HA pair count differs), which does not read as creation being unsupported. Separately it states only that a &lt;strong&gt;SnapLock&lt;/strong&gt; FlexGroup volume cannot be backed up. My target was a non-SnapLock FlexGroup, single constituent, one aggregate, 113 GiB provisioned.&lt;/p&gt;

&lt;p&gt;The consequence: &lt;strong&gt;no &lt;code&gt;AVAILABLE&lt;/code&gt; FlexGroup backup was produced, so the documented copy restriction was never exercised.&lt;/strong&gt; If FlexGroup is in scope, verify it &lt;strong&gt;from the creation side&lt;/strong&gt; in your own environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The attribute I called "not carried over" was a CLI default
&lt;/h3&gt;

&lt;p&gt;Seeing &lt;code&gt;StorageEfficiencyEnabled: true&lt;/code&gt; on the restored volume, I wrote that storage efficiency is not carried over by a restore. &lt;strong&gt;That was wrong too.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Running the same restore through the console showed the field &lt;strong&gt;pre-selected to the source value&lt;/strong&gt; (disabled), and it restored as &lt;code&gt;false&lt;/code&gt;. The initial &lt;code&gt;true&lt;/code&gt; came from omitting the field in the CLI &lt;code&gt;--ontap-configuration&lt;/code&gt;. It is an &lt;strong&gt;API default when omitted&lt;/strong&gt;, not restore behaviour. State it explicitly if you script the restore.&lt;/p&gt;

&lt;p&gt;That is twice in one exercise that I nearly published a behaviour inferred from a single observation. &lt;strong&gt;Running the same operation through a second interface is what exposed both.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Attribute&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;Restored&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;StorageEfficiencyEnabled&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;code&gt;true&lt;/code&gt; if omitted in the CLI&lt;/strong&gt;, &lt;code&gt;false&lt;/code&gt; when stated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SecurityStyle&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;UNIX&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;empty in the API response (both CLI and console; UNIX mode bits behaved correctly over NFS)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Check storage efficiency after a restore&lt;/strong&gt; — omitting the field enables it even when the source had it off. The empty &lt;code&gt;SecurityStyle&lt;/code&gt; reproduced across both paths, but against the same UNIX-style source; other security styles were not tested.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the console shows that the CLI does not
&lt;/h2&gt;

&lt;p&gt;Four behaviours only visible in the console.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A note on the screenshots&lt;/strong&gt;: my console is set to Japanese, so that is what you will see. The field&lt;br&gt;
positions and the behaviour are identical in any locale, and each caption names the field in English.&lt;br&gt;
If you would rather follow along in English, switch your own console language under &lt;strong&gt;Unified&lt;br&gt;
Settings → Language&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The copy form defaults to the same Region
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqilytg95olqhefzadlqa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqilytg95olqhefzadlqa.png" alt="Backups list with the Actions menu open: restore, delete, and copy sit together" width="800" height="426"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Backups list. Select a backup, then **Actions → Copy backup&lt;/em&gt;* (バックアップをコピー), alongside restore&lt;br&gt;
(バックアップを復元) and delete (バックアップを削除).*&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzb3d1z0s609cxme8thbq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzb3d1z0s609cxme8thbq.png" alt="Copy form. The destination Region field defaults to Asia Pacific (Tokyo), the current Region" width="800" height="426"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Copy backup form. *&lt;/em&gt;送信先リージョン (destination Region) defaults to the current Region** — leaving it&lt;br&gt;
alone produces an in-Region copy. タグをコピー is "copy tags".*&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcs12z373usq860hfe84k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcs12z373usq860hfe84k.png" alt="Destination Region dropdown listing commercial Regions" width="800" height="426"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The destination Region list. Commercial Regions only; the China and GovCloud sets cannot be crossed.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flzpoa0q2ufym0t4uky8v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flzpoa0q2ufym0t4uky8v.png" alt="With Osaka selected, the displayed KMS key ID changes to Osaka's default key" width="800" height="426"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;With Asia Pacific (Osaka) selected, the **KMS キー ID (KMS key ID) changes to that Region's default&lt;br&gt;
key&lt;/em&gt;&lt;em&gt;. This is what "incrementality requires the same KMS key" means in practice: the key in the&lt;br&gt;
destination Region.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  While the copy runs
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3jwh1ejzezh6f3he7edv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3jwh1ejzezh6f3he7edv.png" alt="Destination Backups list showing the copy in progress, labelled as a copied backup" width="800" height="426"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Destination Region's Backups list. ステータス reads コピー中 ("copying"), and the entry is labelled&lt;br&gt;
*&lt;/em&gt;(コピーされたバックアップ) — "(copied backup)"*&lt;em&gt;. Tag copying carried the name across.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvxws6wrmnvhetenye5ly.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvxws6wrmnvhetenye5ly.png" alt="Detail page of the completed copy, showing source backup ID and source backup Region" width="800" height="426"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The completed copy. *&lt;/em&gt;ソースバックアップ ID (source backup ID)** and &lt;strong&gt;ソースバックアップリージョン&lt;br&gt;
(source backup Region) = &lt;code&gt;ap-northeast-1&lt;/code&gt;&lt;/strong&gt; are the two fields that establish this is a copy. タグ shows&lt;br&gt;
the copied tags.*&lt;/p&gt;
&lt;h3&gt;
  
  
  Restore needs more than a file system
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F81jlg03oik0z9fgq47b8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F81jlg03oik0z9fgq47b8.png" alt="Restore dialog titled " width="635" height="275"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The dialog is titled *&lt;/em&gt;バックアップからボリュームを作成 — "Create volume from backup"*&lt;em&gt;. The title states&lt;br&gt;
plainly that a restore produces a new volume. ファイルシステム (file system) starts empty.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foeqiho1ye86gp6idmh43.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foeqiho1ye86gp6idmh43.png" alt="The file system dropdown, listing only file systems that exist in the destination Region" width="635" height="280"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The ファイルシステム dropdown lists **only file systems that already exist in the destination Region&lt;/em&gt;&lt;em&gt;. If&lt;br&gt;
you have not created one, there is nothing to select — which is where the "create the file system first"&lt;br&gt;
step announces itself.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgfy6e3t94tjc4t2j35sr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgfy6e3t94tjc4t2j35sr.png" alt="After choosing the file system, the storage virtual machine field appears marked required" width="635" height="1500"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;After picking the file system, *&lt;/em&gt;ストレージ仮想マシン (storage virtual machine) appears with a red 必須&lt;br&gt;
("required")** and blocks submission. This is the concrete form of "the destination needs a file system&lt;br&gt;
&lt;strong&gt;and&lt;/strong&gt; an SVM". Note the SnapLock section at the bottom — irreversible, and not a field to set in&lt;br&gt;
passing during a restore.*&lt;/p&gt;
&lt;h3&gt;
  
  
  The volume size default is 1 TiB
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7bht0iahil6eo4nwqi9x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7bht0iahil6eo4nwqi9x.png" alt="The filled restore form, with the size corrected from the 1 TiB default down to 1 GiB" width="635" height="1500"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The same form filled in. *&lt;/em&gt;ボリュームサイズ (volume size) defaults to 1 TiB** even though the source&lt;br&gt;
volume is 1 GiB — I changed the unit to GiB here. ボリューム名, ジャンクションパス, and ストレージ効率&lt;br&gt;
are all pre-filled from the backup; &lt;strong&gt;size is not.&lt;/strong&gt; On a 1,024 GiB destination the default consumes the&lt;br&gt;
entire file system.*&lt;/p&gt;
&lt;h3&gt;
  
  
  The console shows &lt;code&gt;DP&lt;/code&gt; too, and does not refresh
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7g7oaggwl6r9y8ei1xqn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7g7oaggwl6r9y8ei1xqn.png" alt="Volume detail during the restore: lifecycle " width="800" height="794"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;During the restore. *&lt;/em&gt;ライフサイクルの状態 (lifecycle) = 作成 ("creating")** and &lt;strong&gt;ONTAP ボリュームタイプ&lt;br&gt;
(ONTAP volume type) = &lt;code&gt;DP&lt;/code&gt;&lt;/strong&gt; — with Read-Write (RW) selected in the form. ジャンクションパス and&lt;br&gt;
ストレージ効率 are absent, and セキュリティスタイル (security style) is blank.*&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fasum0pzhr4843ghf2at0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fasum0pzhr4843ghf2at0.png" alt="The same page after a reload: created, RW, with the remaining fields populated" width="800" height="476"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The same page after a reload: *&lt;/em&gt;作成済み ("created")** and &lt;code&gt;RW&lt;/code&gt;, with junction path, storage efficiency,&lt;br&gt;
and snapshot policy now shown. &lt;strong&gt;The page does not auto-refresh&lt;/strong&gt; — it kept showing &lt;code&gt;DP&lt;/code&gt; after the API had&lt;br&gt;
already returned &lt;code&gt;RW&lt;/code&gt;, so judging completion by eye alone misses it. セキュリティスタイル stayed blank.*&lt;/p&gt;


&lt;h2&gt;
  
  
  Designing this for production
&lt;/h2&gt;

&lt;p&gt;Everything above was one manual copy. &lt;strong&gt;Six things still need deciding.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Scheduling — &lt;code&gt;CopyBackup&lt;/code&gt; has none
&lt;/h3&gt;

&lt;p&gt;The AWS documentation describes the FSx for ONTAP path as "&lt;strong&gt;manually&lt;/strong&gt; copy volume backups". &lt;strong&gt;It runs from the console, CLI, or API on demand and carries no scheduler.&lt;/strong&gt; Two ways to make it periodic:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Shape&lt;/th&gt;
&lt;th&gt;Fits when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AWS Backup backup plan&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Adding a &lt;strong&gt;copy rule&lt;/strong&gt; to a plan's rule writes each backup to a backup vault in the chosen Region, and optionally the chosen account, as it is taken. On-demand copy jobs are available too&lt;/td&gt;
&lt;td&gt;You want policy control, or the same protection applied across an organization. &lt;strong&gt;The only path if the copy has to land in another account&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Your own automation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;EventBridge Scheduler → Lambda → &lt;code&gt;create-backup&lt;/code&gt; → wait → &lt;code&gt;copy-backup&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;You want to keep using FSx for ONTAP backups directly and manage generations yourself&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;AWS Backup is a managed service that centralizes taking, copying, and retaining backups under policy. FSx for ONTAP joined its cross-Region and cross-account copy targets in August 2026. Applied across an organization it pairs with AWS Organizations, and the account boundary is defined by Organizations policies; the documentation describes fan-in (many production accounts into one isolated account) and fan-out (one production account into several isolated accounts).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The cross-Region copy and the restore were measured&lt;/strong&gt; (Measured through AWS Backup as well). The copy rule fired without being touched and the restore went through. A plan-triggered job does wait inside the start window, though — about 24 minutes here — so the scheduled time is not when the generation exists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AWS Backup's Region coverage here is "all commercial Regions where both services are available"&lt;/strong&gt;, but the &lt;a href="https://docs.aws.amazon.com/aws-backup/latest/devguide/backup-feature-availability.html" rel="noopener noreferrer"&gt;feature availability page&lt;/a&gt; lists individual exceptions (below). That page also states that when a resource type has no checkmark in the copy columns, copy operations are unsupported in every scenario, including a same-Region same-account copy to a different vault.&lt;/p&gt;

&lt;p&gt;If you build it yourself, &lt;strong&gt;calling &lt;code&gt;copy-backup&lt;/code&gt; before &lt;code&gt;create-backup&lt;/code&gt; completes fails&lt;/strong&gt; — the source must be &lt;code&gt;AVAILABLE&lt;/code&gt;. Poll, or use a Step Functions wait.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Whether &lt;code&gt;AUTOMATIC&lt;/code&gt; backups can be copied is unverified.&lt;/strong&gt; The What's New post says "new and existing backups" without qualifying the type. If automatic backups are your evacuation source, prove it with one generation first.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Make recovery declarative — CloudFormation can express the restore
&lt;/h3&gt;

&lt;p&gt;Typing six steps during an incident is not realistic at any team size. This can be declarative.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;AWS::FSx::Volume&lt;/code&gt; has a &lt;code&gt;BackupId&lt;/code&gt; property&lt;/strong&gt; ("the ID of the volume backup to use to create a new volume"). So the file system, the SVM, and the restore all fit in one template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;CopiedBackupId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;String&lt;/span&gt;
    &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ID of the backup in the destination Region&lt;/span&gt;

&lt;span class="na"&gt;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;DrFileSystem&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::FSx::FileSystem&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;FileSystemType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ONTAP&lt;/span&gt;
      &lt;span class="na"&gt;StorageCapacity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1024&lt;/span&gt;
      &lt;span class="na"&gt;StorageType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SSD&lt;/span&gt;
      &lt;span class="na"&gt;SubnetIds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="nv"&gt;DrSubnetId&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;SecurityGroupIds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="nv"&gt;DrSecurityGroupId&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;OntapConfiguration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;DeploymentType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SINGLE_AZ_1&lt;/span&gt;
        &lt;span class="na"&gt;ThroughputCapacity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;128&lt;/span&gt;
        &lt;span class="na"&gt;PreferredSubnetId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;DrSubnetId&lt;/span&gt;

  &lt;span class="na"&gt;DrSvm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::FSx::StorageVirtualMachine&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;FileSystemId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;DrFileSystem&lt;/span&gt;
      &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dr_svm&lt;/span&gt;
      &lt;span class="na"&gt;RootVolumeSecurityStyle&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;UNIX&lt;/span&gt;

  &lt;span class="na"&gt;DrRestoredVolume&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::FSx::Volume&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dr_restored&lt;/span&gt;
      &lt;span class="na"&gt;VolumeType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ONTAP&lt;/span&gt;
      &lt;span class="na"&gt;BackupId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;CopiedBackupId&lt;/span&gt;
      &lt;span class="na"&gt;OntapConfiguration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;StorageVirtualMachineId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;DrSvm&lt;/span&gt;
        &lt;span class="na"&gt;JunctionPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/dr_restored&lt;/span&gt;
        &lt;span class="na"&gt;SizeInBytes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1073741824&lt;/span&gt;
        &lt;span class="na"&gt;OntapVolumeType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RW&lt;/span&gt;
        &lt;span class="na"&gt;TieringPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NONE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Keeping this template undeployed in the destination Region is the cold-standby-IaC shape.&lt;/strong&gt; Zero cost while idle, one &lt;code&gt;deploy&lt;/code&gt; at recovery, and no step to forget.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I have not deployed this template&lt;/strong&gt; — I ran the same sequence through the CLI. Deploy it once and time it before it becomes your runbook. &lt;strong&gt;An undeployed recovery template is a draft, not a procedure.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. RTO breakdown — "20 minutes" is one line of it
&lt;/h3&gt;

&lt;p&gt;I measured file system creation (20 min) and the restore (13 min). &lt;strong&gt;Your RTO is not the sum of those two.&lt;/strong&gt; Fill this in for your environment.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;My measurement&lt;/th&gt;
&lt;th&gt;Yours&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Decision to recover (detection to action)&lt;/td&gt;
&lt;td&gt;not measured&lt;/td&gt;
&lt;td&gt;　&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Destination file system creation&lt;/td&gt;
&lt;td&gt;~20 min&lt;/td&gt;
&lt;td&gt;　&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SVM creation&lt;/td&gt;
&lt;td&gt;a few minutes&lt;/td&gt;
&lt;td&gt;　&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AD join (if using SMB)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;not performed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;　&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Restore to &lt;code&gt;CREATED&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;13 m 21 s (9.4 MiB; about 22 h at 10 TB on gen 1, calculated)&lt;/td&gt;
&lt;td&gt;　&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Export policy / share reconfiguration&lt;/td&gt;
&lt;td&gt;not measured&lt;/td&gt;
&lt;td&gt;　&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client remount, DNS cutover&lt;/td&gt;
&lt;td&gt;not measured&lt;/td&gt;
&lt;td&gt;　&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Business-side validation&lt;/td&gt;
&lt;td&gt;not measured&lt;/td&gt;
&lt;td&gt;　&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;If you use SMB, the AD join is in there.&lt;/strong&gt; Confirm destination-Region reachability to your domain controllers while nothing is wrong. Discovering that mid-incident is a dead end.&lt;/p&gt;

&lt;h3&gt;
  
  
  Restore time gets longer with capacity
&lt;/h3&gt;

&lt;p&gt;The 13 m 21 s and 16 m 16 s I measured were on a 9.4 MiB volume. &lt;strong&gt;Treat those as reference points only.&lt;/strong&gt; Restore time for FSx for ONTAP backup, through either path, scales with capacity.&lt;/p&gt;

&lt;p&gt;AWS publishes the rates (&lt;a href="https://docs.aws.amazon.com/fsx/latest/ONTAPGuide/using-backups.html" rel="noopener noreferrer"&gt;Backup and restore performance&lt;/a&gt;).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Mostly large files&lt;/th&gt;
&lt;th&gt;Mostly small files&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Backup creation&lt;/td&gt;
&lt;td&gt;750 MBps&lt;/td&gt;
&lt;td&gt;100 MBps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Restore&lt;/td&gt;
&lt;td&gt;250 MBps&lt;/td&gt;
&lt;td&gt;100 MBps&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two conditions come attached to those numbers, and &lt;strong&gt;both push an estimate to the longer side&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They are values "across several concurrent restores" — that is AWS's wording. Nothing says a single restore runs at that rate.&lt;/li&gt;
&lt;li&gt;Backup and restore are background work and use only unused throughput capacity. &lt;strong&gt;Throughput capacity is therefore the ceiling.&lt;/strong&gt; A 128 MBps file system will not reach 250 MBps.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Applying both, the calculation is &lt;code&gt;min(published rate, throughput capacity)&lt;/code&gt;. These are floors, on the optimistic assumption that client I/O is zero.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Logical data&lt;/th&gt;
&lt;th&gt;128 MBps / large files&lt;/th&gt;
&lt;th&gt;256 MBps+ / large files&lt;/th&gt;
&lt;th&gt;Mostly small files (any throughput)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1 TB&lt;/td&gt;
&lt;td&gt;2 h 10 m&lt;/td&gt;
&lt;td&gt;1 h 6 m&lt;/td&gt;
&lt;td&gt;2 h 46 m&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5 TB&lt;/td&gt;
&lt;td&gt;10 h 51 m&lt;/td&gt;
&lt;td&gt;5 h 33 m&lt;/td&gt;
&lt;td&gt;13 h 53 m&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10 TB&lt;/td&gt;
&lt;td&gt;21 h 42 m&lt;/td&gt;
&lt;td&gt;11 h 6 m&lt;/td&gt;
&lt;td&gt;27 h 46 m&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20 TB&lt;/td&gt;
&lt;td&gt;43 h 24 m&lt;/td&gt;
&lt;td&gt;22 h 13 m&lt;/td&gt;
&lt;td&gt;55 h 33 m&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50 TB&lt;/td&gt;
&lt;td&gt;108 h 30 m&lt;/td&gt;
&lt;td&gt;55 h 33 m&lt;/td&gt;
&lt;td&gt;138 h 53 m&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Restoring 10 TB on the minimum configuration (128 MBps) is 22 hours at the floor.&lt;/strong&gt; Mostly small files is 28 hours, and that one does not shrink by adding throughput, because the published 100 MBps binds first. At 50 TB it passes four days.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I could not find a measured figure at 10 TB or above in any public source.&lt;/strong&gt; I searched AWS blogs, re:Post, the Knowledge Center, and the documentation, and found no article that publishes a measured restore duration with the capacity stated. The table above is calculated from AWS published rates — &lt;strong&gt;it is not something anyone measured&lt;/strong&gt;. If you need a meaningful number for your own environment, the only way is to measure once at production-equivalent capacity and file mix.&lt;/p&gt;

&lt;h3&gt;
  
  
  And this can overturn "generation 1 is enough"
&lt;/h3&gt;

&lt;p&gt;The section below says gen 1 is enough for a DR standby. At scale that reverses.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Gen 1&lt;/th&gt;
&lt;th&gt;Gen 2&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Monthly at minimum throughput&lt;/td&gt;
&lt;td&gt;$115.97 (128 MBps)&lt;/td&gt;
&lt;td&gt;$772.99 (384 MBps)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reads while restoring&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;No.&lt;/strong&gt; You wait for completion&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Yes&lt;/strong&gt;, once metadata has loaded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time to service at 10 TB&lt;/td&gt;
&lt;td&gt;21 h 42 m (floor)&lt;/td&gt;
&lt;td&gt;6–46 min (after 1–7% of metadata loads; the rest loads in the background)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Metadata is documented as 1–7% of the backup data, depending on file size — the smaller the files, the larger the share. At 10 TB, loading just that 1–7% first lets clients mount and start reading. A first access to a block that has not loaded yet costs tens of milliseconds, and it is cached on SSD after that.&lt;/p&gt;

&lt;p&gt;So &lt;strong&gt;$657 a month of difference can turn a 21-hour outage into something closer to 30 minutes.&lt;/strong&gt; If you hold 10 TB or more and your RTO is constrained in hours, choosing gen 2 for the standby too is a defensible call. Gen 1 is enough when either the capacity is small or the RTO can absorb waiting for the restore to finish.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A few TB or less, or an RTO in days → gen 1&lt;/li&gt;
&lt;li&gt;10 TB or more with an RTO in hours → gen 2's reads-while-restoring earn their keep&lt;/li&gt;
&lt;li&gt;Both are a longer RTO than SnapMirror (neither reaches a shape where the destination volume already exists)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Application consistency — this does not replace a database backup
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;A backup is a point-in-time copy of a volume.&lt;/strong&gt; It does not quiesce an application.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Workload&lt;/th&gt;
&lt;th&gt;What you need&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;File servers, shared storage&lt;/td&gt;
&lt;td&gt;A volume point-in-time is usually enough&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Databases (Oracle, SQL Server, …)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;A database-level backup, or quiescing before the backup&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anything with constantly open writes&lt;/td&gt;
&lt;td&gt;Application-level consistency checks after restore&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Reading "we copy volume backups to another Region, so DR is covered" for a database workload can end with a volume that restores and a database that will not start.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Concurrency limits at scale
&lt;/h3&gt;

&lt;p&gt;The limits are on &lt;strong&gt;operations in flight&lt;/strong&gt;, not on how much you can keep.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Limit&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Consequence&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Copies in flight (per volume, per destination Region, per KMS key)&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Parallelizing one volume's generations stops at 5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Copies in flight (per account)&lt;/td&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Firing off hundreds or thousands of volumes at once exceeds it.&lt;/strong&gt; Excess requests are rejected&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backups per volume&lt;/td&gt;
&lt;td&gt;4,091&lt;/td&gt;
&lt;td&gt;Work your generation plan backwards from here&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;In a multi-tenant design with one volume per tenant, tenant count becomes your concurrency.&lt;/strong&gt; Batch the submissions and retry rejections with exponential backoff.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Monitoring and control
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Copies are asynchronous.&lt;/strong&gt; When &lt;code&gt;copy-backup&lt;/code&gt; returns, nothing has finished yet.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Decide&lt;/th&gt;
&lt;th&gt;Options&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Where failures are detected&lt;/td&gt;
&lt;td&gt;With AWS Backup you can follow job state. For &lt;code&gt;CopyBackup&lt;/code&gt;, polling &lt;code&gt;describe-backups&lt;/code&gt; is the dependable route. &lt;strong&gt;Whether CloudWatch or EventBridge can carry this is unverified&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How success is evidenced&lt;/td&gt;
&lt;td&gt;Store the &lt;code&gt;describe-backups&lt;/code&gt; response (&lt;code&gt;BackupId&lt;/code&gt;, &lt;code&gt;SourceBackupId&lt;/code&gt;, &lt;code&gt;SourceBackupRegion&lt;/code&gt;, &lt;code&gt;CreationTime&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who approves a restore&lt;/td&gt;
&lt;td&gt;A restore creates a new volume and destroys nothing, but it &lt;strong&gt;consumes destination SSD and starts billing&lt;/strong&gt;. Put it in an approval flow where that is expected&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Which destinations are allowed&lt;/td&gt;
&lt;td&gt;Enforce it in IAM or an SCP, not in a written rule&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One note on tags. &lt;strong&gt;&lt;code&gt;--copy-tags&lt;/code&gt; carries source tags to the destination.&lt;/strong&gt; If your tags name systems or departments, that naming appears in the isolated account too. Depending on why you are isolating, re-tagging may be the better choice.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cost
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The $0.55 I spent was a test bill.&lt;/strong&gt; What is useful in your environment is a monthly figure, so this builds one up from unit rates.&lt;/p&gt;

&lt;h3&gt;
  
  
  Unit rates — queried from the AWS Price List API
&lt;/h3&gt;

&lt;p&gt;The rates below were queried from the AWS Price List API on 2026-08-29 (effective 2026-07-01). Tokyo and Osaka were identical on all four of the items that appear in both.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Charged item&lt;/th&gt;
&lt;th&gt;Tokyo / Osaka rate&lt;/th&gt;
&lt;th&gt;Nature of the charge&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SSD storage (Single-AZ)&lt;/td&gt;
&lt;td&gt;$0.150 / GB-month&lt;/td&gt;
&lt;td&gt;provisioned&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSD storage (Multi-AZ)&lt;/td&gt;
&lt;td&gt;$0.300 / GB-month&lt;/td&gt;
&lt;td&gt;provisioned&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Capacity pool Standard (Single-AZ)&lt;/td&gt;
&lt;td&gt;$0.0238 / GB-month&lt;/td&gt;
&lt;td&gt;consumed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Capacity pool Standard (Multi-AZ)&lt;/td&gt;
&lt;td&gt;$0.0476 / GB-month&lt;/td&gt;
&lt;td&gt;consumed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backup storage&lt;/td&gt;
&lt;td&gt;$0.050 / GB-month&lt;/td&gt;
&lt;td&gt;consumed (incremental)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Throughput capacity (Single-AZ, gen 1)&lt;/td&gt;
&lt;td&gt;$0.906 / MBps-month&lt;/td&gt;
&lt;td&gt;provisioned&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Throughput capacity (Single-AZ, gen 2)&lt;/td&gt;
&lt;td&gt;$2.013 / MBps-month&lt;/td&gt;
&lt;td&gt;provisioned&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Capacity pool read requests&lt;/td&gt;
&lt;td&gt;$0.00037 / 1,000 requests&lt;/td&gt;
&lt;td&gt;consumed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Capacity pool write requests&lt;/td&gt;
&lt;td&gt;$0.0047 / 1,000 requests&lt;/td&gt;
&lt;td&gt;consumed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Going through AWS Backup does not change the backup storage rate.&lt;/strong&gt; FSx for ONTAP is not a resource type that AWS Backup &lt;em&gt;fully&lt;/em&gt; manages, so the storage charge appears on the FSx for ONTAP side rather than on AWS Backup (&lt;a href="https://docs.aws.amazon.com/aws-backup/latest/devguide/metering-and-billing.html" rel="noopener noreferrer"&gt;Metering, costs, and billing for AWS Backup&lt;/a&gt;). The rate is therefore $0.050 / GB-month on the native path and on the AWS Backup path alike. Only when you use a logically air-gapped vault (LAG vault) does all of the storage and transfer land on the AWS Backup side.&lt;/p&gt;

&lt;h3&gt;
  
  
  FSx for ONTAP backup is not the cheap option
&lt;/h3&gt;

&lt;p&gt;$0.050 / GB-month is 2.1× the Single-AZ capacity pool ($0.0238). Put the other way round, the capacity pool is 47.6% of backup storage — less than half.&lt;/p&gt;

&lt;p&gt;AWS makes the same comparison. AWS Prescriptive Guidance puts fully managed backup at $0.05 / GB-month, against $0.03221 / GB-month for SnapVault aimed at a capacity pool at an SSD-to-pool ratio of 1:10 (&lt;a href="https://docs.aws.amazon.com/prescriptive-guidance/latest/optimize-costs-microsoft-workloads/storage-fsx-smb.html" rel="noopener noreferrer"&gt;Choose the right SMB file storage&lt;/a&gt;, US Region example).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That 47.6% is Single-AZ only.&lt;/strong&gt; A Multi-AZ capacity pool is $0.0476, which is 95.2% of backup storage, and the advantage all but disappears. Carrying "the capacity pool is less than half" into a Multi-AZ estimate will put your numbers wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running one continuously has a floor
&lt;/h3&gt;

&lt;p&gt;Read the rate table straight and it seems to say "if the capacity pool is cheap, then keeping a destination file system running is cheap too". It does not work out that way. The minimums on the provisioned charges set the floor for anything kept running.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Constraint&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Minimum SSD capacity&lt;/td&gt;
&lt;td&gt;1,024 GiB per HA pair&lt;/td&gt;
&lt;td&gt;&lt;a href="https://docs.aws.amazon.com/fsx/latest/ONTAPGuide/limits.html" rel="noopener noreferrer"&gt;Quotas&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Minimum throughput capacity (gen 1)&lt;/td&gt;
&lt;td&gt;128 MBps&lt;/td&gt;
&lt;td&gt;same&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Minimum throughput capacity (gen 2, 1 HA pair)&lt;/td&gt;
&lt;td&gt;384 MBps&lt;/td&gt;
&lt;td&gt;same&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Monthly cost of a Single-AZ minimum configuration:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Generation&lt;/th&gt;
&lt;th&gt;Components&lt;/th&gt;
&lt;th&gt;Monthly at minimum&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Gen 1 (128 MBps)&lt;/td&gt;
&lt;td&gt;1,024 GiB × $0.150 + 128 MBps × $0.906&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$269.57&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gen 2 (384 MBps)&lt;/td&gt;
&lt;td&gt;1,024 GiB × $0.150 + 384 MBps × $2.013&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$926.59&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The same "minimum configuration" differs by 3.4× between generations, because gen 2's minimum throughput is 384 MBps and its per-MBps rate is 2.2× gen 1. For backup and a DR standby, gen 1 is the choice (below).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;All-tiering does not remove this floor either.&lt;/strong&gt; Metadata always stays on SSD, so the 1,024 GiB SSD minimum remains provisioned.&lt;/p&gt;

&lt;h3&gt;
  
  
  It inverts with scale
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Assumptions&lt;/strong&gt;: 30 daily backup generations, 2% daily change rate, no dedupe or compression benefit assumed, Single-AZ gen 1 at the 128 MBps minimum, the Osaka rates above. The all-SSD column provisions capacity to the recommended 80% SSD utilisation.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Logical data&lt;/th&gt;
&lt;th&gt;Backup copies (no destination FS)&lt;/th&gt;
&lt;th&gt;Standing + all-tiering&lt;/th&gt;
&lt;th&gt;Standing + all SSD&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1 TB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$80&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$293&lt;/td&gt;
&lt;td&gt;$303&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2 TB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$160&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$317&lt;/td&gt;
&lt;td&gt;$491&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5 TB&lt;/td&gt;
&lt;td&gt;$400&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$389&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$1,053&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10 TB&lt;/td&gt;
&lt;td&gt;$800&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$508&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$1,991&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20 TB&lt;/td&gt;
&lt;td&gt;$1,600&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$746&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$3,866&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50 TB&lt;/td&gt;
&lt;td&gt;$4,000&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$1,460&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$9,491&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The crossover is around 4.8 TB.&lt;/strong&gt; That is the $269.57 floor divided by the per-GB difference: 1.6 generations of backup at $0.050 ($0.080) against the capacity pool at $0.0238, a gap of $0.0562.&lt;/p&gt;

&lt;p&gt;This is what backs up "we keep one running for RTO and RPO". Past 4.8 TB a standing destination file system is &lt;em&gt;lower&lt;/em&gt; monthly, and a minutes-level RPO, a single-digit-minute RTO, and a failback path come with it. Improving recoverability is not an increase in monthly cost at that size. At 1 TB it is the other way round, and the gap between $80 and $293 has to be explained as a difference in requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why a DR standby takes generation 1
&lt;/h3&gt;

&lt;p&gt;For backup and a DR standby, gen 1 is enough. The reason is the throughput increments.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Gen 1&lt;/th&gt;
&lt;th&gt;Gen 2 (1 HA pair)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Selectable throughput&lt;/td&gt;
&lt;td&gt;128 / 256 / 512 / 1,024 / 2,048 MBps (Tokyo and Osaka cap at 2,048)&lt;/td&gt;
&lt;td&gt;384 / 768 / 1,536 / 3,072 / 6,144 MBps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monthly at minimum throughput&lt;/td&gt;
&lt;td&gt;128 MBps × $0.906 = &lt;strong&gt;$115.97&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;384 MBps × $2.013 = &lt;strong&gt;$772.99&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A standby's throughput is sized by SnapMirror transfer volume and the reads right after a failover, not by production load. Gen 2 still floors at 384 MBps for that use, which is 6.7× on the throughput line alone. With gen 1 you start at 128 MBps and move up to 256, then 512, when you need it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deployment type cannot be changed after creation&lt;/strong&gt; (documented). Going from gen 1 (&lt;code&gt;SINGLE_AZ_1&lt;/code&gt;) to gen 2 (&lt;code&gt;SINGLE_AZ_2&lt;/code&gt;) means a restore from backup, or a migration over SnapMirror or AWS DataSync. Decide the generation before you build the standby.&lt;/p&gt;

&lt;h3&gt;
  
  
  Total monthly by throughput
&lt;/h3&gt;

&lt;p&gt;The 4.8 TB crossover above assumes the 128 MBps minimum. Raise throughput and the crossover moves up with it. Here are the combinations laid out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Assumptions&lt;/strong&gt;: Single-AZ gen 1, all-tiering, SSD at the 1,024 GiB minimum, Tokyo / Osaka rates. The backup-copy column is 30 daily generations at a 2% change rate. Capacity pool request charges are not included.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Logical data&lt;/th&gt;
&lt;th&gt;128 MBps&lt;/th&gt;
&lt;th&gt;256 MBps&lt;/th&gt;
&lt;th&gt;512 MBps&lt;/th&gt;
&lt;th&gt;1,024 MBps&lt;/th&gt;
&lt;th&gt;2,048 MBps&lt;/th&gt;
&lt;th&gt;Backup copies&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1 TB&lt;/td&gt;
&lt;td&gt;$293&lt;/td&gt;
&lt;td&gt;$409&lt;/td&gt;
&lt;td&gt;$641&lt;/td&gt;
&lt;td&gt;$1,105&lt;/td&gt;
&lt;td&gt;$2,033&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$80&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5 TB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$389&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$505&lt;/td&gt;
&lt;td&gt;$736&lt;/td&gt;
&lt;td&gt;$1,200&lt;/td&gt;
&lt;td&gt;$2,128&lt;/td&gt;
&lt;td&gt;$400&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10 TB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$508&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$624&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$855&lt;/td&gt;
&lt;td&gt;$1,319&lt;/td&gt;
&lt;td&gt;$2,247&lt;/td&gt;
&lt;td&gt;$800&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20 TB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$746&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$862&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$1,093&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$1,557&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$2,485&lt;/td&gt;
&lt;td&gt;$1,600&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50 TB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$1,460&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$1,576&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$1,807&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$2,271&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$3,199&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$4,000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Bold is cheaper than holding backup copies. Restated as the highest throughput that still comes in under the backup-copy figure:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Logical data&lt;/th&gt;
&lt;th&gt;Backup copies / month&lt;/th&gt;
&lt;th&gt;Highest throughput under that&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1–2 TB&lt;/td&gt;
&lt;td&gt;$80–$160&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;None.&lt;/strong&gt; Even 128 MBps standing costs more&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5 TB&lt;/td&gt;
&lt;td&gt;$400&lt;/td&gt;
&lt;td&gt;128 MBps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10 TB&lt;/td&gt;
&lt;td&gt;$800&lt;/td&gt;
&lt;td&gt;256 MBps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20 TB&lt;/td&gt;
&lt;td&gt;$1,600&lt;/td&gt;
&lt;td&gt;1,024 MBps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50 TB&lt;/td&gt;
&lt;td&gt;$4,000&lt;/td&gt;
&lt;td&gt;2,048 MBps (the gen 1 ceiling in Tokyo and Osaka)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;At 50 TB you can provision the largest throughput gen 1 offers in-Region and still come in cheaper than holding backup copies. At 1–2 TB backup copies are cheaper at every throughput, and there the difference is a requirements conversation.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the "zero while idle" of backup copies buys
&lt;/h3&gt;

&lt;p&gt;With no destination file system in normal operation, there is no throughput capacity charge and no SSD capacity charge. That is a clear advantage on the monthly figure. The work it defers, though, reappears at the moment of failover, as urgent work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And the unit of a backup is the volume, so a restore always produces a new volume.&lt;/strong&gt; There is no path that overwrites an existing volume in place, on the &lt;code&gt;CopyBackup&lt;/code&gt; route or the AWS Backup route. That is what makes steps 4 to 6 below unavoidable rather than optional: a new volume means a new junction path if the original still exists, and export policies, SMB shares, quotas, and the snapshot policy do not travel with it. The volume size default is a separate trap — it comes up as 1 TiB rather than the source's size, covered further up.&lt;/p&gt;

&lt;p&gt;In an incident that same constraint cuts the other way and becomes an advantage: you can preserve a compromised volume exactly as it stands and restore beside it, so the original is still there to investigate.&lt;/p&gt;

&lt;p&gt;Here is what a cold start to restored service actually needed, in the range I measured.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Measured / state&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Create the file system in the destination Region&lt;/td&gt;
&lt;td&gt;20–22 min (measured, &lt;code&gt;SINGLE_AZ_1&lt;/code&gt;, 128 MBps)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Create the SVM&lt;/td&gt;
&lt;td&gt;a few minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Restore the volume from backup&lt;/td&gt;
&lt;td&gt;13 m 21 s (&lt;code&gt;CopyBackup&lt;/code&gt; path) / 16 m 16 s (AWS Backup path), both at 9 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Recreate export policies and SMB shares&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Only the volume is restored.&lt;/strong&gt; Share configuration does not come with it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;AD-join the SVM if you use SMB&lt;/td&gt;
&lt;td&gt;It is a new SVM, so it joins again&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Repoint clients&lt;/td&gt;
&lt;td&gt;The new file system has a different DNS name and different IPs. Either move a CNAME or remount on the client&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Steps 1 and 3 finish if you wait. Steps 4 to 6 are a person deciding and typing. And they are needed mid-incident, not on a quiet afternoon. What you saved monthly is traded against doing that work under pressure.&lt;/p&gt;

&lt;p&gt;In a SnapMirror shape, 1 to 3 do not exist, and the destination SVM in 5 is already joined. What is left is 6 and the break-and-promote, so the number of steps is not comparable to begin with.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Steps 4 to 6 can be automated ahead of time.&lt;/strong&gt; Declare the restore in CloudFormation, script &lt;code&gt;vserver cifs create&lt;/code&gt; and the share creation, put a CNAME in front of DNS. But "can be" and "confirmed to work during an incident" are different claims. Run it through once while nothing is wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  The price difference is the requirement difference
&lt;/h3&gt;

&lt;p&gt;Here is what sits inside the $80 and the $293 at 1 TB.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Backup copies&lt;/th&gt;
&lt;th&gt;Standing (SnapMirror shape)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;RPO&lt;/td&gt;
&lt;td&gt;60 min (the automatic backup minimum)&lt;/td&gt;
&lt;td&gt;5 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RTO&lt;/td&gt;
&lt;td&gt;file system creation 20–22 min + restore 13–16 min (measured)&lt;/td&gt;
&lt;td&gt;promote the volume&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failback&lt;/td&gt;
&lt;td&gt;no path&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Destination file system in normal operation&lt;/td&gt;
&lt;td&gt;not needed&lt;/td&gt;
&lt;td&gt;needed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;If the requirement is a minute-level RPO, backup copies are not a candidate.&lt;/strong&gt; And at recovery time the backup-copy side creates a destination file system too, so the same floor starts applying from then on. The accurate reading is a design whose cost structure differs between normal operation and recovery.&lt;/p&gt;

&lt;h3&gt;
  
  
  Not included above
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Capacity pool request charges&lt;/strong&gt;: not in the tables above. The rates are known, but I have not measured the conversion from SnapMirror transferred bytes to request counts, so I cannot produce a number. With all-tiering every write goes to the capacity pool, so write requests ($0.0047 / 1,000) may not be negligible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inter-Region data transfer&lt;/strong&gt;: treated differently depending on the path. Split out into the next section&lt;/li&gt;
&lt;li&gt;Source-side backup storage (unchanged — you only added somewhere to put a copy)&lt;/li&gt;
&lt;li&gt;KMS request charges if you use a CMK&lt;/li&gt;
&lt;li&gt;SnapMirror inter-Region transfer (SnapMirror is not measured in this post)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Inter-Region data transfer is treated differently per path
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;AWS Backup copies have a transfer line item.&lt;/strong&gt; Tokyo to Osaka is $0.09 / GB (Price List API, &lt;code&gt;AWSBackup&lt;/code&gt;, &lt;code&gt;APN1-APN3-CrossRegion-WarmBytes-FSx&lt;/code&gt;, retrieved 2026-08-29). The AWS Backup pricing page gives the same transfer rate for Resource Group 3, which includes Amazon FSx, on a standard vault and on a logically air-gapped vault alike.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For native &lt;code&gt;CopyBackup&lt;/code&gt;, the reasonable thing is to estimate as though it applies.&lt;/strong&gt; I started to write "probably does not apply", checked again, and withdrew it. Here is why.&lt;/p&gt;

&lt;p&gt;The argument I withdrew: backups sit in AWS-managed S3 and never traverse the customer VPC, so they are not subject to inter-Region transfer charges the way EC2 is. &lt;strong&gt;That does not hold.&lt;/strong&gt; EBS snapshots are equally in an AWS-managed area and equally do not traverse a VPC, and a cross-Region snapshot copy does incur AWS Data Transfer charges. The AWS Storage Blog walks through tracking that charge in Cost Explorer, and the usage type that appears there is &lt;code&gt;USW2-USE1-AWS-Out-Byte&lt;/code&gt;, the service is "EC2 - Other", and the usage type group is Data Transfer - Region to Region (Out) (&lt;a href="https://aws.amazon.com/blogs/storage/effectively-track-aws-data-transfer-costs-for-cross-region-amazon-ebs-snapshot-copy/" rel="noopener noreferrer"&gt;Effectively track AWS data transfer costs for cross-region Amazon EBS Snapshot Copy&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;That also exposes a hole in how I first checked it. I based it on "there is no transfer item in the FSx for ONTAP price list", but inter-Region transfer is listed under &lt;code&gt;AWSDataTransfer&lt;/code&gt;, not under the originating service. EBS is exactly that case: there is no "snapshot transfer" item in the EBS price list, and the charge happens anyway.&lt;/p&gt;

&lt;p&gt;And &lt;code&gt;AWSDataTransfer&lt;/code&gt; has a generic, service-agnostic inter-Region SKU.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Direction&lt;/th&gt;
&lt;th&gt;Usage type&lt;/th&gt;
&lt;th&gt;Rate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tokyo → Osaka (out)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;APN1-APN3-AWS-Out-Bytes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;$0.09 / GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Osaka → Tokyo (in)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;APN1-APN3-AWS-In-Bytes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;$0.00 / GB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;(Price List API, &lt;code&gt;AWSDataTransfer&lt;/code&gt;, effective 2026-06-01, retrieved 2026-08-29)&lt;/p&gt;

&lt;p&gt;The Amazon FSx SKU on the AWS Backup side is also $0.09 / GB. The same transfer rate appearing in both places makes it natural to read them as the same underlying charge.&lt;/p&gt;

&lt;p&gt;To summarise, this is as far as it can be taken right now.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AWS Backup cross-Region copies incur transfer&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Listed in the price list&lt;/strong&gt; ($0.09 / GB, Tokyo → Osaka)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Native &lt;code&gt;CopyBackup&lt;/code&gt; incurs transfer&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Estimate as though it does.&lt;/strong&gt; A generic $0.09 / GB SKU exists, and the same shape (EBS snapshot copy) is charged in practice. But I have not confirmed it on a bill — &lt;strong&gt;unverified&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"It is not in the FSx for ONTAP price list, so it is free"&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;This inference is wrong.&lt;/strong&gt; Transfer is listed under &lt;code&gt;AWSDataTransfer&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In an estimate, put in the first full copy's capacity × $0.09 / GB. After that it is incremental, so changed volume × $0.09 / GB. A 10 TB first full is $900 — not a monthly figure, but a one-off of real size. The shortest way to settle it is to copy one small volume and read the &lt;code&gt;UsageType&lt;/code&gt; in Cost Explorer. I have not done that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On which account is billed for transfer, two AWS statements disagree.&lt;/strong&gt; FSx for ONTAP is not a resource type AWS Backup fully manages, so that discrepancy lands directly on this case.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;Statement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://docs.aws.amazon.com/aws-backup/latest/devguide/metering-and-billing.html" rel="noopener noreferrer"&gt;AWS Backup Developer Guide&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;For resource types that are not fully managed, transfer appears on the destination account&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://aws.amazon.com/backup/pricing/" rel="noopener noreferrer"&gt;AWS Backup pricing page&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Transfer is charged to the account sending the data (the copy source)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Which one is correct I have not been able to confirm. If cost attribution has to go into the design of a cross-account setup, copy once first and check which account it lands on in the actual bill.&lt;/p&gt;

&lt;h3&gt;
  
  
  The trade-off of all-tiering
&lt;/h3&gt;

&lt;p&gt;Keeping a standby cheap with all-tiering means reads after a failover start from the capacity pool. All does not pull read blocks back onto SSD, so performance immediately after a cutover is not the same as production sized for SSD. It is a choice between the monthly cost while idle and the performance immediately after cutover.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prioritise cost while idle → all&lt;/li&gt;
&lt;li&gt;Need production-equivalent performance from the moment of cutover → provision SSD as in production (the "all SSD" column above)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When you build your own estimate, replace the change rate and the generation count with measurements from your environment.&lt;/strong&gt; The 2% is an assumption, not something I measured. Rates are revised, too. The figures above were retrieved on 2026-08-29 and are effective 2026-07-01, so check the current values on the &lt;a href="https://aws.amazon.com/fsx/netapp-ontap/pricing/" rel="noopener noreferrer"&gt;FSx for ONTAP pricing page&lt;/a&gt; and the &lt;a href="https://aws.amazon.com/backup/pricing/" rel="noopener noreferrer"&gt;AWS Backup pricing page&lt;/a&gt; before you decide.&lt;/p&gt;




&lt;h2&gt;
  
  
  As a ransomware layer
&lt;/h2&gt;

&lt;p&gt;An isolated copy is a useful layer, but it &lt;strong&gt;lacks two properties people expect of it.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Expectation&lt;/th&gt;
&lt;th&gt;Reality&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Put it in an isolated account and it cannot be deleted&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Copies can be deleted.&lt;/strong&gt; The isolation holds only if the destination account's IAM or vault policy prevents deletion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A backup copy satisfies an immutability requirement&lt;/td&gt;
&lt;td&gt;It does not. Preventing deletion during a retention period is the domain of WORM features (Object Lock, SnapLock, Tamperproof Snapshot, Vault Lock)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;WORM features are irreversible.&lt;/strong&gt; Once enabled, nothing is deletable until retention expires, and the blast radius is routinely wider than the resource you named. Get the retention value and the blast radius approved separately before enabling one. I made a file system undeletable for six months this way.&lt;/p&gt;

&lt;p&gt;On the other hand, &lt;strong&gt;a restore always producing a new volume is an advantage during an incident.&lt;/strong&gt; You can preserve the compromised volume for investigation and restore into a separate volume to resume operations, without overwriting the original.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common errors and how to isolate them
&lt;/h2&gt;

&lt;p&gt;Decide &lt;strong&gt;which layer stopped&lt;/strong&gt; before you start debugging. Get the layer wrong and you spend the incident suspecting something that works.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Look at&lt;/th&gt;
&lt;th&gt;What to check&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1. Source&lt;/td&gt;
&lt;td&gt;The backup&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Lifecycle&lt;/code&gt; is &lt;code&gt;AVAILABLE&lt;/code&gt;; volume type is &lt;code&gt;RW&lt;/code&gt;; style is FlexVol&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. Permissions&lt;/td&gt;
&lt;td&gt;IAM / KMS&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;fsx:CopyBackup&lt;/code&gt; present; source-Region backup reachable; destination key permissions for a CMK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. Copy&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;describe-backups&lt;/code&gt; at the destination&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;COPYING&lt;/code&gt;, &lt;code&gt;AVAILABLE&lt;/code&gt;, or &lt;code&gt;FAILED&lt;/code&gt;; concurrency limits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4. Restore&lt;/td&gt;
&lt;td&gt;The destination volume&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Lifecycle&lt;/code&gt;; enough SSD (it pauses when short)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5. Access&lt;/td&gt;
&lt;td&gt;The client&lt;/td&gt;
&lt;td&gt;Export policy, security group, SVM NFS / SMB endpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Messages I actually saw:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Message&lt;/th&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;What to do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Backup &amp;lt;id&amp;gt; is being copied, can't be deleted&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Wait for the copy. &lt;strong&gt;There is a short delay even after it completes&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Backup failed. Please delete the backup and try again.&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Names no cause. Suspect FlexGroup and take a FlexVol control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Volume with type DP is not backupable.&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;That is a SnapMirror destination. &lt;strong&gt;Back up at the source instead&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rejection from exceeding copies in flight&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;5 per volume per destination Region per KMS key, 1,000 per account. Wait and retry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Unknown options: --ontap-configuration&lt;/code&gt; (&lt;code&gt;delete-file-system&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;A CLI version difference. &lt;strong&gt;Read the response.&lt;/strong&gt; This cost me 8 minutes of polling&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Whether a failed copy leaves a partial backup at the destination is unverified&lt;/strong&gt; — I did not induce a failure deliberately. If you automate this, detect &lt;code&gt;FAILED&lt;/code&gt; and delete it (the backup created by my FlexGroup failure did remain as &lt;code&gt;FAILED&lt;/code&gt;, and it deleted cleanly).&lt;/p&gt;




&lt;h2&gt;
  
  
  Where this sits alongside an existing backup product or SaaS
&lt;/h2&gt;

&lt;p&gt;If you already run a data protection product or backup SaaS, this update is not a migration question. It is a question of &lt;strong&gt;which layer you give to what.&lt;/strong&gt; There are three paths, and combining them is normal.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Managed by&lt;/th&gt;
&lt;th&gt;Another Region&lt;/th&gt;
&lt;th&gt;Another account&lt;/th&gt;
&lt;th&gt;Fits&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A. FSx for ONTAP backups&lt;/td&gt;
&lt;td&gt;FSx for ONTAP API / console&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;yes&lt;/strong&gt; (new)&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;Volume-level evacuation with the fewest moving parts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B. AWS Backup&lt;/td&gt;
&lt;td&gt;Backup plans&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;yes&lt;/strong&gt; (needs Organizations)&lt;/td&gt;
&lt;td&gt;Policy control, one view across services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C. Existing data protection product / SaaS&lt;/td&gt;
&lt;td&gt;That product's policy&lt;/td&gt;
&lt;td&gt;Depends on the product&lt;/td&gt;
&lt;td&gt;Depends on the product&lt;/td&gt;
&lt;td&gt;File- or application-level restore, catalog search, fitting existing workflows&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  A concrete way to split them
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;File-level restores from the existing product; whole-volume evacuation from FSx for ONTAP backup copies.&lt;/strong&gt; One split that often works, and the reason is restore granularity. A restore from an FSx for ONTAP backup &lt;strong&gt;produces one new volume&lt;/strong&gt;, which is a lot of machinery to return a single file a user deleted. Conversely, pushing whole volumes to another Region through an existing product tends to grow the transfer volume and the catalog. Where your product already handles cross-Region evacuation efficiently, the opposite split is equally reasonable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The decisive question for AWS Backup is whether you need another account.&lt;/strong&gt; Evacuating to an isolated account is out of reach for the FSx for ONTAP API alone; it needs AWS Backup and AWS Organizations. If you only need another Region, the FSx for ONTAP API is fewer moving parts.&lt;/p&gt;

&lt;h3&gt;
  
  
  What to ask, regardless of product
&lt;/h3&gt;

&lt;p&gt;Ask along these axes rather than by product name:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Axis&lt;/th&gt;
&lt;th&gt;What to establish&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Unit of protection&lt;/td&gt;
&lt;td&gt;Whole volume, file / directory, or application-consistent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Restore granularity&lt;/td&gt;
&lt;td&gt;Can it return one file, or only a whole volume&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-Region / cross-account path&lt;/td&gt;
&lt;td&gt;The product's own feature, via AWS Backup, or your own copy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What the restore target requires&lt;/td&gt;
&lt;td&gt;Does a file system need to exist first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where retention policy lives&lt;/td&gt;
&lt;td&gt;The product or AWS. &lt;strong&gt;Is it managed in two places&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monitoring and alerting&lt;/td&gt;
&lt;td&gt;Where failures surface, and whether that reaches your existing observability&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where the cost lands&lt;/td&gt;
&lt;td&gt;Product licensing, AWS usage, or both&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;"Managed in two places" is where the accidents are.&lt;/strong&gt; If FSx for ONTAP automatic backups and the product's policy each hold generations independently, changing retention on one side leaves the other holding an unintended number. The ceiling is 4,091 backups per volume, so plan backwards from it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Storage is also billed twice.&lt;/strong&gt; Holding the same data in both the product's repository and FSx for ONTAP backups charges for both sets of generations. If you combine them, pick a primary and &lt;strong&gt;make the generation counts differ&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Shape&lt;/th&gt;
&lt;th&gt;Primary&lt;/th&gt;
&lt;th&gt;Secondary&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;File-level restore matters most&lt;/td&gt;
&lt;td&gt;Existing product, long retention, many generations&lt;/td&gt;
&lt;td&gt;FSx for ONTAP backups short and few (recent whole-volume evacuation)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-Region evacuation matters most&lt;/td&gt;
&lt;td&gt;FSx for ONTAP backup copies, many generations&lt;/td&gt;
&lt;td&gt;Existing product limited to in-Region file restores&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Neither is correct in the abstract. It comes down to &lt;strong&gt;which granularity 90% of your restore requests arrive in.&lt;/strong&gt; Counting your past restore tickets is the most reliable way to find out.&lt;/p&gt;

&lt;h3&gt;
  
  
  About monitoring
&lt;/h3&gt;

&lt;p&gt;My verification polled &lt;code&gt;describe-backups&lt;/code&gt;. &lt;strong&gt;I have not verified a path for detecting copy completion through CloudWatch metrics or EventBridge events.&lt;/strong&gt; If you plan to wire this into an existing observability stack, that is a gap you will need to close yourself.&lt;/p&gt;




&lt;h2&gt;
  
  
  Choosing between this and SnapMirror
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Neither is better.&lt;/strong&gt; The AWS documentation states RPO and RTO figures for both, and they protect different things.&lt;/p&gt;

&lt;p&gt;For context: cross-Region disaster recovery for FSx for ONTAP has been buildable with SnapMirror for years — a destination file system, cluster peering, asynchronous replication. Plenty of production environments run exactly that, and backup copies do not displace it. What got added is the option of not holding the destination while nothing is wrong.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Backup copies&lt;/th&gt;
&lt;th&gt;SnapMirror&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Suits&lt;/td&gt;
&lt;td&gt;Offline copies for compliance or isolation&lt;/td&gt;
&lt;td&gt;Availability in a second Region&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RPO guidance&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;60 minutes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;as low as &lt;strong&gt;5 minutes&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RTO guidance&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;minutes to hours&lt;/strong&gt; (size dependent)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;single-digit minutes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Needed while idle&lt;/td&gt;
&lt;td&gt;No destination file system&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;A running destination file system&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shape of recovery&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Restore as a new volume.&lt;/strong&gt; The original does not come back&lt;/td&gt;
&lt;td&gt;Break the relationship and promote the destination&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Path back to production&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;None.&lt;/strong&gt; Continue on the recovery side, or copy back in the other direction&lt;/td&gt;
&lt;td&gt;Delete the relationship and &lt;code&gt;snapmirror resync&lt;/code&gt; from the DR side&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trade-off&lt;/td&gt;
&lt;td&gt;Destination file system creation lands on RTO (20 min measured); RPO is as coarse as the backup interval. &lt;strong&gt;The unit of backup and restore is the volume, and with AWS Backup / FSx backups a restore always lands in a newly created volume&lt;/strong&gt; — there is no restore-in-place path, so junction paths, share configuration and where clients point all have to be rebuilt&lt;/td&gt;
&lt;td&gt;You pay for destination capacity and throughput continuously; needs cluster peering and &lt;strong&gt;does not work through NAT&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In operation, the asymmetry in "path back to production" is what bites. SnapMirror can be re-established in reverse to fail back. Backup copies only restore, and what you get is a new volume. If a documented failback procedure is a requirement, backup copies do not substitute for it.&lt;/p&gt;

&lt;p&gt;One caveat on &lt;code&gt;snapmirror resync&lt;/code&gt;: &lt;strong&gt;user-created snapshots are not replicated by a resync.&lt;/strong&gt; The &lt;code&gt;preserve&lt;/code&gt; parameter is available on XDP relationships.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running both
&lt;/h3&gt;

&lt;p&gt;This is a natural combination: &lt;strong&gt;SnapMirror for availability, backup copies for isolated retention.&lt;/strong&gt; One catch — &lt;strong&gt;a SnapMirror destination is a &lt;code&gt;DP&lt;/code&gt; volume and cannot be backed up&lt;/strong&gt;, so "back up the replica and evacuate that" does not work. &lt;strong&gt;Take backups at the source and copy those.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Added (September 2026)&lt;/strong&gt;: read-only does not mean inert. Verified on ONTAP 9.18.1P5 in ap-northeast-1: a SnapMirror destination can be served read-only through an FSx for ONTAP S3 Access Point &lt;strong&gt;while replication is still running&lt;/strong&gt; — no break, no clone. What gates the attachment is the junction path, not the volume type, and setting that junction path is an ONTAP-side operation: the Amazon FSx API refuses it on a &lt;code&gt;DP&lt;/code&gt; volume, and &lt;code&gt;UpdateVolume&lt;/code&gt; refuses it silently, returning HTTP 200 with the volume unchanged. After a transfer, new data was readable through the same access point in 15 seconds. Budget minutes to tens of minutes for the FSx control plane to report a newly mounted volume as attachable. &lt;a href="https://github.com/Yoshiki0705/fsxn-lakehouse-integrations/blob/main/docs/en/s3ap-flexcache-snapmirror-considerations.md#32-s3-ap-attachment-at-the-destination" rel="noopener noreferrer"&gt;Design considerations&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Regional DR within one country
&lt;/h2&gt;

&lt;p&gt;Copying to Osaka keeps data inside Japan while placing it in a second Region.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Consideration&lt;/th&gt;
&lt;th&gt;Detail&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Where the data sits&lt;/td&gt;
&lt;td&gt;Both Tokyo and Osaka are in-country&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Idle cost&lt;/td&gt;
&lt;td&gt;Backup storage only ($0.050/GB-month in Osaka, rate effective 2026-07-01)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Needed at recovery&lt;/td&gt;
&lt;td&gt;A file system and SVM in Osaka. &lt;strong&gt;Creation time lands on RTO&lt;/strong&gt; (20 min measured)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Generation availability&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;SINGLE_AZ_1&lt;/code&gt; was creatable in Osaka (measured 2026-08-28). &lt;strong&gt;This differs by Region&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trade-off&lt;/td&gt;
&lt;td&gt;Coarser RPO and RTO than SnapMirror. A minute-level RPO requirement means keeping the destination running&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;How you satisfy a data residency requirement is a design question, &lt;strong&gt;not a legal or regulatory determination.&lt;/strong&gt; Interpreting regulatory requirements belongs to your compliance function and the relevant authority. This post does not substitute for that.&lt;/p&gt;




&lt;h2&gt;
  
  
  A staged rollout
&lt;/h2&gt;

&lt;p&gt;Do not start on a production volume. The order I would suggest:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Run the whole path on a small test volume (~1 GiB)&lt;/strong&gt; — copy, destination file system, restore, content check, teardown. This is where the holes in your procedure show up&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time it including teardown.&lt;/strong&gt; Destination file system creation lands on RTO, so this is the number you most want&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure once at production data volume.&lt;/strong&gt; My 9.4 MiB figures are dominated by fixed overhead and are not evidence for your environment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check whether the second copy is incremental&lt;/strong&gt; by comparing transfer volume and duration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run periodic copies on a non-production volume and build the failure-detection path.&lt;/strong&gt; This is where the real work is&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Go to production, keeping the existing backup path running in parallel&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Put restore drills into operations.&lt;/strong&gt; Without them your RTO is an estimate&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Do not go to production without step 5.&lt;/strong&gt; Copies are asynchronous, and when one fails your command has already returned success.&lt;/p&gt;

&lt;h3&gt;
  
  
  A verification plan template
&lt;/h3&gt;

&lt;p&gt;Useful to have on one page if a team is involved:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;What goes in it&lt;/th&gt;
&lt;th&gt;Mine&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Question being answered&lt;/td&gt;
&lt;td&gt;"Does cross-Region evacuation work without a standing destination?"&lt;/td&gt;
&lt;td&gt;same&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Success criteria&lt;/td&gt;
&lt;td&gt;Content matches after restore, and every line of the RTO breakdown is filled&lt;/td&gt;
&lt;td&gt;Hash match achieved. RTO breakdown incomplete (AD and remount unmeasured)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time required&lt;/td&gt;
&lt;td&gt;Half a day for the small path, a day at production volume&lt;/td&gt;
&lt;td&gt;~1.5 hours including teardown&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost ceiling&lt;/td&gt;
&lt;td&gt;Driven by destination file system runtime. Set it first&lt;/td&gt;
&lt;td&gt;Capped at 3 hours / $2; actual $0.55&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stakeholders&lt;/td&gt;
&lt;td&gt;Storage operations, the business (accepting the restored data), audit / control, networking (destination VPC, AD reachability)&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go criteria&lt;/td&gt;
&lt;td&gt;Measured RTO under target / incrementality confirmed / failure detection working&lt;/td&gt;
&lt;td&gt;Not met (incrementality and detection unverified)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No-Go criteria&lt;/td&gt;
&lt;td&gt;RPO needed in minutes / failback required / FlexGroup in scope&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;By that standard I cannot call my own run a Go.&lt;/strong&gt; Incrementality and failure detection are unverified, and the RTO breakdown does not include AD. What this post establishes is that the path works.&lt;/p&gt;

&lt;h3&gt;
  
  
  A recovery runbook skeleton
&lt;/h3&gt;

&lt;p&gt;Naming the decision points keeps the day itself calm:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Decision or action&lt;/th&gt;
&lt;th&gt;Branch&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Is recovery needed (Region-level failure, or data loss)?&lt;/td&gt;
&lt;td&gt;Data loss alone is &lt;strong&gt;an in-Region restore&lt;/strong&gt;. You do not need the other Region&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Which generation?&lt;/td&gt;
&lt;td&gt;Check &lt;code&gt;CreationTime&lt;/code&gt; on the copies. &lt;strong&gt;This is also where you see whether copying kept up&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Create the destination file system&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;deploy&lt;/code&gt; the template, or steps 4–5 by hand&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;SMB in play?&lt;/td&gt;
&lt;td&gt;Then AD join is required. &lt;strong&gt;Put the reachability check here&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Restore&lt;/td&gt;
&lt;td&gt;Wait for &lt;code&gt;Lifecycle: CREATED&lt;/code&gt;. &lt;strong&gt;&lt;code&gt;OntapVolumeType&lt;/code&gt; reads &lt;code&gt;DP&lt;/code&gt; while restoring&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Business validation&lt;/td&gt;
&lt;td&gt;Data match and application startup. &lt;strong&gt;Databases need a consistency check&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Return to the original Region?&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Backup copies have no failback path.&lt;/strong&gt; Either continue on the recovery side, or copy back in the other direction&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Try it now
&lt;/h2&gt;

&lt;p&gt;The same steps with only the values to substitute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Change these five lines for your environment&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SRC_REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ap-northeast-1
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DST_REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ap-northeast-3
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SRC_VOLUME_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;fsvol-0123456789abcdef0
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SRC_BACKUP_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;backup-0123456789abcdef0
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DST_BACKUP_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;   &lt;span class="c"&gt;# fill in after the copy&lt;/span&gt;

&lt;span class="c"&gt;# 1) create a backup&lt;/span&gt;
aws fsx create-backup &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SRC_REGION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--volume-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SRC_VOLUME_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Backup.BackupId'&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text

&lt;span class="c"&gt;# 2) wait for AVAILABLE&lt;/span&gt;
aws fsx describe-backups &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SRC_REGION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--backup-ids&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SRC_BACKUP_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Backups[0].Lifecycle'&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text

&lt;span class="c"&gt;# 3) copy to another Region (--region is the destination, --source-region is the origin)&lt;/span&gt;
aws fsx copy-backup &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DST_REGION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--source-backup-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SRC_BACKUP_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--source-region&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SRC_REGION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--copy-tags&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Backup.BackupId'&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text

&lt;span class="c"&gt;# 4) confirm the copy&lt;/span&gt;
aws fsx describe-backups &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DST_REGION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--backup-ids&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DST_BACKUP_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Backups[0].{Life:Lifecycle,Src:SourceBackupId,SrcRegion:SourceBackupRegion}'&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;If step 4 shows &lt;code&gt;SourceBackupId&lt;/code&gt; and &lt;code&gt;SourceBackupRegion&lt;/code&gt;, the copy is real.&lt;/strong&gt; That is the whole day-to-day preparation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;What this update removed is &lt;strong&gt;what you keep running day to day.&lt;/strong&gt; Putting data in a second Region no longer requires a file system there.&lt;/p&gt;

&lt;p&gt;You pay for that at recovery time instead — file system creation (20 minutes measured) plus an RPO as coarse as your backup interval. &lt;strong&gt;If minute-level RPO and a failback procedure are requirements, this does not replace SnapMirror.&lt;/strong&gt; If isolated retention is the requirement, you now have an option that needs nothing standing by.&lt;/p&gt;

&lt;p&gt;Three things I plan to do next:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Measure cross-account copies.&lt;/strong&gt; The AWS Backup cross-Region copy and restore are measured now; cross-account needs AWS Organizations and remains documentation only&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure incrementality at production data volume.&lt;/strong&gt; At 9.4 MiB it disappears into fixed overhead&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Establish a failure-detection path.&lt;/strong&gt; Whether CloudWatch or EventBridge can carry copy state is unverified&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The most useful thing I learned was smaller than the feature: &lt;strong&gt;I believed &lt;code&gt;DP&lt;/code&gt; was a permanent property until I tried writing to the volume&lt;/strong&gt;, and I believed storage efficiency was not carried over until I ran the same restore through the console. Two conclusions drawn from a single observation on a single interface, both wrong.&lt;/p&gt;

&lt;p&gt;Thanks for reading.&lt;/p&gt;




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/08/fsx-ontap-cross-region-backup-copy/" rel="noopener noreferrer"&gt;AWS What's New: FSx for NetApp ONTAP now supports copying backups across AWS Regions and accounts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/08/aws-backup-amazon-fsx-netapp-cross-account-region/" rel="noopener noreferrer"&gt;AWS What's New: AWS Backup adds cross-Region and cross-account backup support&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/fsx/latest/ONTAPGuide/copy-backups.html" rel="noopener noreferrer"&gt;AWS: Copying backups&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/fsx/latest/ONTAPGuide/copying-backups-same-account.html" rel="noopener noreferrer"&gt;AWS: Copying backups within the same AWS account&lt;/a&gt; — the backup-copy vs SnapMirror guidance and the RPO / RTO figures&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/fsx/latest/ONTAPGuide/using-backups.html" rel="noopener noreferrer"&gt;AWS: Protecting your data with volume backups&lt;/a&gt; — restore target confined to the backup's Region, SSD-first restores, background priority&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/fsx/latest/ONTAPGuide/to-restore-backups.html" rel="noopener noreferrer"&gt;AWS: Restoring a backup to a new volume&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/fsx/latest/APIReference/API_CreateVolumeFromBackup.html" rel="noopener noreferrer"&gt;AWS: CreateVolumeFromBackup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-fsx-volume.html" rel="noopener noreferrer"&gt;AWS CloudFormation: AWS::FSx::Volume&lt;/a&gt; — the &lt;code&gt;BackupId&lt;/code&gt; property&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/aws-backup/latest/devguide/cross-region-backup.html" rel="noopener noreferrer"&gt;AWS Backup: Creating backup copies across AWS Regions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/aws-backup/latest/devguide/create-cross-account-backup.html" rel="noopener noreferrer"&gt;AWS Backup: Creating backup copies across AWS accounts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/aws-backup/latest/devguide/encryption.html" rel="noopener noreferrer"&gt;AWS Backup: Encryption for backups in AWS Backup&lt;/a&gt; (why AWS managed keys cannot do a cross-account copy)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/aws-backup/latest/devguide/backup-feature-availability.html" rel="noopener noreferrer"&gt;AWS Backup: Feature availability&lt;/a&gt; (the per-Region exceptions)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://community.netapp.com/community/discussion/468353/cross-region-and-cross-account-backup-for-amazon-fsx-for-netapp-ontap" rel="noopener noreferrer"&gt;Cross-Region and Cross-Account Backup for Amazon FSx for NetApp ONTAP&lt;/a&gt; (NetApp Community: the cross-account walkthrough, four architecture patterns, clean room and restore testing)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/fsx/latest/ONTAPGuide/limits.html" rel="noopener noreferrer"&gt;AWS: Quotas (FSx for ONTAP)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Every resource used for this test has been deleted. The figures are measurements from one specific environment and configuration, and will differ with data volume and setup.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>storage</category>
      <category>disasterrecovery</category>
      <category>devops</category>
    </item>
    <item>
      <title>Using Databricks FILE type against data on NAS: what works today with FSx for ONTAP S3 Access Points</title>
      <dc:creator>Yoshiki Fujiwara(藤原 善基)@AWS Community Builder</dc:creator>
      <pubDate>Wed, 12 Aug 2026 15:07:29 +0000</pubDate>
      <link>https://dev.to/aws-builders/using-databricks-file-type-against-data-on-nas-what-works-today-with-fsx-for-ontap-s3-access-points-52m6</link>
      <guid>https://dev.to/aws-builders/using-databricks-file-type-against-data-on-nas-what-works-today-with-fsx-for-ontap-s3-access-points-52m6</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A follow-up to &lt;a href="https://hakobiya.hatenablog.com/entry/fsxn-lakehouse-part2-databricks" rel="noopener noreferrer"&gt;Part 2: Databricks and FSx for ONTAP S3 Access Points&lt;/a&gt; in the "FSx for ONTAP S3 Access Points x Lakehouse Deep Dive" series. Some results changed since Part 2 — if you read that one, see "Why reads fail" below.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;I tested whether images and PDFs already sitting on FSx for ONTAP can be referenced from&lt;br&gt;
Databricks without copying them into the lakehouse. The result is: registration works, reads&lt;br&gt;
do not.&lt;/p&gt;

&lt;p&gt;If you only need the zero-copy verdict, read "Connecting to an FSx for ONTAP S3 Access Point"&lt;br&gt;
and "Design tips" below. If you want the behaviour of the &lt;code&gt;FILE&lt;/code&gt; type itself, start from the&lt;br&gt;
top.&lt;/p&gt;

&lt;p&gt;Databricks shipped &lt;code&gt;FILE&lt;/code&gt; as a beta column type: a reference to an unstructured file held as a&lt;br&gt;
single column in a Delta table. The value is a struct carrying &lt;code&gt;uri&lt;/code&gt; / &lt;code&gt;offset&lt;/code&gt; / &lt;code&gt;size&lt;/code&gt; /&lt;br&gt;
&lt;code&gt;content_type&lt;/code&gt; / &lt;code&gt;checksum&lt;/code&gt;. Because it holds the reference and the metadata as a struct, you&lt;br&gt;
can pass it straight from a table into an AI function such as &lt;code&gt;ai_parse_document&lt;/code&gt;. There are&lt;br&gt;
two variants — &lt;code&gt;FILE EXTERNAL&lt;/code&gt;, which holds only the reference, and &lt;code&gt;FILE MANAGED&lt;/code&gt;, which&lt;br&gt;
copies the bytes into Databricks.&lt;/p&gt;

&lt;p&gt;I tested this against files on Amazon FSx for NetApp ONTAP, reached through an FSx for ONTAP S3&lt;br&gt;
Access Point — the feature that exposes an ONTAP volume over an S3-compatible API.&lt;/p&gt;

&lt;p&gt;Here is what I found:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Registration against an FSx for ONTAP S3 AP succeeds; reads do not.&lt;/strong&gt; The cause sits in the
session policy attached to the temporary credentials Unity Catalog vends, and there is no way
to work around it from the user side&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The FILE type itself works.&lt;/strong&gt; But you have to choose between &lt;code&gt;FILE EXTERNAL&lt;/code&gt; and
&lt;code&gt;FILE MANAGED&lt;/code&gt; before you ingest&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If your design puts metadata in object tags, tag values are effectively ASCII.&lt;/strong&gt; Most CJK
strings are rejected while a minority are accepted. AWS Support has escalated that split to
the service team as a potential defect&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The operator surface is inconsistent.&lt;/strong&gt; &lt;code&gt;GROUP BY&lt;/code&gt; and &lt;code&gt;DISTINCT&lt;/code&gt; are accepted; &lt;code&gt;=&lt;/code&gt; and
&lt;code&gt;ORDER BY&lt;/code&gt; are rejected&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Below: what is established for each of those four, and how to turn it into design decisions.&lt;br&gt;
The templates and scripts are in the repository, so you can re-run the same tests in your own&lt;br&gt;
account.&lt;/p&gt;

&lt;p&gt;Repository: &lt;a href="https://github.com/Yoshiki0705/fsxn-lakehouse-integrations" rel="noopener noreferrer"&gt;Yoshiki0705/fsxn-lakehouse-integrations&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Test environment
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Measured&lt;/td&gt;
&lt;td&gt;2026-08-12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage&lt;/td&gt;
&lt;td&gt;One FSx for ONTAP file system&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Access Point&lt;/td&gt;
&lt;td&gt;One, INTERNET origin, UNIX root identity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workspace&lt;/td&gt;
&lt;td&gt;One non-trial workspace, purpose-built, same account and region&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compute&lt;/td&gt;
&lt;td&gt;Serverless SQL warehouse, and a classic DBR 18.2 cluster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Control&lt;/td&gt;
&lt;td&gt;A S3 general bucket in the same account, running the same operations alongside&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Method&lt;/td&gt;
&lt;td&gt;SQL Statement Execution API, and boto3 from my workstation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Running the S3 general bucket control alongside matters. Looking only at the Access Point side, you&lt;br&gt;
cannot separate a defect in your own environment from behaviour specific to the Access Point.&lt;br&gt;
Except where stated otherwise, every conclusion below rests on the same operation succeeding&lt;br&gt;
against S3 general bucket.&lt;/p&gt;

&lt;p&gt;Note that the object tag results depend on the ONTAP tag validation implementation, so a&lt;br&gt;
different ONTAP version may behave differently. The FSx console and &lt;code&gt;describe-file-systems&lt;/code&gt; do&lt;br&gt;
not expose the version; use the ONTAP REST API (&lt;code&gt;GET /api/cluster?fields=version&lt;/code&gt;) to check&lt;br&gt;
yours.&lt;/p&gt;
&lt;h2&gt;
  
  
  What the FILE type can and cannot do
&lt;/h2&gt;

&lt;p&gt;First, the behaviour of the type itself. There is prior work on this feature from Databricks&lt;br&gt;
Japan: &lt;a href="https://qiita.com/taka_yayoi/items/ab110946862e63dca9f6" rel="noopener noreferrer"&gt;パス文字列でもバイナリでもない。DatabricksのFILE型を試す&lt;/a&gt;&lt;br&gt;
by taka_yayoi. Every operational caveat it lists reproduced in my environment, so I have&lt;br&gt;
tabulated those first. &lt;em&gt;(What follows is my paraphrase and my own measurements, not a&lt;br&gt;
translation.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Some terms first. &lt;code&gt;FILE MANAGED&lt;/code&gt; copies bytes into a Databricks-side location called a&lt;br&gt;
FileSpace. &lt;code&gt;list_files&lt;/code&gt; enumerates files on a Volume and returns FILE values; &lt;code&gt;create_file&lt;/code&gt;&lt;br&gt;
constructs a FILE value explicitly. &lt;code&gt;_object_metadata&lt;/code&gt; reads an object's tags and user&lt;br&gt;
metadata.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Behaviour&lt;/th&gt;
&lt;th&gt;Measured&lt;/th&gt;
&lt;th&gt;Design implication&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;FileSpace on the same volume as the source&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;CREATE TABLE&lt;/code&gt; succeeds; &lt;code&gt;INSERT&lt;/code&gt; fails with &lt;code&gt;Cannot get file metadata under managed storage&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Give the FileSpace its own volume&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;checksum&lt;/code&gt; on FILE values from &lt;code&gt;list_files&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Null on every row&lt;/td&gt;
&lt;td&gt;Not usable for integrity checks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;checksum&lt;/code&gt; under &lt;code&gt;FILE MANAGED&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Populated with &lt;code&gt;ETAG:"…"&lt;/code&gt;, matching the &lt;code&gt;etag&lt;/code&gt; from &lt;code&gt;_object_metadata&lt;/code&gt; on S3 general bucket exactly as a string&lt;/td&gt;
&lt;td&gt;Comes from the object store, so it is usable for reconciliation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Filenames under &lt;code&gt;FILE MANAGED&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Replaced with an opaque UUID; the extension does not survive either&lt;/td&gt;
&lt;td&gt;Copy the name to a column &lt;strong&gt;before&lt;/strong&gt; ingesting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Format of &lt;code&gt;uri&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Prefixed with &lt;code&gt;dbfs:&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Normalise before writing string comparisons&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Serverless notebooks&lt;/td&gt;
&lt;td&gt;Unsupported&lt;/td&gt;
&lt;td&gt;Use the SQL Statement Execution API against a serverless SQL warehouse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automatic garbage collection in beta&lt;/td&gt;
&lt;td&gt;Does not run&lt;/td&gt;
&lt;td&gt;The FileSpace grows on every re-run. Plan for manual deletion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The Previews page toggle&lt;/td&gt;
&lt;td&gt;Used &lt;code&gt;FILE EXTERNAL&lt;/code&gt; / &lt;code&gt;FILE MANAGED&lt;/code&gt; without touching it (confirmed on 2 workspaces)&lt;/td&gt;
&lt;td&gt;May not be required. Do not assume either way&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;content_type&lt;/code&gt; from &lt;code&gt;create_file&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;binary/octet-stream&lt;/code&gt; for both Japanese and ASCII&lt;/td&gt;
&lt;td&gt;Pass it explicitly if anything downstream branches on it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;What this tells us&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The difference between &lt;code&gt;FILE EXTERNAL&lt;/code&gt; and &lt;code&gt;FILE MANAGED&lt;/code&gt; is not only whether bytes are copied.&lt;br&gt;
Whether &lt;code&gt;checksum&lt;/code&gt; is populated and whether the filename survives both change. Reversing the&lt;br&gt;
decision later means re-ingesting, so decide before you ingest.&lt;/p&gt;

&lt;p&gt;On &lt;code&gt;content_type&lt;/code&gt;, I got &lt;code&gt;binary/octet-stream&lt;/code&gt; for both Japanese and ASCII input. For the same&lt;br&gt;
files, &lt;code&gt;list_files&lt;/code&gt; reported &lt;code&gt;text/plain&lt;/code&gt;, so the two paths infer differently. If anything&lt;br&gt;
downstream branches on &lt;code&gt;content_type&lt;/code&gt;, pass it explicitly.&lt;/p&gt;
&lt;h2&gt;
  
  
  The state of operator support
&lt;/h2&gt;

&lt;p&gt;Both the documentation and the prior article state that FILE columns cannot be used in grouping&lt;br&gt;
expressions. Testing it, support splits more finely than that.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GROUP BY file&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;accepted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SELECT DISTINCT file&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;accepted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GROUP BY file.uri&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;accepted (the documented approach)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;d.file = r.attachment&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;rejected — &lt;code&gt;The `=` does not support ordering on type "FILE"&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ORDER BY file&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;rejected — &lt;code&gt;The `sortorder` does not support ordering on type "FILE EXTERNAL"&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;What this tells us&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;GROUP BY&lt;/code&gt; and &lt;code&gt;DISTINCT&lt;/code&gt; both need equality semantics to decide group membership. Both are&lt;br&gt;
accepted while the equality operator itself is explicitly rejected. Since the error names the&lt;br&gt;
type's operator surface, this does not look like row-count-dependent behaviour.&lt;/p&gt;

&lt;p&gt;That said, this was confirmed on two rows. I cannot rule out that grouping degrades to identity&lt;br&gt;
comparison rather than value equality. In practice, use &lt;code&gt;GROUP BY file.uri&lt;/code&gt; and do not depend on&lt;br&gt;
&lt;code&gt;GROUP BY file&lt;/code&gt; being accepted. This is beta, so the behaviour may change.&lt;/p&gt;
&lt;h2&gt;
  
  
  Connecting to an FSx for ONTAP S3 Access Point
&lt;/h2&gt;

&lt;p&gt;This is the main question. The answer is "registration works, reads do not", and the cause is&lt;br&gt;
not a lack of Access Point support.&lt;/p&gt;
&lt;h3&gt;
  
  
  The four things registration needs
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Required form&lt;/th&gt;
&lt;th&gt;Symptom when wrong&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;External ID in the trust policy&lt;/td&gt;
&lt;td&gt;The Databricks &lt;strong&gt;account UUID&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;403 Forbidden&lt;/code&gt; from the storage provider. The metastore ID and the workspace ID do not work&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Self-assume in the trust policy&lt;/td&gt;
&lt;td&gt;Account root as the principal, with an &lt;code&gt;aws:PrincipalArn&lt;/code&gt; condition naming the role ARN&lt;/td&gt;
&lt;td&gt;Naming the role as its own principal fails at creation with &lt;code&gt;Invalid principal&lt;/code&gt;, because IAM validates that the principal exists. Unity Catalog requires the role to be able to assume itself&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resource in the IAM permission policy&lt;/td&gt;
&lt;td&gt;The &lt;strong&gt;access point ARN&lt;/strong&gt; (&lt;code&gt;arn:aws:s3:&amp;lt;region&amp;gt;:&amp;lt;account&amp;gt;:accesspoint/&amp;lt;name&amp;gt;&lt;/code&gt; and &lt;code&gt;.../object/*&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;AccessDeniedException&lt;/code&gt;. The alias-as-bucket-name form alone does not work&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;External location URL&lt;/td&gt;
&lt;td&gt;The &lt;strong&gt;alias&lt;/strong&gt; form (&lt;code&gt;s3://&amp;lt;alias&amp;gt;/&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;An ARN-style URL is rejected immediately with &lt;code&gt;url does not specify a valid bucket name&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;What this tells us&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There is an asymmetry worth committing to memory: the same Access Point must be written as an&lt;br&gt;
ARN in the IAM policy and as an alias in the external location URL. The AWS CLI works with the&lt;br&gt;
alias form of the ARN, so verifying connectivity with the CLI first and then writing the IAM&lt;br&gt;
policy will trip you up.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;aws:PrincipalArn&lt;/code&gt; approach works because that key resolves to the role ARN rather than the&lt;br&gt;
assumed-role session ARN. Rather than trust my memory of the condition key, I deployed the&lt;br&gt;
template, assumed the role, and assumed it again with those credentials to confirm.&lt;/p&gt;

&lt;p&gt;With all four satisfied, &lt;code&gt;CREATE EXTERNAL LOCATION&lt;/code&gt; succeeds with &lt;code&gt;skip_validation=False&lt;/code&gt; —&lt;br&gt;
meaning Unity Catalog's own validation passed. The external volume on top can be created too.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why reads fail
&lt;/h3&gt;

&lt;p&gt;After registration, every read path returns 403 or an authorisation error. The same on&lt;br&gt;
serverless SQL and on a classic DBR 18.2 cluster.&lt;/p&gt;

&lt;p&gt;This is the test that isolated the cause. Unity Catalog will vend the temporary credentials it&lt;br&gt;
uses if you ask for them. I requested them for both paths and used them from my workstation,&lt;br&gt;
with no Databricks compute or network involved.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Vended for&lt;/th&gt;
&lt;th&gt;&lt;code&gt;ListObjectsV2&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;HeadObject&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;S3 general bucket control path&lt;/td&gt;
&lt;td&gt;success&lt;/td&gt;
&lt;td&gt;success&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FSx for ONTAP S3 AP path&lt;/td&gt;
&lt;td&gt;AccessDenied&lt;/td&gt;
&lt;td&gt;403&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Same role, same session, same network; the only variable is which path the credentials were&lt;br&gt;
scoped to. The error states the cause explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;is not authorized to perform: s3:ListBucket on resource:
"arn:aws:s3:&amp;lt;region&amp;gt;:&amp;lt;account&amp;gt;:accesspoint/&amp;lt;name&amp;gt;"
because no session policy allows the s3:ListBucket action
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What this tells us&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;because no session policy allows&lt;/code&gt; is the decisive part. What is named is the session policy,&lt;br&gt;
not the role's permissions. By session policy I mean the down-scoped policy passed to&lt;br&gt;
&lt;code&gt;AssumeRole&lt;/code&gt; to constrain the role's permissions on a per-request basis.&lt;/p&gt;

&lt;p&gt;The policy body is generated by Unity Catalog and is not readable by the user. But since adding&lt;br&gt;
an access-point-ARN allowance to the role changes nothing, &lt;strong&gt;the session policy appears not to&lt;br&gt;
mention the access point ARN resource form at all.&lt;/strong&gt; Only the overlap between the session policy&lt;br&gt;
and the role policy is effective, so additions on the role side have no effect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There is no workaround on the user side.&lt;/strong&gt; Unity Catalog generates the policy, so the fix has&lt;br&gt;
to happen there. Emitting the access point ARN form in the session policy should resolve it,&lt;br&gt;
though I do not know what implementation constraints apply.&lt;/p&gt;

&lt;p&gt;Note also that validation at registration time (&lt;code&gt;skip_validation=False&lt;/code&gt;) passes while reads&lt;br&gt;
return 403. What that validation checks is not published, so I cannot account for the&lt;br&gt;
difference.&lt;/p&gt;

&lt;h3&gt;
  
  
  What changed since Part 2
&lt;/h3&gt;

&lt;p&gt;Part 2 of this series (measured 2026-05) reported that listing top-level files and reading an&lt;br&gt;
explicit file did work through a UC external location. In this run (2026-08-12), &lt;code&gt;read_files&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;list_files&lt;/code&gt;, &lt;code&gt;to_file&lt;/code&gt; and &lt;code&gt;dbutils.fs.ls&lt;/code&gt; all returned 403; no read path partially succeeded.&lt;/p&gt;

&lt;p&gt;That environment is gone, so I cannot account for the difference conclusively. The likely&lt;br&gt;
explanation is that the earlier partial successes were observed on an instance-profile path&lt;br&gt;
rather than through UC-vended credentials — Part 2 lists instance-profile direct access as a&lt;br&gt;
separate approach that works but sits outside UC governance, so the paths may not have been&lt;br&gt;
cleanly separated.&lt;/p&gt;

&lt;p&gt;This run had a S3 general bucket control alongside and includes the vended-credential test outside&lt;br&gt;
Databricks entirely, so take these results as the current ones. I will annotate the Part 2&lt;br&gt;
table accordingly.&lt;/p&gt;

&lt;p&gt;This test has prerequisites. Enable &lt;strong&gt;External Data Access&lt;/strong&gt; on the metastore and grant&lt;br&gt;
&lt;code&gt;EXTERNAL USE SCHEMA&lt;/code&gt; and &lt;code&gt;EXTERNAL USE LOCATION&lt;/code&gt;. Both are disabled by default. They are the&lt;br&gt;
controls that allow credentials to be used externally, so they are worth understanding&lt;br&gt;
independently of this issue.&lt;/p&gt;

&lt;h3&gt;
  
  
  A table to work through when you see a 403
&lt;/h3&gt;

&lt;p&gt;Arranged so that anyone hitting the same 403 can check top to bottom. &lt;strong&gt;None of these is the&lt;br&gt;
cause.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What to suspect&lt;/th&gt;
&lt;th&gt;Finding&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Unity Catalog volume privileges&lt;/td&gt;
&lt;td&gt;The failing volume had &lt;code&gt;READ VOLUME&lt;/code&gt;; the working control volume had no privileges at all&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IAM role permissions&lt;/td&gt;
&lt;td&gt;Assumed the same role from my workstation and listed successfully. HEAD on a real object returned 200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HEAD on a missing key returning 403 (killing the &lt;code&gt;_delta_log&lt;/code&gt; probe)&lt;/td&gt;
&lt;td&gt;The Access Point returned 404 for every shape I tried — same as S3 general bucket&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;x-amz-expected-bucket-owner&lt;/code&gt; being rejected&lt;/td&gt;
&lt;td&gt;Accepted by both the Access Point and the control bucket&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A different endpoint&lt;/td&gt;
&lt;td&gt;Both Databricks and the AWS CLI use &lt;code&gt;&amp;lt;alias&amp;gt;.s3.&amp;lt;region&amp;gt;.amazonaws.com&lt;/code&gt;, which CNAMEs to &lt;code&gt;s3-r-w.&amp;lt;region&amp;gt;.amazonaws.com&lt;/code&gt;. Same host&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Insufficient compute role permissions&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;sts:GetCallerIdentity&lt;/code&gt; raised &lt;code&gt;NoCredentialsError&lt;/code&gt; on the driver. The cluster has no default AWS credentials; the compute role is not in the path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The S3 gateway endpoint&lt;/td&gt;
&lt;td&gt;No change after disassociating the route. The failure reproduces outside the VPC with vended credentials, so this path is not the problem&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One note on the last row. A Databricks-managed VPC creates an S3 gateway endpoint at workspace&lt;br&gt;
creation, and the private route tables point the S3 managed prefix list at it. That was not the&lt;br&gt;
cause here, but for designs that reach an S3 AP from inside a VPC, check first whether the&lt;br&gt;
alias resolves into the prefix list's range. I did not reconcile the resolved CIDRs against the&lt;br&gt;
prefix list contents in this environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  What object tags can actually hold
&lt;/h2&gt;

&lt;p&gt;If you want metadata to live with the file, object tags are one option: tag at write time, read&lt;br&gt;
from the table side later. That path has constraints.&lt;/p&gt;

&lt;p&gt;The following was measured without Databricks in the picture — boto3 from my workstation&lt;br&gt;
straight against the FSx for ONTAP S3 Access Point, with the same operations running against&lt;br&gt;
the S3 general bucket control bucket.&lt;/p&gt;

&lt;p&gt;Start with what works. &lt;code&gt;PutObjectTagging&lt;/code&gt;, &lt;code&gt;GetObjectTagging&lt;/code&gt; and &lt;code&gt;DeleteObjectTagging&lt;/code&gt; all&lt;br&gt;
function, as do &lt;code&gt;x-amz-meta-*&lt;/code&gt; headers and &lt;code&gt;x-amz-tagging&lt;/code&gt; on the same &lt;code&gt;PutObject&lt;/code&gt; as the data.&lt;br&gt;
The limits (10 tags per object, 128 characters for a key, 256 for a value) are the same values&lt;br&gt;
S3 general bucket documents; I did not measure the boundaries.&lt;/p&gt;

&lt;p&gt;Reading through a second access point on the same volume returned tags written through the&lt;br&gt;
first. &lt;strong&gt;Tag retention is not scoped per access point.&lt;/strong&gt; For existing NAS assets shared across&lt;br&gt;
several paths, that property works in your favour. But I did not observe where the tags are&lt;br&gt;
physically stored, so visibility from NFS or SMB, and retention across SnapMirror, FlexClone&lt;br&gt;
and Snapshot restore, remain unverified.&lt;/p&gt;

&lt;p&gt;Two constraints change how you design.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An object overwrite clears tags and user metadata.&lt;/strong&gt; Nothing errors. A pipeline that rewrites&lt;br&gt;
a file has to re-apply them in the same &lt;code&gt;PutObject&lt;/code&gt;. My understanding is that S3 general bucket behaves&lt;br&gt;
the same way, since &lt;code&gt;PutObject&lt;/code&gt; replaces the object, so treat this as a general S3 design point&lt;br&gt;
rather than something specific to FSx for ONTAP. I did not run a side-by-side control for this&lt;br&gt;
particular case.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tag values are effectively ASCII.&lt;/strong&gt; Every printable Latin-1 character I tried&lt;br&gt;
(U+00A1–U+00FF) was accepted. Above U+0100, most are rejected with &lt;code&gt;InvalidTag&lt;/code&gt; — but not all.&lt;br&gt;
A minority of CJK strings are accepted, deterministically, 6 runs out of 6.&lt;/p&gt;

&lt;p&gt;To find the unit of validation, I tested single characters:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;U+5206&lt;/td&gt;
&lt;td&gt;&lt;code&gt;InvalidTag&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;U+985E&lt;/td&gt;
&lt;td&gt;&lt;code&gt;InvalidTag&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;U+5206 U+985E&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;accepted&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;U+6771&lt;/td&gt;
&lt;td&gt;&lt;code&gt;InvalidTag&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;U+4EAC&lt;/td&gt;
&lt;td&gt;&lt;code&gt;InvalidTag&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;U+6771 U+4EAC&lt;/td&gt;
&lt;td&gt;&lt;code&gt;InvalidTag&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;What this tells us&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Two two-character strings, every constituent character rejected on its own, and the two strings&lt;br&gt;
disagree with each other. &lt;strong&gt;Validity is determined by the complete byte sequence, not as a&lt;br&gt;
function of the characters in it&lt;/strong&gt; — so this is not a per-character allowlist.&lt;/p&gt;

&lt;p&gt;For the two-character pairs I tested, results were identical forwards and backwards, and&lt;br&gt;
identical as a tag key and as a tag value. I did not test longer strings or other scripts.&lt;/p&gt;

&lt;p&gt;I raised this with AWS Support. After checking it against the S3 and FSx for ONTAP&lt;br&gt;
documentation, they gave the view that the pattern does not match any intentional validation&lt;br&gt;
they could identify, and escalated it to the service team as a potential defect in the tag&lt;br&gt;
validation layer.&lt;/p&gt;

&lt;p&gt;Their reply also quoted the documented character set: letters, whitespace, and&lt;br&gt;
&lt;code&gt;+ - = . _ : / @&lt;/code&gt;. That does not predict the split under either reading. If "letters" means&lt;br&gt;
Unicode letters, every string I tested qualifies yet half are rejected. If it means ASCII&lt;br&gt;
letters, all of them should be rejected yet half are accepted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The design guidance is simple: keep tags ASCII and keep localised text in a column.&lt;/strong&gt; Today&lt;br&gt;
the situation is "some strings pass and some do not", which is more awkward to handle than a&lt;br&gt;
uniform restriction. I will update the repository and this article when a determination&lt;br&gt;
arrives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design tips
&lt;/h2&gt;

&lt;p&gt;The same material, ordered the way you actually make the decisions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FILE type&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Decide between &lt;code&gt;FILE EXTERNAL&lt;/code&gt; and &lt;code&gt;FILE MANAGED&lt;/code&gt; &lt;strong&gt;before&lt;/strong&gt; ingesting. Checksum and filename handling differ, and changing course later means re-ingesting&lt;/li&gt;
&lt;li&gt;Give the FileSpace its own volume. On the same volume, &lt;code&gt;INSERT&lt;/code&gt; fails — and because the error surfaces at ingest rather than at configuration time, diagnosis is slower&lt;/li&gt;
&lt;li&gt;Copy the filename to its own column before ingesting into &lt;code&gt;FILE MANAGED&lt;/code&gt;. It is replaced with a UUID and the extension does not survive&lt;/li&gt;
&lt;li&gt;Do not rely on &lt;code&gt;content_type&lt;/code&gt; inference; pass it explicitly&lt;/li&gt;
&lt;li&gt;Group by &lt;code&gt;file.uri&lt;/code&gt;. Do not depend on &lt;code&gt;GROUP BY file&lt;/code&gt; being accepted&lt;/li&gt;
&lt;li&gt;Automatic garbage collection does not run in beta. Build manual FileSpace deletion into operations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Unity Catalog and FSx for ONTAP S3 Access Points&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The external ID in the trust policy is the Databricks &lt;strong&gt;account UUID&lt;/strong&gt;. The metastore ID does not work&lt;/li&gt;
&lt;li&gt;The trust policy needs self-assume: account root as the principal with an &lt;code&gt;aws:PrincipalArn&lt;/code&gt; condition naming the role ARN&lt;/li&gt;
&lt;li&gt;The IAM permission policy needs the &lt;strong&gt;access point ARN&lt;/strong&gt;, including &lt;code&gt;.../object/*&lt;/code&gt;. The alias-form ARN alone does not work&lt;/li&gt;
&lt;li&gt;The external location URL takes the &lt;strong&gt;alias&lt;/strong&gt; form. ARN-style URLs are rejected&lt;/li&gt;
&lt;li&gt;Reads do not work today. If zero-copy is a requirement, evaluate AWS-native engines that authorise in the caller's own IAM context alongside it (this repository has walkthroughs under &lt;code&gt;integrations/athena&lt;/code&gt;, &lt;code&gt;integrations/glue&lt;/code&gt;, &lt;code&gt;integrations/emr-spark&lt;/code&gt;, &lt;code&gt;integrations/redshift-spectrum&lt;/code&gt; and &lt;code&gt;integrations/lake-formation&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Object tags&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep tags ASCII and keep localised text in a column&lt;/li&gt;
&lt;li&gt;Re-apply tags in the same &lt;code&gt;PutObject&lt;/code&gt; when you rewrite a file. An overwrite clears them&lt;/li&gt;
&lt;li&gt;Tag retention is not scoped per access point, so the same tags are readable through several access points&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How to test&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run a S3 general bucket control alongside. Without it you cannot separate a defect in your environment from Access Point behaviour, and the first 403 looks like the answer&lt;/li&gt;
&lt;li&gt;If the platform exposes credential vending, use those credentials from your own machine and compare. Isolating the problem is much faster without the vendor's compute in the path&lt;/li&gt;
&lt;li&gt;Verify teardown by reconciling against a baseline. NAT Gateways do not disappear when you delete the workspace&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Run it yourself
&lt;/h2&gt;

&lt;p&gt;All of the above is in the repository as templates and scripts: one template, one script, one&lt;br&gt;
runbook.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Artefact&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;integrations/databricks/uc-storage-credential-role.yaml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The IAM role the Unity Catalog storage credential assumes, plus a S3 general bucket control bucket&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;shared/scripts/probe_uc_external_location.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Seeds identical object tags on both sides, registers both external locations with validation on, reads &lt;code&gt;_object_metadata&lt;/code&gt; through each, prints a verdict&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docs/en/databricks-verification-runbook.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Prerequisites, where each parameter comes from, the verdict branches, dependency-ordered teardown, measured cost&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Baseline first. This is what makes "teardown looks clean" verifiable&lt;/span&gt;
python3 shared/scripts/audit_databricks_workspace_footprint.py &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; &amp;lt;region&amp;gt; &lt;span class="nt"&gt;--save&lt;/span&gt; /tmp/baseline.json

&lt;span class="c"&gt;# 2. The IAM role and control bucket&lt;/span&gt;
aws cloudformation deploy &lt;span class="nt"&gt;--region&lt;/span&gt; &amp;lt;region&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--stack-name&lt;/span&gt; fsxn-databricks-uc-credential &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--template-file&lt;/span&gt; integrations/databricks/uc-storage-credential-role.yaml &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--capabilities&lt;/span&gt; CAPABILITY_NAMED_IAM &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--parameter-overrides&lt;/span&gt; file://cfn-params/databricks-uc-storage-credential.json

&lt;span class="c"&gt;# 3. Compare. --control-bucket is deliberately mandatory&lt;/span&gt;
.venv/bin/python shared/scripts/probe_uc_external_location.py &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--profile&lt;/span&gt; &amp;lt;your-profile&amp;gt; &lt;span class="nt"&gt;--role-arn&lt;/span&gt; &amp;lt;from stack output&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--ap-alias&lt;/span&gt; &amp;lt;&lt;span class="nb"&gt;alias&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;-ext-s3alias&lt;/span&gt; &lt;span class="nt"&gt;--ap-name&lt;/span&gt; &amp;lt;name&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--control-bucket&lt;/span&gt; &amp;lt;from stack output&amp;gt; &lt;span class="nt"&gt;--region&lt;/span&gt; &amp;lt;region&amp;gt; &lt;span class="nt"&gt;--vend-check&lt;/span&gt;

&lt;span class="c"&gt;# 4. Tear down and reconcile against the baseline&lt;/span&gt;
python3 shared/scripts/audit_databricks_workspace_footprint.py &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; &amp;lt;region&amp;gt; &lt;span class="nt"&gt;--compare&lt;/span&gt; /tmp/baseline.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script prints one of four verdicts. The first is the important one.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Inconclusive&lt;/strong&gt; — the S3 general bucket control could not be read either, so this run says nothing about the Access Point. Fix the environment first&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Registration succeeded, reads denied&lt;/strong&gt; — the result in this article&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Registration and reads both succeeded&lt;/strong&gt; — the platform changed. Please tell me which release&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Registration itself failed&lt;/strong&gt; — almost certainly a missing Access Point ARN in the IAM policy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One note on the parameter file: a &lt;code&gt;_comment&lt;/code&gt; key in a CloudFormation parameter file makes the&lt;br&gt;
CLI fail with &lt;code&gt;Unknown parameter in Parameters[n]&lt;/code&gt;. The description of each parameter lives in&lt;br&gt;
&lt;code&gt;cfn-params/README.md&lt;/code&gt; instead, so fill in values with that open alongside.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you are evaluating a different platform
&lt;/h2&gt;

&lt;p&gt;The specific bug is Databricks'. The shape is not, and the shape is the part worth taking away&lt;br&gt;
if you are looking at a different product.&lt;/p&gt;

&lt;p&gt;Managed lakehouse platforms generally reach into your cloud account by assuming a role you&lt;br&gt;
create, then constrain that role at runtime with a down-scoped session policy so that&lt;br&gt;
credentials handed to a query cannot roam. That is good design.&lt;/p&gt;

&lt;p&gt;But the policy has to name your storage. If the storage is addressed through an access point&lt;br&gt;
while the policy names it in bucket-form ARNs, the two do not match and authorisation fails —&lt;br&gt;
however correct your own role policy is.&lt;/p&gt;

&lt;p&gt;So the question to put to a platform you are evaluating, before you commit to an architecture,&lt;br&gt;
can be narrow and answerable:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When you vend temporary credentials for an external location, which resource ARN forms does&lt;br&gt;
the session policy contain? Specifically, does it include the S3 access point ARN form in&lt;br&gt;
addition to the bucket form?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And if the platform exposes credential vending, you can verify it without asking anyone:&lt;br&gt;
request the credentials, use them from your own machine, and compare a native bucket path&lt;br&gt;
against an access point path. That is what &lt;code&gt;--vend-check&lt;/code&gt; above does, and it was the only test&lt;br&gt;
in this exercise that produced a conclusion with no room for interpretation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost and cleanup
&lt;/h2&gt;

&lt;p&gt;I created a test workspace and destroyed it the same day. The workspace mode that uses your own&lt;br&gt;
AWS account creates a VPC with a NAT Gateway. A NAT Gateway runs roughly 45 USD a month per&lt;br&gt;
gateway (hourly charge only, excluding data processing, at the August 2026 Tokyo region rate).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;These resources are created directly rather than through a CloudFormation stack, so deleting&lt;br&gt;
the workspace does not remove them.&lt;/strong&gt; This is the easiest place to make a foolish mistake during&lt;br&gt;
cleanup.&lt;/p&gt;

&lt;p&gt;I recorded a baseline before starting and reconciled afterwards. Available NAT Gateways back to&lt;br&gt;
zero; VPC, both IAM roles and the bucket gone, with no unassociated Elastic IPs.&lt;/p&gt;

&lt;p&gt;I turned that check into a script (&lt;code&gt;shared/scripts/audit_databricks_workspace_footprint.py&lt;/code&gt;). In&lt;br&gt;
a shared account a plain listing is not a verdict, because most of what it shows belongs to&lt;br&gt;
someone else. Run it once before creating anything and once after teardown; it exits non-zero if&lt;br&gt;
anything exists that was not in the baseline. Running it confirmed this teardown was clean and&lt;br&gt;
also surfaced leftover roles from an earlier workspace. The cost breakdown is in the runbook with&lt;br&gt;
measured figures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current state and open items
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Established&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CREATE STORAGE CREDENTIAL&lt;/code&gt; / &lt;code&gt;CREATE EXTERNAL LOCATION&lt;/code&gt; / &lt;code&gt;CREATE EXTERNAL VOLUME&lt;/code&gt; succeed against an FSx for ONTAP S3 AP alias, with Unity Catalog validation left on&lt;/li&gt;
&lt;li&gt;Reads through it return 403, on both serverless SQL and a classic DBR 18.2 cluster&lt;/li&gt;
&lt;li&gt;The same failure reproduces outside Databricks, using vended credentials from a workstation&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;_object_metadata&lt;/code&gt; on S3 general bucket returns object tags and user metadata correctly&lt;/li&gt;
&lt;li&gt;Object tag acceptance is determined by the complete byte sequence, and within the range tested is neither order- nor position-dependent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Not established&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VPC-origin Access Points, and Access Points with WINDOWS identity&lt;/li&gt;
&lt;li&gt;Whether &lt;code&gt;_object_metadata&lt;/code&gt; would read tags through an Access Point if the session policy were fixed&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ai_parse_document&lt;/code&gt; over files served from an Access Point&lt;/li&gt;
&lt;li&gt;Throughput and latency of these paths&lt;/li&gt;
&lt;li&gt;Whether &lt;code&gt;GROUP BY&lt;/code&gt; on a FILE column is correct at scale, or merely accepted&lt;/li&gt;
&lt;li&gt;Where object tags are physically stored; visibility from NFS or SMB; retention across SnapMirror, FlexClone, Snapshot restore and FabricPool tiering&lt;/li&gt;
&lt;li&gt;The boundary values for tag limits (10 tags, 128-character keys, 256-character values)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Reporting status&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The object tag behaviour has been escalated through AWS Support to the FSx for ONTAP service&lt;br&gt;
team and is awaiting a determination. What I asked for is not a timeline but the distinction:&lt;br&gt;
a defect that will be fixed, or an undocumented restriction that gets written down. Either&lt;br&gt;
answer lets me state the constraint accurately.&lt;/p&gt;

&lt;p&gt;The session policy issue and the operator inconsistency go to Databricks when this article&lt;br&gt;
publishes.&lt;/p&gt;

&lt;p&gt;This repository carried an entry from May 2026 saying Unity Catalog external locations do not&lt;br&gt;
support S3 Access Points. Registration does work; the entry has been corrected. If you read the&lt;br&gt;
earlier version, please note the change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;The FILE type is a well-built mechanism for bringing unstructured data under table governance.&lt;br&gt;
For referencing files on an FSx for ONTAP S3 Access Point with zero copies, though, it does not&lt;br&gt;
get there today. Registration works, so you can try it, but reads fail, and because the cause&lt;br&gt;
sits in the session policy on the vending side, users cannot fix it.&lt;/p&gt;

&lt;p&gt;If zero-copy is the requirement, AWS-native engines that authorise in the caller's own IAM&lt;br&gt;
context are the more straightforward path for now — in exchange for assembling table-level&lt;br&gt;
governance yourself with Lake Formation. If Unity Catalog governance is the requirement, then&lt;br&gt;
for the time being data placement has to be part of the design.&lt;/p&gt;

&lt;p&gt;The object tag character set is awaiting a determination. When it moves, I will update both the&lt;br&gt;
article and the repository.&lt;/p&gt;

&lt;p&gt;I hope this is useful to someone.&lt;/p&gt;

&lt;p&gt;See you next time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related posts
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Previous: &lt;a href="https://hakobiya.hatenablog.com/entry/fsxn-lakehouse-part2-databricks" rel="noopener noreferrer"&gt;Part 2: Databricks and FSx for ONTAP S3 Access Points&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Next: &lt;a href="https://dev.to/aws-builders/why-delta-iceberg-and-hudi-cant-write-to-fsx-s3-access-points-and-what-works-instead-5be3"&gt;Part 8: OpenSharing and FSx for ONTAP&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Series index: &lt;a href="https://hakobiya.hatenablog.com/entry/fsxn-lakehouse-part0-overview" rel="noopener noreferrer"&gt;What works, what does not, and why&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  About this article
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;This series is based on verification in the &lt;a href="https://github.com/Yoshiki0705/fsxn-lakehouse-integrations" rel="noopener noreferrer"&gt;fsxn-lakehouse-integrations&lt;/a&gt; repository&lt;/li&gt;
&lt;li&gt;Author: Yoshiki Fujiwara&lt;/li&gt;
&lt;li&gt;Affiliation: NetApp G.K. Japan&lt;/li&gt;
&lt;li&gt;Role: Senior Cloud Solutions Architect - AWS&lt;/li&gt;
&lt;li&gt;Topic: Amazon FSx for NetApp ONTAP / Hybrid Cloud / Generative AI Data Architecture&lt;/li&gt;
&lt;li&gt;Level: 200-300&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;All test environments have been deleted. The behaviours described are measurements from&lt;br&gt;
2026-08-12 in one specific configuration and may change with platform updates. Beta features are&lt;br&gt;
involved, so please check against current documentation before making production decisions.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>databricks</category>
      <category>fsxforontap</category>
      <category>lakehouse</category>
    </item>
  </channel>
</rss>
