<?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: Zelmar Michelini</title>
    <description>The latest articles on DEV Community by Zelmar Michelini (@zelmario).</description>
    <link>https://dev.to/zelmario</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%2F4059599%2F67a8f738-423c-4fa9-8c7f-b01d9cb18fd9.png</url>
      <title>DEV Community: Zelmar Michelini</title>
      <link>https://dev.to/zelmario</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zelmario"/>
    <language>en</language>
    <item>
      <title>Getting FTDC out of MongoDB Atlas</title>
      <dc:creator>Zelmar Michelini</dc:creator>
      <pubDate>Sun, 02 Aug 2026 22:10:03 +0000</pubDate>
      <link>https://dev.to/zelmario/getting-ftdc-out-of-mongodb-atlas-12mo</link>
      <guid>https://dev.to/zelmario/getting-ftdc-out-of-mongodb-atlas-12mo</guid>
      <description>&lt;p&gt;Every MongoDB server writes FTDC,  Full Time Diagnostic Data Capture,  into a &lt;code&gt;diagnostic.data&lt;/code&gt; folder next to its log. Roughly 5,700 metrics, once per second, compressed hard enough that days of history fit in a few hundred megabytes. It's what MongoDB support asks for when you open a performance ticket, and it's the difference between "the database was slow" and "the WiredTiger ticket pool hit zero at 14:32:07."&lt;/p&gt;

&lt;p&gt;On Atlas there's no such folder you can reach. Not in the UI, and the log download only gives you &lt;code&gt;mongodb.gz&lt;/code&gt; and the audit logs. Search for a way around it and you'll find &lt;a href="https://github.com/simagix/keyhole/issues/57" rel="noopener noreferrer"&gt;an issue on keyhole from February 2021&lt;/a&gt; asking exactly this, still unanswered.&lt;/p&gt;

&lt;p&gt;I maintain &lt;a href="https://github.com/zelmario/Big-hole" rel="noopener noreferrer"&gt;Big Hole&lt;/a&gt;, an FTDC viewer, so this wall is one I hit often on other people's behalf. Here's the way through it, measured on an Atlas M10 running 8.0.29.&lt;/p&gt;

&lt;h2&gt;
  
  
  Have Atlas build the bundle
&lt;/h2&gt;

&lt;p&gt;The Admin API has an endpoint that packages FTDC on demand. It only exists in the v1.0 API,  I checked the v2 OpenAPI spec, 325 endpoints, it isn't among them,  but it works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Three things you need first:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A programmatic API key&lt;/strong&gt; (not a database user): Atlas UI → &lt;em&gt;Access Manager&lt;/em&gt; → &lt;em&gt;Project Access&lt;/em&gt; → &lt;em&gt;Create Application&lt;/em&gt; → API Key. &lt;code&gt;Project Owner&lt;/code&gt; works. If your organisation requires an access list for API keys, add your IP or the first call returns &lt;code&gt;ORG_REQUIRES_ACCESS_LIST&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The project ID&lt;/strong&gt;,  24 hex characters, not a UUID. It's in the URL: &lt;code&gt;cloud.mongodb.com/v2/&amp;lt;PROJECT_ID&amp;gt;/clusters&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The replica set name&lt;/strong&gt;,  the internal one (&lt;code&gt;atlas-abc123-shard-0&lt;/code&gt;), not the cluster's display name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PUB&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$PRIV&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--digest&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://cloud.mongodb.com/api/atlas/v1.0/groups/&lt;/span&gt;&lt;span class="nv"&gt;$GROUP_ID&lt;/span&gt;&lt;span class="s2"&gt;/processes"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.results[] | "\(.hostname):\(.port) \(.replicaSetName)"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The three calls
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;BASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://cloud.mongodb.com/api/atlas/v1.0/groups/&lt;/span&gt;&lt;span class="nv"&gt;$GROUP_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;AUTH&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PUB&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$PRIV&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--digest&lt;/span&gt; &lt;span class="nt"&gt;-sS&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# 1. create the job&lt;/span&gt;
curl &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;AUTH&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;/logCollectionJobs"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"resourceType":"REPLICASET",
       "resourceName":"atlas-abc123-shard-0",
       "redacted":true,
       "sizeRequestedPerFileBytes":100000000,
       "logTypes":["FTDC"]}'&lt;/span&gt;
&lt;span class="c"&gt;# -&amp;gt; {"id":"6a6bd2dd0fc2dcfc08d68af1"}&lt;/span&gt;

&lt;span class="c"&gt;# 2. poll until SUCCESS&lt;/span&gt;
curl &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;AUTH&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;/logCollectionJobs/6a6bd2dd0fc2dcfc08d68af1"&lt;/span&gt;

&lt;span class="c"&gt;# 3. download&lt;/span&gt;
curl &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;AUTH&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;/logCollectionJobs/6a6bd2dd0fc2dcfc08d68af1/download"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; ftdc.tar.gz
&lt;span class="nb"&gt;tar &lt;/span&gt;xzf ftdc.tar.gz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;sizeRequestedPerFileBytes&lt;/code&gt; caps each collected file; 100 MB is more than any single FTDC file, so you get everything. &lt;code&gt;resourceType&lt;/code&gt; also accepts &lt;code&gt;PROCESS&lt;/code&gt; and &lt;code&gt;CLUSTER&lt;/code&gt;,  &lt;code&gt;REPLICASET&lt;/code&gt; is what gets every member in one job.&lt;/p&gt;

&lt;p&gt;Mine returned &lt;code&gt;SUCCESS&lt;/code&gt; on the first poll. The job response also carries &lt;code&gt;expirationDate&lt;/code&gt; (the bundle is kept 30 days) and &lt;code&gt;uncompressedSizeTotalBytes&lt;/code&gt;. What comes out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mongodb-logfiles_atlas-abc123-shard-0_2026-07-30T2240Z/
  ac-...-shard-00-00.mongodb.net/27017/diagnostic.data/metrics.2026-07-30T22-00-06Z-00000
  ac-...-shard-00-00.mongodb.net/27017/diagnostic.data/metrics.interim
  ac-...-shard-00-01.mongodb.net/27017/diagnostic.data/...
  ac-...-shard-00-02.mongodb.net/27017/diagnostic.data/...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The genuine files mongod wrote, one &lt;code&gt;diagnostic.data&lt;/code&gt; per member,  the layout every FTDC tool already understands.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't skip &lt;code&gt;metrics.interim&lt;/code&gt;.&lt;/strong&gt; It's the chunk the server hasn't flushed to a numbered file yet, and it holds the most recent samples. In my bundle the newest numbered file stopped at 22:33 while the interim carried data to 22:38.&lt;/p&gt;

&lt;p&gt;I found this endpoint by reading &lt;a href="https://github.com/maoertel/mongodb-ftdc" rel="noopener noreferrer"&gt;maoertel/mongodb-ftdc&lt;/a&gt;, a Rust CLI that automates the whole flow. Five stars. It deserves more.&lt;/p&gt;

&lt;h2&gt;
  
  
  How far back does it go?
&lt;/h2&gt;

&lt;p&gt;The bundle's metadata document carries &lt;code&gt;getCmdLineOpts&lt;/code&gt;, which shows how Atlas starts mongod:&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;diagnosticDataCollectionDirectorySizeMB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;400&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Double the mongod default of 200 MB, and a hard ceiling: when the directory fills, the oldest file is deleted.&lt;/p&gt;

&lt;p&gt;How long that lasts depends on load, because FTDC compresses by delta,  a metric that never moves costs nearly nothing, one that changes every second costs real bytes. On an idle cluster I measured ~0.93 MB per 32 minutes per node, which extrapolates to a window near ten days. Under real traffic, expect &lt;strong&gt;two to five days&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If the incident you're chasing is older than a week, it's gone and no API brings it back. Pull it while it's fresh,  that's most of the value of this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is this so hard?
&lt;/h2&gt;

&lt;p&gt;Worth stating plainly, because none of the above should have taken an afternoon.&lt;/p&gt;

&lt;p&gt;FTDC is the first artifact MongoDB support asks for when you open a performance ticket. It contains no user data,  it's counters, and I checked: in 228 KB of diagnostic document there are 38 distinct strings, none of which is the name of a database or collection on the cluster. It's the single most useful thing you can hand someone debugging your server.&lt;/p&gt;

&lt;p&gt;And on MongoDB's own managed product, the only way to obtain it is an endpoint that appears nowhere in the log download UI, isn't mentioned in the Atlas documentation, and lives on a deprecated API version. The question "how do I get diagnostic data out of Atlas" has been sitting unanswered on GitHub since 2021, so I'm evidently not the first person to lose an afternoon to it.&lt;/p&gt;

&lt;p&gt;I doubt it's deliberate,  it reads more like nobody owning the problem of making it discoverable. But the effect is real: Atlas customers run one tier of observability behind the engineers who support them, and the gap is filled by community projects with single-digit star counts. A "Download diagnostic data" button next to "Download logs" would close it tomorrow.&lt;/p&gt;

&lt;h2&gt;
  
  
  A note on monitoring permissions
&lt;/h2&gt;

&lt;p&gt;While working out the minimum privileges a diagnostic collector needs, I found something worth passing on regardless of whether you ever touch FTDC.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;clusterMonitor&lt;/code&gt; is the role most people grant when something needs to monitor MongoDB. From &lt;code&gt;src/mongo/db/auth/builtin_roles.yml&lt;/code&gt; in the server source:&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;clusterMonitor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;adminOnly&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;roles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
      &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;config&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
      &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;local&lt;/span&gt;      &lt;span class="c1"&gt;# &amp;lt;-- includes local.oplog.rs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;read&lt;/code&gt; on &lt;code&gt;local&lt;/code&gt; includes &lt;code&gt;local.oplog.rs&lt;/code&gt;, and the oplog holds the full content of every recent write. An account with &lt;code&gt;clusterMonitor&lt;/code&gt; can page back through everything your application wrote for as long as the oplog window holds,  the documents themselves, not statistics about them. It reads as a metrics role and grants a data-reading one.&lt;/p&gt;

&lt;p&gt;Monitoring agents often legitimately need it. But if something in your environment holds &lt;code&gt;clusterMonitor&lt;/code&gt; purely to scrape counters, it can read your data, and whoever approved that grant probably didn't intend it. For comparison, Atlas accepts custom roles at this granularity:&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;"roleName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"diagnosticsOnly"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"actions"&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="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;"SERVER_STATUS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nl"&gt;"resources"&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="nl"&gt;"cluster"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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="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;"REPLSET_GET_STATUS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"resources"&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="nl"&gt;"cluster"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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="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;"COLL_STATS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="nl"&gt;"resources"&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="nl"&gt;"db"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"local"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"collection"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"oplog.rs"&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;I tested a user holding only that: it can't read a collection, can't read the oplog, and &lt;code&gt;listDatabases&lt;/code&gt; returns just &lt;code&gt;local&lt;/code&gt;,  it can't even establish that your application's databases exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading what you collected
&lt;/h2&gt;

&lt;p&gt;The tarball is ready to open,  but the tooling here is thin, which is why I built one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/zelmario/Big-hole" rel="noopener noreferrer"&gt;&lt;strong&gt;Big Hole&lt;/strong&gt;&lt;/a&gt; runs entirely in your browser. No backend, no upload, no container: you drop the untarred folder in and it decodes locally. That matters more than it sounds for this particular file,  you're often looking at a bundle from someone else's production cluster, and "nothing leaves your machine" is the difference between being allowed to analyse it and not. It opens the Atlas tarball as-is, puts all three members on one time axis, shows who was primary when, and has automated checks for the usual pathologies (ticket pool exhaustion, cache pressure, flow control).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/simagix/keyhole" rel="noopener noreferrer"&gt;&lt;strong&gt;keyhole&lt;/strong&gt;&lt;/a&gt; has been the reference tool for years and renders FTDC through Grafana. If you want dashboards and don't mind running the stack, start there.&lt;/p&gt;




&lt;p&gt;Everything above was measured on Atlas M10 / MongoDB 8.0.29 against the current server source. The v1.0 endpoint is on borrowed time; if it stops working, I'd like to know.&lt;/p&gt;

</description>
      <category>mongodb</category>
      <category>atlas</category>
      <category>database</category>
      <category>performance</category>
    </item>
  </channel>
</rss>
