<?xml version="1.0" encoding="UTF-8" ?><!-- generator=Zoho Sites --><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><atom:link href="https://www.highperformance.tech/blogs/author/brad-fair/feed" rel="self" type="application/rss+xml"/><title>High Performance Technologies - High Performance Technologies Blog by Brad Fair</title><description>High Performance Technologies - High Performance Technologies Blog by Brad Fair</description><link>https://www.highperformance.tech/blogs/author/brad-fair</link><lastBuildDate>Thu, 23 Apr 2026 06:38:01 -0700</lastBuildDate><generator>http://zoho.com/sites/</generator><item><title><![CDATA[Why "If It Ain't Broke" Is the Wrong Strategy for Tableau Server]]></title><link>https://www.highperformance.tech/blogs/post/tableau-server-update-strategy</link><description><![CDATA[<img align="left" hspace="5" src="https://www.highperformance.tech/Blog Images/Tableau Server Updates Blog/Size and Scope_thumbnail_linkedin.jpg"/>Deferring Tableau Server updates turns routine maintenance into a project — and eventually, an outage. Here's how to choose an update cadence that works.]]></description><content:encoded><![CDATA[<div class="zpcontent-container blogpost-container "><div data-element-id="elm_LWzwuUJpQ92YzqUwx25P8Q" data-element-type="section" class="zpsection "><style type="text/css"></style><div class="zpcontainer-fluid zpcontainer"><div data-element-id="elm_OV0P56DoSRazMdsMFA3LCQ" data-element-type="row" class="zprow zprow-container zpalign-items-flex-start zpjustify-content- " data-equal-column="false"><style type="text/css"></style><div data-element-id="elm_1QQZR6_GRWyzFWJkYRop4Q" data-element-type="column" class="zpelem-col zpcol-12 zpcol-md-12 zpcol-sm-12 zpalign-self- "><style type="text/css"></style><div data-element-id="elm_w4O2VKhFSdyvPhco-dcs0w" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-center zptext-align-mobile-center zptext-align-tablet-center " data-editor="true"><p></p><div><p style="text-align:left;">There is a version of this story that plays out more often than it should.</p><p style="text-align:left;"><br/></p><p style="text-align:left;">A company's Tableau Server has been humming along for years without issue. Then something changes — an SSL certificate expires, an OS patch is applied, a staff member turns over — and suddenly the server is down. The team calls Tableau support. Support pulls up the version number and says, essentially: You are too far out of date, and your version is no longer supported.</p><p style="text-align:left;"><br/></p><p style="text-align:left;">That is not a hypothetical. It is what happens when people mistake &quot;if it ain't broke, don't fix it&quot; for an infrastructure strategy.</p><p style="text-align:left;"><br/></p><p style="text-align:left;">The good news is that this outcome is entirely preventable — it just requires putting a plan in place.</p></div><p></p></div>
</div><div data-element-id="elm_XA8aANa2UhkULUYQp305aA" data-element-type="divider" class="zpelement zpelem-divider "><style type="text/css"></style><style></style><div class="zpdivider-container zpdivider-line zpdivider-align-center zpdivider-align-mobile-center zpdivider-align-tablet-center zpdivider-width100 zpdivider-line-style-solid "><div class="zpdivider-common"></div>
</div></div><div data-element-id="elm_Zuc_IwmxXQmfVXRbEYH3Fw" data-element-type="heading" class="zpelement zpelem-heading "><style></style><h2
 class="zpheading zpheading-style-none zpheading-align-left zpheading-align-mobile-left zpheading-align-tablet-left " data-editor="true"><span>The Pegboard Problem<br/></span></h2></div>
<div data-element-id="elm_G43P9z_pfLdYyqQG1Yasaw" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true"><p></p><div><p>&quot;Wait,&quot; you might say. &quot;Why should we mess with things that are working just fine?&quot;</p><p><br/></p><p>Because no connected system operates in isolation, and most people are only in control of a subset of the infrastructure, not all of it. There's an illustration we use to drive this point home.</p><p><br/></p><p>Imagine, if you will, all the different pieces of your analytics infrastructure as pegs on a pegboard. Every individual aspect of your infrastructure — Tableau Server,&nbsp;<em>the Tableau Support contract</em>, the hardware it's running on, Tableau Desktop, database drivers, the data warehouse itself, authentication configuration (I'm looking at you, Snowflake) — these are the individual&nbsp;<strong>rows</strong>&nbsp;of holes on the pegboard.</p></div></div>
</div><div data-element-id="elm_iLIdRzYSBWSj4vHVKAGjSw" data-element-type="image" class="zpelement zpelem-image "><style> @media (min-width: 992px) { [data-element-id="elm_iLIdRzYSBWSj4vHVKAGjSw"] .zpimage-container figure img { width: 518px !important ; height: 587px !important ; } } [data-element-id="elm_iLIdRzYSBWSj4vHVKAGjSw"] .zpimage-container[class*='zpimage-overlay-effect-'] figure:hover figcaption , [data-element-id="elm_iLIdRzYSBWSj4vHVKAGjSw"] .zpimage-container[class*='zpimage-overlay-effect-'] figure figcaption { background:rgba(101,98,99,1) ; } [data-element-id="elm_iLIdRzYSBWSj4vHVKAGjSw"] .zpimage-container figure figcaption .zpimage-caption-content { color:rgba(101,98,99,0.41) ; font-size:12px; } </style><div data-caption-color="" data-size-tablet="" data-size-mobile="" data-align="center" data-tablet-image-separate="false" data-mobile-image-separate="false" class="zpimage-container zpimage-align-center zpimage-tablet-align-center zpimage-mobile-align-center zpimage-size-custom zpimage-tablet-fallback-fit zpimage-mobile-fallback-fit hb-lightbox " data-lightbox-options="
                type:fullscreen,
                theme:dark"><figure role="none" class="zpimage-data-ref"><span class="zpimage-anchor" role="link" tabindex="0" aria-label="Open Lightbox" style="cursor:pointer;"><picture><img class="zpimage zpimage-style-none zpimage-space-none " src="/Blog%20Images/Tableau%20Server%20Updates%20Blog/Pegboard%201.png" size="custom" data-lightbox="true"/></picture></span><figcaption class="zpimage-caption zpimage-caption-align-center"><span class="zpimage-caption-content">Each row represents an individual component of your analytics infrastructure.</span></figcaption></figure></div>
</div><div data-element-id="elm_VbtEPdjfifYBqDKALhGLPA" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true"><p></p><div><p>The versions of those components are the columns—and versions tend to increase over time, so you can also imagine the columns as &quot;when was this version released.&quot; Some components iterate slowly (like Tableau Server's supported version) and some iterate quickly (like Tableau Server itself), which shows up as peg holes being closer together on some rows and farther apart on others.</p></div><p></p></div>
</div><div data-element-id="elm_j_elkvlktPij_vVdPbsnPA" data-element-type="image" class="zpelement zpelem-image "><style> @media (min-width: 992px) { [data-element-id="elm_j_elkvlktPij_vVdPbsnPA"] .zpimage-container figure img { width: 518px !important ; height: 587.86px !important ; } } [data-element-id="elm_j_elkvlktPij_vVdPbsnPA"] .zpimage-container[class*='zpimage-overlay-effect-'] figure:hover figcaption , [data-element-id="elm_j_elkvlktPij_vVdPbsnPA"] .zpimage-container[class*='zpimage-overlay-effect-'] figure figcaption { background:#656263 ; } [data-element-id="elm_j_elkvlktPij_vVdPbsnPA"] .zpimage-container figure figcaption .zpimage-caption-content { color:rgba(101,98,99,0.41) ; font-size:12px; letter-spacing:0px; } [data-element-id="elm_j_elkvlktPij_vVdPbsnPA"].zpelem-image { margin-block-start:7px; } </style><div data-caption-color="" data-size-tablet="" data-size-mobile="" data-align="center" data-tablet-image-separate="false" data-mobile-image-separate="false" class="zpimage-container zpimage-align-center zpimage-tablet-align-center zpimage-mobile-align-center zpimage-size-custom zpimage-tablet-fallback-fit zpimage-mobile-fallback-fit hb-lightbox " data-lightbox-options="
                type:fullscreen,
                theme:dark"><figure role="none" class="zpimage-data-ref"><span class="zpimage-anchor" role="link" tabindex="0" aria-label="Open Lightbox" style="cursor:pointer;"><picture><img class="zpimage zpimage-style-none zpimage-space-none " src="/Blog%20Images/Tableau%20Server%20Updates%20Blog/Pegboard%202.png" size="custom" data-lightbox="true"/></picture></span><figcaption class="zpimage-caption zpimage-caption-align-center"><span class="zpimage-caption-content">Columns represent versions over time. Some components release more frequently than others.</span></figcaption></figure></div>
</div><div data-element-id="elm_j7KIFBw5eoC4_EBIbLc_Bw" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true"><p></p><div><p>The pegs themselves are placed in the holes for which versions are relevant for each component. Remember, you probably only control&nbsp;<em>some of these</em>&nbsp;pegs.</p><p><br/></p><p>Now, imagine rubber bands looping around the pegs that need to work together.</p><p><br/></p><p>If the versions of the components are compatible, those pegs are closer together, and the rubber band can fit around all the pegs without snapping. It might be totally slack, with room to give as things change. Or it might have a little bit of tension, but nothing the rubber band couldn't handle. As long as the tension in the rubber band isn't too much to handle, everything works.</p></div><p></p></div>
</div><div data-element-id="elm_TkhFOxZI1VrUKlOUhVU_sw" data-element-type="image" class="zpelement zpelem-image "><style> @media (min-width: 992px) { [data-element-id="elm_TkhFOxZI1VrUKlOUhVU_sw"] .zpimage-container figure img { width: 518px !important ; height: 587px !important ; } } [data-element-id="elm_TkhFOxZI1VrUKlOUhVU_sw"] .zpimage-container figure figcaption .zpimage-caption-content { color:rgba(101,98,99,0.41) ; font-size:12px; } </style><div data-caption-color="" data-size-tablet="" data-size-mobile="" data-align="center" data-tablet-image-separate="false" data-mobile-image-separate="false" class="zpimage-container zpimage-align-center zpimage-tablet-align-center zpimage-mobile-align-center zpimage-size-custom zpimage-tablet-fallback-fit zpimage-mobile-fallback-fit hb-lightbox " data-lightbox-options="
                type:fullscreen,
                theme:dark"><figure role="none" class="zpimage-data-ref"><span class="zpimage-anchor" role="link" tabindex="0" aria-label="Open Lightbox" style="cursor:pointer;"><picture><img class="zpimage zpimage-style-none zpimage-space-none " src="/Blog%20Images/Tableau%20Server%20Updates%20Blog/Pegboard%203.png" size="custom" data-lightbox="true"/></picture></span><figcaption class="zpimage-caption zpimage-caption-align-center"><span class="zpimage-caption-content">Rubber bands stretch across compatible versions. A little tension is fine.</span></figcaption></figure></div>
</div><div data-element-id="elm_J-tn81f6K_akEpRa9Mv-tA" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true"><p></p><div><p>But when one of those pegs drifts too far — especially the ones that aren't in your control — the rubber band stretches and, eventually, snaps. And if you've ever been hit by a rubber band when it snaps… it doesn't feel good.</p><p><br/></p><p>That is the situation organizations find themselves in when deferred maintenance finally catches up: a down server, no vendor support, and a team scrambling to reconstruct years of configuration decisions from memory.</p></div><p></p></div>
</div><div data-element-id="elm_NG-p2ujRb2fhmZ_u6Oz0Ig" data-element-type="image" class="zpelement zpelem-image "><style> @media (min-width: 992px) { [data-element-id="elm_NG-p2ujRb2fhmZ_u6Oz0Ig"] .zpimage-container figure img { width: 518px !important ; height: 587px !important ; } } [data-element-id="elm_NG-p2ujRb2fhmZ_u6Oz0Ig"] .zpimage-container figure figcaption .zpimage-caption-content { color:rgba(101,98,99,0.41) ; font-size:12px; } </style><div data-caption-color="" data-size-tablet="" data-size-mobile="" data-align="center" data-tablet-image-separate="false" data-mobile-image-separate="false" class="zpimage-container zpimage-align-center zpimage-tablet-align-center zpimage-mobile-align-center zpimage-size-custom zpimage-tablet-fallback-fit zpimage-mobile-fallback-fit hb-lightbox " data-lightbox-options="
                type:fullscreen,
                theme:dark"><figure role="none" class="zpimage-data-ref"><span class="zpimage-anchor" role="link" tabindex="0" aria-label="Open Lightbox" style="cursor:pointer;"><picture><img class="zpimage zpimage-style-none zpimage-space-none " src="/Blog%20Images/Tableau%20Server%20Updates%20Blog/Pegboard%204.png" size="custom" data-lightbox="true"/></picture></span><figcaption class="zpimage-caption zpimage-caption-align-center"><span class="zpimage-caption-content">When versions drift too far apart, something eventually snaps.</span></figcaption></figure></div>
</div><div data-element-id="elm_blfSLM1f9NPlqaxFbYuv9g" data-element-type="divider" class="zpelement zpelem-divider "><style type="text/css"></style><style></style><div class="zpdivider-container zpdivider-line zpdivider-align-center zpdivider-align-mobile-center zpdivider-align-tablet-center zpdivider-width100 zpdivider-line-style-solid "><div class="zpdivider-common"></div>
</div></div><div data-element-id="elm_qWqxsXREcRQsj1ogXdq8dw" data-element-type="heading" class="zpelement zpelem-heading "><style></style><h2
 class="zpheading zpheading-style-none zpheading-align-left zpheading-align-mobile-left zpheading-align-tablet-left " data-editor="true"><span>Scope and Size: A Better Way to Think About Updates<br/></span></h2></div>
<div data-element-id="elm_cSR7X-KCxltydrRlVvONKA" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true"><p></p><div><p>Not all updates are created equal. Two factors determine how much work any given update actually requires:</p><p><br/></p><h3 style="font-weight:bold;"><em>Scope</em>&nbsp;<span style="color:rgba(101, 98, 99, 0.69);">describes the types of things that change from your current version to the target version.</span></h3><ul><li style="margin-bottom:6px;">A&nbsp;<strong>patch</strong>&nbsp;release typically carries only bug fixes — small changes, narrow impact.</li><li style="margin-bottom:6px;">A&nbsp;<strong>minor</strong>&nbsp;release introduces new features, and may require a bit more: it may contain several bug fixes you need to communicate to your stakeholders, but it also contains new functionality to assess and potentially implement. Users&nbsp;<em>love</em>&nbsp;new features!</li><li style="margin-bottom:6px;">A&nbsp;<strong>major</strong>&nbsp;release with backwards-incompatible changes may require an adjustment to your infrastructure's architecture, or configuration reviews — and as a result, it probably requires more planning and preparation than the other two scopes.</li></ul><h3 style="font-weight:bold;"><em>Size</em>&nbsp;<span style="color:rgba(101, 98, 99, 0.69);">describes how many of those changes accumulate before you act.</span></h3><ul><li style="margin-bottom:6px;">If you update <span style="font-weight:bold;">monthly</span>, a given release might carry a few bug fixes and one small feature.</li><li style="margin-bottom:6px;">If you update <span style="font-weight:bold;">yearly</span>, you're handling 12 months of patches, features, potential regressions, and breaking changes.</li></ul><p>Together, scope and size reflect the amount of work required to perform the maintenance, as well as the potential amount of&nbsp;<em>risk</em>&nbsp;that work carries. A sustainable maintenance strategy keeps both as small as possible.</p></div><p></p></div>
</div><div data-element-id="elm_prXy-XCu06QOzxlXpt5uRA" data-element-type="divider" class="zpelement zpelem-divider "><style type="text/css"></style><style></style><div class="zpdivider-container zpdivider-line zpdivider-align-center zpdivider-align-mobile-center zpdivider-align-tablet-center zpdivider-width100 zpdivider-line-style-solid "><div class="zpdivider-common"></div>
</div></div><div data-element-id="elm_5JZgcgU1lS2-qwTtnrISxQ" data-element-type="heading" class="zpelement zpelem-heading "><style></style><h2
 class="zpheading zpheading-style-none zpheading-align-left zpheading-align-mobile-left zpheading-align-tablet-left " data-editor="true"><span>What Deferred Maintenance Actually Costs<br/></span></h2></div>
<div data-element-id="elm_IwMUwp4Gqqau6FtMXybMbg" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true"><p></p><div><p>When you defer updates long enough, something beyond technical debt accumulates:&nbsp;<em>organizational</em>&nbsp;debt. The team member who understood the original configuration moves on. Documentation grows stale. And when something eventually breaks — because something always eventually breaks — no one knows where to start.</p><p><br/></p><p>Consider the practical math. A customer running a 2021 version of Tableau Server who needed to reach a current 2025 release was technically dealing with one upgrade. <a href="https://public.tableau.com/app/profile/tableau.core.product.marketing/viz/ReleaseNavigator-V21/FeaturesInVersionDash" title="See for yourself: use  the dash at this link to select the Tableau Server product versions between 2021.1 and the latest version. There's a lot to consider." target="_blank" rel="">But that upgrade contained&nbsp;</a><strong><a href="https://public.tableau.com/app/profile/tableau.core.product.marketing/viz/ReleaseNavigator-V21/FeaturesInVersionDash" title="See for yourself: use  the dash at this link to select the Tableau Server product versions between 2021.1 and the latest version. There's a lot to consider." target="_blank" rel="">four</a></strong><a href="https://public.tableau.com/app/profile/tableau.core.product.marketing/viz/ReleaseNavigator-V21/FeaturesInVersionDash" title="See for yourself: use  the dash at this link to select the Tableau Server product versions between 2021.1 and the latest version. There's a lot to consider." target="_blank" rel="">&nbsp;years of accumulated changes</a>:</p><ul><li style="margin-bottom:6px;">dozens of individual patch releases rolled into one,</li><li style="margin-bottom:6px;">over 200 new or updated features,</li><li style="margin-bottom:6px;">more than 20 features deprecated,</li><li style="margin-bottom:6px;">all in all, roughly 315 items to consider (excluding bug fixes).</li></ul><p>That is a fundamentally different undertaking than a routine maintenance window.</p></div><p></p></div>
</div><div data-element-id="elm_1DiY4860nUMQITHQYUOLTQ" data-element-type="divider" class="zpelement zpelem-divider "><style type="text/css"></style><style></style><div class="zpdivider-container zpdivider-line zpdivider-align-center zpdivider-align-mobile-center zpdivider-align-tablet-center zpdivider-width100 zpdivider-line-style-solid "><div class="zpdivider-common"></div>
</div></div><div data-element-id="elm_VB6gYGRMbNgjLKfqB1rKNQ" data-element-type="heading" class="zpelement zpelem-heading "><style></style><h2
 class="zpheading zpheading-style-none zpheading-align-left zpheading-align-mobile-left zpheading-align-tablet-left " data-editor="true"><span>How Often Should You Update?<br/></span></h2></div>
<div data-element-id="elm_J-gR_OTNEkLtpRpKQSbb7Q" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true"><p><span>The answer depends on your risk tolerance, your team's capacity, and how critical Tableau is to your organization — but the options break down clearly.</span></p></div>
</div><div data-element-id="elm_utJ-n1C7polAzjy8NgqVSg" data-element-type="row" class="zprow zprow-container zpalign-items-flex-start zpjustify-content-flex-start zpdefault-section zpdefault-section-bg " data-equal-column="false"><style type="text/css"></style><div data-element-id="elm_8_A02DBc29YLC--M6uNs5Q" data-element-type="column" class="zpelem-col zpcol-12 zpcol-md-6 zpcol-sm-12 zpalign-self- zpdefault-section zpdefault-section-bg "><style type="text/css"></style><div data-element-id="elm_Zj0E7Kq8pAoZQ-1EC69tBg" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true"><p></p><div><ul><li style="margin-bottom:6px;"><strong>Monthly</strong>&nbsp;is the lowest-risk posture.<ul><li style="margin-bottom:6px;">Each release carries the least accumulated change.</li><li style="margin-bottom:6px;">Scope is narrow, size is minimal, and updates become routine enough that they stop feeling like events.</li><li style="margin-bottom:6px;">If something&nbsp;<em>does</em>&nbsp;go wrong, the workarounds are inconvenient, but not show-stopping.</li><li style="margin-bottom:6px;">This is the option that makes Tableau Server maintenance boring — and boring maintenance is exactly what you want.</li></ul></li><li style="margin-bottom:6px;"><strong>Quarterly</strong>&nbsp;is manageable.<ul><li style="margin-bottom:6px;">Contains a larger update window and will contain a few months of patches.</li><li style="margin-bottom:6px;">You will typically stay within the same major version, avoiding the impact of several backwards-incompatible changes at once.</li><li style="margin-bottom:6px;">You may have more workarounds to manage, depending on the scope and size of the changes between versions.</li></ul></li><li style="margin-bottom:6px;"><strong>Semi-annually</strong>&nbsp;is better than nothing.<ul><li style="margin-bottom:6px;">If you're not changing major versions during one maintenance window, you will be during the next one.</li><li style="margin-bottom:6px;">The scope expands accordingly: what was a recurring maintenance task starts to look more like a recurring project.</li></ul></li><li style="margin-bottom:6px;"><strong>Ad hoc or annual</strong>&nbsp;updates lead to more work, at best.<ul><li style="margin-bottom:6px;">These often require project planning, stakeholder communication, thorough testing cycles, and meaningful downtime risk.</li><li style="margin-bottom:6px;">The gap between versions is no longer a maintenance consideration — it is the project itself.</li><li style="margin-bottom:6px;">If you approach these tasks as if they are routine maintenance instead of projects, you may only learn about the risks after they've materialized. By then, you've committed to dealing with the impacts, even though they can't be quantified until everything is back to normal.</li></ul></li></ul></div><p></p></div>
</div></div><div data-element-id="elm_PgtqL9DzyjOMP-MXTayR-Q" data-element-type="column" class="zpelem-col zpcol-12 zpcol-md-6 zpcol-sm-12 zpalign-self- zpdefault-section zpdefault-section-bg "><style type="text/css"></style><div data-element-id="elm_BSJOLa_B9F8k6Wad3eVmxw" data-element-type="image" class="zpelement zpelem-image "><style> @media (min-width: 992px) { [data-element-id="elm_BSJOLa_B9F8k6Wad3eVmxw"] .zpimage-container figure img { width: 500px !important ; height: 511.37px !important ; } } [data-element-id="elm_BSJOLa_B9F8k6Wad3eVmxw"] .zpimage-container figure figcaption .zpimage-caption-content { color:rgba(101,98,99,0.41) ; font-size:12px; } [data-element-id="elm_BSJOLa_B9F8k6Wad3eVmxw"].zpelem-image { margin-block-start:194px; } </style><div data-caption-color="" data-size-tablet="" data-size-mobile="" data-align="center" data-tablet-image-separate="false" data-mobile-image-separate="false" class="zpimage-container zpimage-align-center zpimage-tablet-align-center zpimage-mobile-align-center zpimage-size-custom zpimage-tablet-fallback-fit zpimage-mobile-fallback-fit hb-lightbox " data-lightbox-options="
                type:fullscreen,
                theme:dark"><figure role="none" class="zpimage-data-ref"><span class="zpimage-anchor" role="link" tabindex="0" aria-label="Open Lightbox" style="cursor:pointer;"><picture><img class="zpimage zpimage-style-none zpimage-space-none " src="/Blog%20Images/Tableau%20Server%20Updates%20Blog/Size-and-Scope.png" size="custom" data-lightbox="true"/></picture></span><figcaption class="zpimage-caption zpimage-caption-align-center"><span class="zpimage-caption-content">Update cadence shapes how much work—and risk—each maintenance window carries.</span></figcaption></figure></div>
</div></div></div><div data-element-id="elm_mbJzIErsMHLqqKpd6NxpvQ" data-element-type="divider" class="zpelement zpelem-divider "><style type="text/css"></style><style></style><div class="zpdivider-container zpdivider-line zpdivider-align-center zpdivider-align-mobile-center zpdivider-align-tablet-center zpdivider-width100 zpdivider-line-style-solid "><div class="zpdivider-common"></div>
</div></div><div data-element-id="elm_MVMEIfpfhfr0jwlQQ9qj9w" data-element-type="heading" class="zpelement zpelem-heading "><style></style><h2
 class="zpheading zpheading-style-none zpheading-align-left zpheading-align-mobile-left zpheading-align-tablet-left " data-editor="true"><span>Making Updates Part of the Routine<br/></span></h2></div>
<div data-element-id="elm_pZxaqUDBCPTbHqCY0IkNXw" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true"><p></p><div><p>The organizations that handle Tableau Server best are not necessarily the ones with the most sophisticated environments.</p><p><br/></p><div><strong style="color:rgb(106, 90, 205);">They are the ones that treat updates as routine rather than optional.</strong></div><div><strong><br/></strong></div><p>They have a maintenance schedule. They stick to it. And if something goes wrong, it doesn't take a ton of effort to remediate, because the size and scope of the maintenance was appropriately constrained.</p></div><p></p></div>
</div><div data-element-id="elm_TqYW2ln9lRTmGerSyh8O9Q" data-element-type="spacer" class="zpelement zpelem-spacer "><style> div[data-element-id="elm_TqYW2ln9lRTmGerSyh8O9Q"] div.zpspacer { height:30px; } @media (max-width: 768px) { div[data-element-id="elm_TqYW2ln9lRTmGerSyh8O9Q"] div.zpspacer { height:calc(30px / 3); } } </style><div class="zpspacer " data-height="30"></div>
</div><div data-element-id="elm_-r0Z61f1NO9SJW7cCLB73w" data-element-type="box" class="zpelem-box zpelement zpbox-container zpdefault-section zpdefault-section-bg "><style type="text/css"> [data-element-id="elm_-r0Z61f1NO9SJW7cCLB73w"].zpelem-box{ background-color:rgba(237,28,36,0.12); background-image:unset; border-style:solid; border-color:#ED1C24 !important; border-width:1px; border-radius:12px; padding:15px; margin:0px; } </style><div data-element-id="elm_kNyEGPoE70m0MxKMFs9j4Q" data-element-type="heading" class="zpelement zpelem-heading "><style></style><h2
 class="zpheading zpheading-style-none zpheading-align-left zpheading-align-mobile-left zpheading-align-tablet-left " data-editor="true"><span>Need Help Getting Current?<br/></span></h2></div>
<div data-element-id="elm_RPs6UpXIixjZIpDoReFNGA" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true"><p></p><div><p>If your Tableau Server is behind on updates — or if you are not sure where it stands — High Performance Technologies can help you assess the gap and build a path forward. Whether that means a one-time upgrade to get you current, or an ongoing managed services arrangement to keep you there, we can scope what makes sense for your environment.</p><p><br/></p><p>Learn more at&nbsp;<a href="https://www.highperformance.tech/services">highperformance.tech/services</a>&nbsp;or reach out directly at&nbsp;<a href="https://www.highperformance.tech/contact-us">highperformance.tech/contact-us</a>.</p></div><p></p></div>
</div></div><div data-element-id="elm_5581ggqZTayueILbIsBBxQ" data-element-type="button" class="zpelement zpelem-button "><style></style><div class="zpbutton-container zpbutton-align-center zpbutton-align-mobile-center zpbutton-align-tablet-center"><style type="text/css"></style><a class="zpbutton-wrapper zpbutton zpbutton-type-primary zpbutton-size-md " href="javascript:;" target="_blank"><span class="zpbutton-content">Get Started Now</span></a></div>
</div></div></div></div></div></div> ]]></content:encoded><pubDate>Tue, 21 Apr 2026 11:48:18 -0500</pubDate></item><item><title><![CDATA[Introducing ViewAs: User Impersonation for Tableau Server]]></title><link>https://www.highperformance.tech/blogs/post/tableau-server-impersonation</link><description><![CDATA[<img align="left" hspace="5" src="https://www.highperformance.tech/Blog Images/ViewAs_Set Up Screen.png"/>ViewAs is a small application that lets Tableau Server administrators view the server exactly as another user sees it — through the actual UI, not just the API.]]></description><content:encoded><![CDATA[<div class="zpcontent-container blogpost-container "><div data-element-id="elm_1AgEe0FYRtqE3OO6PX4yhQ" data-element-type="section" class="zpsection "><style type="text/css"></style><div class="zpcontainer-fluid zpcontainer"><div data-element-id="elm_Ybv4JJFmQ8KwkPKlwpG6ZQ" data-element-type="row" class="zprow zprow-container zpalign-items- zpjustify-content- " data-equal-column=""><style type="text/css"></style><div data-element-id="elm_ncUi9D0dQdKRJ-gEZM63eA" data-element-type="column" class="zpelem-col zpcol-12 zpcol-md-12 zpcol-sm-12 zpalign-self- "><style type="text/css"></style><div data-element-id="elm_bzrPvprYuxAuUc912sQptA" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true"><p></p><div><p>I've had the pleasure of working with Tableau Server administrators for over a decade and a half now, and one question that comes up somewhat frequently is some variation of: &quot;A user says they can't see this dashboard, but from my end it looks fine. How do I see what <i>they</i> see?&quot;</p><p><br/></p><p>In most cases the answer, until now, has been: outside of Tableau Desktop, you can't. Not easily, anyway.</p></div><p></p></div>
</div><div data-element-id="elm_F1s8G9CNQZUGfmIcmhlP5w" data-element-type="heading" class="zpelement zpelem-heading "><style></style><h2
 class="zpheading zpheading-style-none zpheading-align-left zpheading-align-mobile-left zpheading-align-tablet-left " data-editor="true"><span>The Typical Approaches</span></h2></div>
<div data-element-id="elm_NKPYv2G8p8sXECjDoKB6Wg" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true"><p></p><div><p>How you handle this depends on your environment. If you're embedding Tableau in a custom application, your wrapper app can impersonate the user and render the viz as them. This works well when it's available, but it requires that you have embedding set up in the first place and that your portal actually implements impersonation. If you're not in that situation, it doesn't help you.</p><p><br/></p><p>For row-level security issues, the workbook author or other Creators could troubleshoot in Tableau Desktop. This is effective, but now you're potentially pulling someone else into the investigation and waiting on their availability.</p><p><br/></p><p>You can also check effective permissions in the Tableau Server content permissions dialog. It's easy enough to access, but interpreting the results takes some practice — and it only tells you about permissions, not what the user actually experiences when they navigate the server.</p><p><br/></p><p>My go-to approach has been scheduling a Zoom call for a screenshare. I like this because you can troubleshoot user behavior and technical issues at the same time — sometimes the problem isn't permissions at all, it's just that the user doesn't know where to click. But scheduling a call isn't always convenient, and sometimes you just need a quick answer without the coordination overhead.</p></div><p></p></div>
</div><div data-element-id="elm_0POoyX17KPu8MgHuRCpbXQ" data-element-type="heading" class="zpelement zpelem-heading "><style></style><h2
 class="zpheading zpheading-style-none zpheading-align-left zpheading-align-mobile-left zpheading-align-tablet-left " data-editor="true"><span>Introducing: ViewAs</span></h2></div>
<div data-element-id="elm_DwgVnnhupXRLnoazXbhgwg" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true"><p>ViewAs is a small application that lets Tableau Server administrators <span style="font-weight:bold;">view the server&nbsp;</span><span style="font-weight:bold;">exactly as another user sees it</span> — through the actual UI, not just the API.</p><p><br/></p><p>The workflow is simple:&nbsp;</p><ol><ol><li>You authenticate as yourself</li><li>Select the user you want to impersonate from a searchable list</li><li>Launch a session</li></ol></ol><p><span style="font-family:-apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Roboto, &quot;Helvetica Neue&quot;, Arial, sans-serif;"><br/></span></p><p><span style="font-family:-apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Roboto, &quot;Helvetica Neue&quot;, Arial, sans-serif;">From there, you see what they see. Their navigation, their row-level security filtering, their dashboard behavior, their effective permissions at every level. When someone reports they can't see the Sales Dashboard, you can verify in thirty seconds whether the issue is permissions, RLS, or something the user is doing on their end.</span></p><p><br/></p><p>This is also useful for validating RLS across tenants, testing how dashboards appear for different user roles, verifying that new users have appropriate access on their first day, and documenting access controls for audit purposes. Automation is still the right answer for anything you need to do repeatedly at scale — but manual validation lets you iterate quickly when you're building something new, and it gives you peace of mind that what you've built actually works the way you expect. Both have their place.<br/></p></div>
</div><div data-element-id="elm_u_c8G_T7Lwpu9sXjA1z7IA" data-element-type="row" class="zprow zprow-container zpalign-items-flex-start zpjustify-content-flex-start zpdefault-section zpdefault-section-bg " data-equal-column="false"><style type="text/css"></style><div data-element-id="elm_IHyEHg0CtstqN-34k1GhRg" data-element-type="column" class="zpelem-col zpcol-12 zpcol-md-6 zpcol-sm-12 zpalign-self- zpdefault-section zpdefault-section-bg "><style type="text/css"></style><div data-element-id="elm_ZuO6IRMNNNnMv9Dud5GsCA" data-element-type="image" class="zpelement zpelem-image "><style> @media (min-width: 992px) { [data-element-id="elm_ZuO6IRMNNNnMv9Dud5GsCA"] .zpimage-container figure img { width: 472.52px !important ; height: 312px !important ; } } [data-element-id="elm_ZuO6IRMNNNnMv9Dud5GsCA"] .zpimage-container[class*='zpimage-overlay-effect-'] figure:hover figcaption , [data-element-id="elm_ZuO6IRMNNNnMv9Dud5GsCA"] .zpimage-container[class*='zpimage-overlay-effect-'] figure figcaption { background:#013A51 ; } </style><div data-caption-color="" data-size-tablet="" data-size-mobile="" data-align="center" data-tablet-image-separate="false" data-mobile-image-separate="false" class="zpimage-container zpimage-align-center zpimage-tablet-align-center zpimage-mobile-align-center zpimage-size-custom zpimage-tablet-fallback-fit zpimage-mobile-fallback-fit zpimage-overlay zpimage-overlay-effect-static-bottom hb-lightbox " data-lightbox-options="
                type:fullscreen,
                theme:dark"><figure role="none" class="zpimage-data-ref"><span class="zpimage-anchor" role="link" tabindex="0" aria-label="Open Lightbox" style="cursor:pointer;"><picture><img class="zpimage zpimage-style-none zpimage-space-none " src="/Blog%20Images/ViewAs_Select%20User%20to%20Impersonate.png" size="custom" data-lightbox="true"/></picture></span><figcaption class="zpimage-caption zpimage-caption-align-center"><span class="zpimage-caption-content">Logged in as myself (ironman) and prompted to impersonate a user.</span></figcaption></figure></div>
</div></div><div data-element-id="elm_Gu_pYFDU0pMYzZHgxlXJRQ" data-element-type="column" class="zpelem-col zpcol-12 zpcol-md-6 zpcol-sm-12 zpalign-self- zpdefault-section zpdefault-section-bg "><style type="text/css"></style><div data-element-id="elm_gl0V4dJVUWdgWJZxUvA43Q" data-element-type="image" class="zpelement zpelem-image "><style> @media (min-width: 992px) { [data-element-id="elm_gl0V4dJVUWdgWJZxUvA43Q"] .zpimage-container figure img { width: 350px ; height: 202.93px ; } } [data-element-id="elm_gl0V4dJVUWdgWJZxUvA43Q"] .zpimage-container[class*='zpimage-overlay-effect-'] figure:hover figcaption , [data-element-id="elm_gl0V4dJVUWdgWJZxUvA43Q"] .zpimage-container[class*='zpimage-overlay-effect-'] figure figcaption { background:#013A51 ; } </style><div data-caption-color="" data-size-tablet="" data-size-mobile="" data-align="center" data-tablet-image-separate="false" data-mobile-image-separate="false" class="zpimage-container zpimage-align-center zpimage-tablet-align-center zpimage-mobile-align-center zpimage-size-fit zpimage-tablet-fallback-fit zpimage-mobile-fallback-fit zpimage-overlay zpimage-overlay-effect-static-bottom hb-lightbox " data-lightbox-options="
                type:fullscreen,
                theme:dark"><figure role="none" class="zpimage-data-ref"><span class="zpimage-anchor" role="link" tabindex="0" aria-label="Open Lightbox" style="cursor:pointer;"><picture><img class="zpimage zpimage-style-none zpimage-space-none " src="/Blog%20Images/ViewAs_Impersonating%20User.png" size="fit" data-lightbox="true"/></picture></span><figcaption class="zpimage-caption zpimage-caption-align-center"><span class="zpimage-caption-content">Impersonating a user (thehulk) on my server. Now I see what he sees — exactly as he sees it.</span></figcaption></figure></div>
</div></div></div><div data-element-id="elm_HZkf9WkiLM5omhxp2pPKWQ" data-element-type="heading" class="zpelement zpelem-heading "><style></style><h2
 class="zpheading zpheading-style-none zpheading-align-left zpheading-align-mobile-left zpheading-align-tablet-left " data-editor="true"><span>Tableau Cloud</span></h2></div>
<div data-element-id="elm_P_7PmuxXQ82JKY5jzbWdtA" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true"><p></p><div><p>This version of ViewAs is for Tableau Server on-premises deployments. Tableau Cloud has tighter restrictions around impersonation — API impersonation requires Connected Apps with JWT authentication, which is significant overhead just to debug a permission issue. I'm exploring options for a Cloud version, but the architectural constraints are real. If you're a Cloud customer running into these friction points, I'd like to hear about your specific scenarios.</p></div><p></p></div>
</div><div data-element-id="elm__B9saYcPXaepPlU-fo7TSg" data-element-type="heading" class="zpelement zpelem-heading "><style></style><h2
 class="zpheading zpheading-style-none zpheading-align-left zpheading-align-mobile-left zpheading-align-tablet-left " data-editor="true"><span>Getting Started</span></h2></div>
<div data-element-id="elm_x05Ho2_fkVOZax-mz9VOkQ" data-element-type="row" class="zprow zprow-container zpalign-items-flex-start zpjustify-content-flex-start zpdefault-section zpdefault-section-bg " data-equal-column="false"><style type="text/css"></style><div data-element-id="elm_HuuffqGnHz5rP7BByUlzew" data-element-type="column" class="zpelem-col zpcol-12 zpcol-md-6 zpcol-sm-12 zpalign-self- zpdefault-section zpdefault-section-bg "><style type="text/css"></style><div data-element-id="elm_0PXNyIic5v4lAA30PpeDtw" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true"><p></p><div><p><strong>ViewAs is available now for free at&nbsp;</strong><a href="https://viewas.hpt.tools/"><strong>viewas.hpt.tools</strong></a><strong>&nbsp;</strong>(email required for download).&nbsp;</p><p><br/></p><p>It works on Mac, Windows, and Linux, and takes less than a minute to set up. If you're using Tableau Server authentication, you can sign in with your credentials directly. Otherwise, if you are using SSO or another method, you'll need a Personal Access Token.</p><p><br/></p><p>Questions? Reach out at&nbsp;<a href="mailto:hello@highperformance.tech">hello@highperformance.tech</a>.</p></div><p></p></div>
</div></div><div data-element-id="elm_erPXGUPy528RD81gOsFisw" data-element-type="column" class="zpelem-col zpcol-12 zpcol-md-6 zpcol-sm-12 zpalign-self- zpdefault-section zpdefault-section-bg "><style type="text/css"></style><div data-element-id="elm_-VIZqd5-wRj6dMY-jTEciA" data-element-type="image" class="zpelement zpelem-image "><style> @media (min-width: 992px) { [data-element-id="elm_-VIZqd5-wRj6dMY-jTEciA"] .zpimage-container figure img { width: 350px ; height: 202.34px ; } } </style><div data-caption-color="" data-size-tablet="" data-size-mobile="" data-align="center" data-tablet-image-separate="false" data-mobile-image-separate="false" class="zpimage-container zpimage-align-center zpimage-tablet-align-center zpimage-mobile-align-center zpimage-size-fit zpimage-tablet-fallback-fit zpimage-mobile-fallback-fit hb-lightbox " data-lightbox-options="
                type:fullscreen,
                theme:dark"><figure role="none" class="zpimage-data-ref"><span class="zpimage-anchor" role="link" tabindex="0" aria-label="Open Lightbox" style="cursor:pointer;"><picture><img class="zpimage zpimage-style-none zpimage-space-none " src="/Blog%20Images/ViewAs_Set%20Up%20Screen.png" size="fit" data-lightbox="true"/></picture></span></figure></div>
</div></div></div></div></div></div></div></div> ]]></content:encoded><pubDate>Mon, 16 Feb 2026 11:57:00 -0600</pubDate></item><item><title><![CDATA[Tableau Server Log Reference]]></title><link>https://www.highperformance.tech/blogs/post/tableau-server-logs</link><description><![CDATA[<img align="left" hspace="5" src="https://www.highperformance.tech/files/img/abstract code.png"/>Comprehensive Tableau Server log troubleshooting reference. Maps symptoms like login failures, slow dashboards, and extract errors to specific log folders across 20+ processes. Includes Linux/Windows paths and quick reference tables. Features open-source ts-olly tool for log ingestion.]]></description><content:encoded><![CDATA[<div class="zpcontent-container blogpost-container "><div data-element-id="elm_3TnYPRdTTgyIheBF-j4l2A" data-element-type="section" class="zpsection "><style type="text/css"></style><div class="zpcontainer-fluid zpcontainer"><div data-element-id="elm_Kbo85UDkRMWk9GLL6Ne3hA" data-element-type="row" class="zprow zprow-container zpalign-items- zpjustify-content- " data-equal-column=""><style type="text/css"></style><div data-element-id="elm_ifiuZmxaRqOQqi2G4AVzHQ" data-element-type="column" class="zpelem-col zpcol-12 zpcol-md-12 zpcol-sm-12 zpalign-self- "><style type="text/css"></style><div data-element-id="elm_n7p5yRRzRgKse2BhTGojrw" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-center zptext-align-mobile-center zptext-align-tablet-center " data-editor="true"><p></p><div><div><p style="text-align:left;"><span style="font-size:16px;">Tableau Server writes logs across dozens of folders in different formats. It can be a bit tough to find info on which logs you need, given the context you’re trying to find. Tableau's documentation covers it, but you have to cross-reference multiple pages to find what you need.</span></p><span style="font-size:16px;"></span><p style="text-align:left;"><span style="font-size:16px;">&nbsp;</span></p><span style="font-size:16px;"></span><p style="text-align:left;"><span style="font-size:16px;">This quick reference should help!</span></p></div>
</div><p></p></div></div><div data-element-id="elm_uPnKsjMIPUbM1hMYZIR1Hg" data-element-type="codeSnippet" class="zpelement zpelem-codesnippet "><div class="zpsnippet-container"><!-- HPT Bookmark Callout - Info Purple --><div class="hpt-bookmark-inline" style="font-family:Arial, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Roboto, sans-serif;background:rgb(240, 239, 255);border-radius:10px;padding:24px 28px;margin:32px 0;display:flex;align-items:center;justify-content:center;gap:12px;position:relative;overflow:hidden;border:2px solid rgb(106, 90, 205);text-align:center;box-shadow:0 3px 10px rgba(106, 90, 205, 0.15);"><div style="content:&quot;&quot;;position:absolute;left:0;top:0;bottom:0;width:5px;background:rgb(106, 90, 205);"></div>
<div style="color:rgb(106, 90, 205);flex-shrink:0;"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:22px;height:22px;"><path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"></path></svg></div>
<p style="font-size:16px;font-weight:600;color:rgb(61, 59, 60);margin:0;"><strong style="color:rgb(106, 90, 205);">Don't forget to bookmark this page</strong> so you can easily find it when you need it. </p></div>
</div></div><div data-element-id="elm_xZeIGbwyVIpg5cDHGvZUzw" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true"><p></p><div><div><p><span style="font-size:16px;">Log base path on Linux: <b>/var/opt/tableau/tableau_server/data/tabsvc/logs/</b></span></p><span style="font-size:16px;"></span><p><span style="font-size:16px;">Log base path on Windows: <b>C:\ProgramData\Tableau\Tableau Server\data\tabsvc\logs\</b></span></p><span style="font-size:16px;"></span><p><b><span style="font-size:16px;">&nbsp;</span></b></p><span style="font-size:16px;"></span><p><b><span style="font-size:16px;">Note: </span></b><span style="font-size:16px;">if you installed to a non-default location, look there for the relevant <b>data → tabsvc </b>directory, and you’ll find the logs directory inside.</span></p></div>
</div><p></p></div></div><div data-element-id="elm_GJlzS0SCaDm10TBYo8zRtw" data-element-type="codeSnippet" class="zpelement zpelem-codesnippet "><div class="zpsnippet-container"><!-- COMPLETE TABLEAU SERVER LOGS REFERENCE TABLES --><style> /* HPT Table Styles - Embedded */ .hpt-table-container { margin: 32px 0; border-radius: 10px; overflow: hidden; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); border: 1px solid #e8e8e8; } .hpt-table { width: 100%; border-collapse: collapse; font-family: Arial, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #fff; margin: 0; } .hpt-table th { background: #3D3B3C !important; /* HPT Dark Gray from brand guidelines */ color: #fff !important; padding: 16px 20px !important; font-weight: 600 !important; font-size: 14px !important; text-align: left !important; border: none !important; letter-spacing: 0.3px !important; } .hpt-table th:first-child { border-top-left-radius: 10px; } .hpt-table th:last-child { border-top-right-radius: 10px; } .hpt-table td { padding: 16px 20px; border-bottom: 1px solid #f0f0f0; color: #656263; font-size: 14px; line-height: 1.6; vertical-align: top; } .hpt-table tbody tr:hover { background: #fafafa; transition: background 0.2s ease; } .hpt-table tbody tr:last-child td { border-bottom: none; } .hpt-table td strong, .hpt-table td b { color: #3D3B3C; font-weight: 600; } .hpt-table code { background: #f5f5f5; padding: 2px 6px; border-radius: 3px; font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-size: 12px; color: #656263; } /* Header Styles */ .hpt-section h2 { font-family: Arial, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; color: #656263; font-size: 28px; font-weight: 600; margin: 48px 0 24px 0; } .hpt-section h3 { font-family: Arial, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; color: #3D3B3C; font-size: 22px; font-weight: 600; margin: 36px 0 16px 0; } @media (max-width: 768px) { .hpt-table-container { overflow-x: auto; -webkit-overflow-scrolling: touch; } .hpt-table { min-width: 600px; } .hpt-table th, .hpt-table td { padding: 12px 16px; font-size: 13px; } .hpt-section h2 { font-size: 24px; } .hpt-section h3 { font-size: 20px; } } </style><div class="hpt-section"><!-- QUICK TROUBLESHOOTING SECTION --><h2>Quick Troubleshooting</h2><div class="hpt-table-container"><table class="hpt-table"><thead><tr><th>Symptom</th><th>Logs to Check</th></tr></thead><tbody><tr><td><strong>Users can't log in</strong></td><td><code>authnservice/</code>, <code>vizportal/</code>, <code>httpd/</code></td></tr><tr><td><strong>Dashboard won't load</strong></td><td><code>vizqlserver/</code>, <code>dataserver/</code>, <code>hyper/</code>, <code>httpd/</code></td></tr><tr><td><strong>Extract refresh failed</strong></td><td><code>backgrounder/</code>, <code>hyper/</code>, <code>filestore/</code></td></tr><tr><td><strong>Search not working</strong></td><td><code>indexandsearchserver/</code>, <code>noninteractive/</code></td></tr><tr><td><strong>Cluster node unhealthy</strong></td><td><code>tabadmincontroller/</code>, <code>clustercontroller/</code>, <code>tabadminagent/</code></td></tr><tr><td><strong>TSM commands failing</strong></td><td><code>tabadmincontroller/</code>, <code>tabadminagent/</code>, <code>appzookeeper/</code></td></tr></tbody></table></div>
<!-- PROCESS LOG REFERENCE SECTION --><h2>Process Log Reference</h2><!-- CORE REQUEST PROCESSING --><h3>Core Request Processing</h3><div class="hpt-table-container"><table class="hpt-table"><thead><tr><th>Process</th><th>Log Folder</th><th>Check When</th></tr></thead><tbody><tr><td><strong>Gateway</strong></td><td><code>httpd/</code></td><td>User-facing errors, SSL problems</td></tr><tr><td><strong>Application Server</strong></td><td><code>vizportal/</code></td><td>Web UI errors, REST API failures, login page issues</td></tr><tr><td><strong>VizQL Server</strong></td><td><code>vizqlserver/</code></td><td>Viz rendering failures, session timeouts, memory issues</td></tr><tr><td><strong>API Gateway</strong></td><td><code>apigateway/</code></td><td>REST API v3.x errors (check out http first)</td></tr><tr><td><strong>Data Server</strong></td><td><code>dataserver/</code></td><td>Published data source issues</td></tr></tbody></table></div>
<!-- DATA & QUERY PROCESSING --><h3>Data &amp; Query Processing</h3><div class="hpt-table-container"><table class="hpt-table"><thead><tr><th>Process</th><th>Log Folder</th><th>Check When</th></tr></thead><tbody><tr><td><strong>Data Engine (Hyper)</strong></td><td><code>hyper/</code></td><td>Extract query failures, Hyper crashes, memory exhaustion</td></tr><tr><td><strong>File Store</strong></td><td><code>filestore/</code></td><td>Extract replication failures, missing extracts, sync issues</td></tr><tr><td><strong>Cache Server</strong></td><td><code>cacheserver/</code></td><td>Query cache issues, Redis failures, session data loss</td></tr></tbody></table></div>
<!-- BACKGROUND JOBS --><h3>Background Jobs</h3><div class="hpt-table-container"><table class="hpt-table"><thead><tr><th>Process</th><th>Log Folder</th><th>Check When</th></tr></thead><tbody><tr><td><strong>Backgrounder</strong></td><td><code>backgrounder/</code></td><td>Extract refresh failures, subscription failures, job queue backlog</td></tr><tr><td><strong>Flow Processor</strong></td><td><code>flowprocessor/</code></td><td>Prep flow execution failures, flow step errors</td></tr></tbody></table></div>
<!-- AUTHENTICATION --><h3>Authentication</h3><div class="hpt-table-container"><table class="hpt-table"><thead><tr><th>Process</th><th>Log Folder</th><th>Check When</th></tr></thead><tbody><tr><td><strong>Authentication Service</strong></td><td><code>authnservice/</code></td><td>SAML/OIDC failures, SSO redirect loops, token validation (also check the vizportal logs)</td></tr><tr><td><strong>Identity Service</strong></td><td><code>identityservice/</code></td><td>SCIM provisioning, identity migration, user/group sync</td></tr></tbody></table></div>
<!-- CLUSTER & ADMINISTRATION --><h3>Cluster &amp; Administration</h3><div class="hpt-table-container"><table class="hpt-table"><thead><tr><th>Process</th><th>Log Folder</th><th>Check When</th></tr></thead><tbody><tr><td><strong>Coordination Service</strong></td><td><code>appzookeeper/</code></td><td>Cluster quorum, split-brain, leader election failures</td></tr><tr><td><strong>Cluster Controller</strong></td><td><code>clustercontroller/</code></td><td>Repository failover, node health, process state transitions</td></tr><tr><td><strong>Repository</strong></td><td><code>pgsql/</code></td><td>Database connections, replication lag, failover problems</td></tr><tr><td><strong>TSM Controller</strong></td><td><code>tabadmincontroller/</code></td><td>TSM command failures, config apply errors, backup/restore, service startup errors</td></tr><tr><td><strong>TSM Agent</strong></td><td><code>tabadminagent/</code></td><td>Service startup/shutdown failures on specific nodes (also check the individual service's logs)</td></tr></tbody></table></div>
<!-- SEARCH & INDEXING --><h3>Search &amp; Indexing</h3><div class="hpt-table-container"><table class="hpt-table"><thead><tr><th>Process</th><th>Log Folder</th><th>Check When</th></tr></thead><tbody><tr><td><strong>Index and Search</strong></td><td><code>indexandsearchserver/</code></td><td>Search not returning results, indexing delays</td></tr><tr><td><strong>Non-Interactive Container</strong></td><td><code>noninteractive/</code></td><td>Background indexing failures, metadata operations</td></tr></tbody></table></div>
</div></div></div><div data-element-id="elm_fHzX951rIadUpwYRSOJ3MQ" data-element-type="heading" class="zpelement zpelem-heading "><style></style><h2
 class="zpheading zpheading-style-none zpheading-align-left zpheading-align-mobile-left zpheading-align-tablet-left " data-editor="true"><span style="font-family:Arial, sans-serif;"><strong>Ingesting Logs with ts-olly</strong></span></h2></div>
<div data-element-id="elm_zUHt6wC-Q2B9g6cy-fyYFA" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true"><p><span style="font-size:16px;">If you want these logs in Splunk, Elasticsearch, Loki, or similar, you'll need to parse multiple formats — log4j, log4j2, httpd, JSON — and keep up as Tableau adds or changes processes over time.</span></p><p></p><div><div><p><span style="font-size:16px;">&nbsp;</span></p><p><b><span style="font-size:16px;">ts-olly</span></b><span style="font-size:16px;"> handles this. It reads Tableau's config files to learn the log formats automatically, detects new processes and their configs as they appear, and outputs unified JSON. You don't have to build or maintain parsers.</span></p></div></div></div>
</div><div data-element-id="elm_DaW-kjQJsZQMOtBhW_TtDw" data-element-type="text" class="zpelement zpelem-text "><style> [data-element-id="elm_DaW-kjQJsZQMOtBhW_TtDw"].zpelem-text { background-color:rgba(0,0,0,1); background-image:unset; color:rgba(255,255,255,0.97) ; padding:12px; margin:16px; } [data-element-id="elm_DaW-kjQJsZQMOtBhW_TtDw"].zpelem-text :is(h1,h2,h3,h4,h5,h6){ color:rgba(255,255,255,0.97) ; } </style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true"><p></p><div><p><span style="font-family:&quot;Courier New&quot;, monospace;">ts-olly -node node1 \</span></p><p><span style="font-family:&quot;Courier New&quot;, monospace;">&nbsp; -logsdir /var/opt/tableau/tableau_server/data/tabsvc/logs \</span></p><p><span style="font-family:&quot;Courier New&quot;, monospace;">&nbsp; -configdir /var/opt/tableau/tableau_server/data/tabsvc/config</span></p></div><p></p></div>
</div><div data-element-id="elm_l9tQf2WbqJo3_qMSPmAaUA" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true"><p></p><div><p><span style="font-size:16px;">MIT-licensed: <a href="https://github.com/highperformance-tech/ts-olly">github.com/highperformance-tech/ts-olly</a></span></p></div><p></p></div>
</div><div data-element-id="elm_JVC3J1S-IDgVPOqTTaV8uQ" data-element-type="codeSnippet" class="zpelement zpelem-codesnippet "><div class="zpsnippet-container"><div style="margin:48px 0;padding:24px;background:rgb(232, 231, 231);border-radius:8px;font-family:Arial, sans-serif;"><p style="font-size:16px;color:rgb(101, 98, 99);margin-bottom:16px;"> Need help interpreting what you're seeing in these logs? We've helped dozens of organizations troubleshoot complex Tableau Server issues — from memory leaks that only show up under load to intermittent authentication failures that disappear when you're looking for them. </p><p style="font-size:16px;color:rgb(101, 98, 99);margin:0;"> If you're dealing with a tricky performance issue or need someone to review your server health, feel free to <a href="mailto:hello@highperformance.tech" style="color:rgb(237, 28, 36);text-decoration:none;">reach out</a> — happy to point you in the right direction. </p></div>
</div></div></div></div></div></div></div> ]]></content:encoded><pubDate>Tue, 03 Feb 2026 13:31:00 -0600</pubDate></item><item><title><![CDATA[Bring Tableau and Agentic Analytics Together with the VDS API]]></title><link>https://www.highperformance.tech/blogs/post/bring-tableau-and-agentic-analytics-together-with-the-vds-api</link><description><![CDATA[We talk with a lot of companies that want AI in their analytics practice, but aren't totally sure what that looks like. Many of them want it enough ]]></description><content:encoded><![CDATA[<div class="zpcontent-container blogpost-container "><div data-element-id="elm_RMAPxqigS92nnBj0B8_cOg" data-element-type="section" class="zpsection "><style type="text/css"></style><div class="zpcontainer-fluid zpcontainer"><div data-element-id="elm_V1oLFlDNSM-to5AuuosMdQ" data-element-type="row" class="zprow zprow-container zpalign-items-flex-start zpjustify-content- " data-equal-column="false"><style type="text/css"></style><div data-element-id="elm_m0kg8GLPQxC9duxaZjzdwA" data-element-type="column" class="zpelem-col zpcol-12 zpcol-md-8 zpcol-sm-12 zpalign-self- "><style type="text/css"></style><div data-element-id="elm_74ibrWaaGUOh2W1vw4r7sA" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true"><div>We talk with a lot of companies that want AI in their analytics practice, but aren't totally sure what that looks like. Many of them want it enough that they are tolerating some oddities they shouldn't be, especially as they're exploring feature sets that haven't yet proven their worth. Specifically, they're having to build/define the same data sources and metrics in the AI system, simply because the AI system can't handle connecting to what's already there.</div></div>
</div><div data-element-id="elm_70qtiLwgHa-FY-VkdqH8Rg" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true"><div><div style="text-align:left;">I've been working a lot with Ana, the agentic AI analyst made by TextQL, and have been extremely impressed by how it's able to navigate querying and connecting data from many different data sources, including APIs. Its command of Python and the iterative nature of analytics is the best I've seen. So I thought it might make sense for me to have Ana use Tableau's VizQL Data Service API to connect to Published Data Sources on Tableau Server (or Cloud), and see how it handles interacting with and querying the very same data sources that Tableau dashboards use. It took some iteration but worked out well. So I'm showing you how I did it!</div></div></div>
</div><div data-element-id="elm_aoW7YLHj1ghC5RoHo7Kxsw" data-element-type="text" class="zpelement zpelem-text zp-hidden-md zp-hidden-sm zp-hidden-xs "><style> [data-element-id="elm_aoW7YLHj1ghC5RoHo7Kxsw"].zpelem-text { background-color:#F3CECE; background-image:unset; border-style:solid; border-color:#D42B2B !important; border-width:1px; border-radius:4px; padding-inline-start:10px; } </style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true"><span style="font-style:italic;"><span style="font-weight:bold;">pssst.</span> if you'd like to see me&nbsp;actually&nbsp;do this live, register for the webinar on the right. i'd love to see you there</span></div>
</div><div data-element-id="elm_XYyrUMIK19BL3MSMHy9PLw" data-element-type="codeSnippet" class="zpelement zpelem-codesnippet zp-hidden-md zp-hidden-sm zp-hidden-xs "><div class="zpsnippet-container"><!-- Webinar Inline Snippet 1 - Compact Banner --><!-- For Zoho Sites: Add as Custom HTML element between paragraphs --><style> .hpt-webinar-inline-1 { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; background: #f7f7f7; border-radius: 10px; padding: 20px 24px; margin: 32px 0; display: flex; align-items: center; justify-content: space-between; gap: 20px; flex-wrap: wrap; position: relative; overflow: hidden; border: 1px solid #e8e8e8; } .hpt-webinar-inline-1::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 4px; background: rgb(235, 77, 94); } .hpt-webinar-inline-1 .content { flex: 1; min-width: 250px; } .hpt-webinar-inline-1 .label { display: inline-flex; align-items: center; gap: 6px; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.8px; color: rgb(200, 60, 75); margin-bottom: 6px; } .hpt-webinar-inline-1 .label svg { width: 14px; height: 14px; } .hpt-webinar-inline-1 h4 { font-size: 16px; font-weight: 600; color: #2a2a2a; margin: 0 0 4px 0; line-height: 1.4; } .hpt-webinar-inline-1 .meta { font-size: 13px; color: #666; } .hpt-webinar-inline-1 .cta-btn { display: inline-block; background: rgb(235, 77, 94); color: #fff; padding: 12px 24px; border-radius: 6px; text-decoration: none; font-weight: 600; font-size: 14px; white-space: nowrap; transition: background 0.2s, transform 0.2s; } .hpt-webinar-inline-1 .cta-btn:hover { background: rgb(210, 65, 80); transform: translateY(-1px); } </style><div class="hpt-webinar-inline-1"><div class="content"><div class="label"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="23 7 16 12 23 17 23 7"></polygon><rect x="1" y="5" width="15" height="14" rx="2" ry="2"></rect></svg> Live Webinar · Dec 18 </div>
<h4>Unlock AI-Powered Analytics on Your Existing Tableau Data</h4><div class="meta">Live demo of Ana + Tableau VDS Integration · 11:00 AM CST</div>
</div><a href="https://go.highperformance.tech/ana-tableau-vds-webinar-dec-2025/" class="cta-btn" target="_blank" rel="noopener"> Register Free </a></div>
</div></div><div data-element-id="elm_32fGC8sMVPl4os-XJoYyGA" data-element-type="heading" class="zpelement zpelem-heading "><style></style><h3
 class="zpheading zpheading-style-none zpheading-align-left zpheading-align-mobile-left zpheading-align-tablet-left " data-editor="true">What's VDS?</h3></div>
<div data-element-id="elm_dqTBNg-2ryy0Hc9qpcNbbw" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true"><div>Tableau's&nbsp;<a href="https://help.tableau.com/current/api/vizql-data-service/en-us/index.html">VizQL Data Service</a>&nbsp;API (VDS) gives you programmatic access to Published Data Sources on Tableau Server/Cloud. Since it's the same data sources your dashboards use (and therefore the same data model), you can reuse the field names, calculations, and relationships you've already defined. It automatically respects row-level security and permissions. There's no need to download extracts or hit underlying databases directly, risking bypassing the governance you've already established in Tableau. Instead, everything goes through Tableau's governance.</div></div>
</div><div data-element-id="elm_QYCAokRwW-YjX9Ag3PzF3Q" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true"><div>In contrast to the REST API, which is powerful for managing metadata and performing administrative operations, VDS provides a more direct way to access data. While the REST API allows you to download detail or summary data from specific visualizations, it requires an extra step. VDS lets you query the Published Data Source itself, just like the dashboards do, making it a more seamless option for accessing data.</div></div>
</div><div data-element-id="elm_oPNX3X_uMOX2zFRIbXy5Rg" data-element-type="heading" class="zpelement zpelem-heading "><style></style><h3
 class="zpheading zpheading-style-none zpheading-align-left zpheading-align-mobile-left zpheading-align-tablet-left " data-editor="true">Who or What is Ana?</h3></div>
<div data-element-id="elm_k9w5bEuJmrBJp3ZSoi304g" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true"><div>Ana is an agentic AI analyst built by TextQL. It's excellent at querying and coding (especially making API requests), tool calling, and iterative analysis. It understands data structures and relationships intuitively, and can reason about analysis steps. It can hold context across many many steps, build on previous queries, and create artifacts like Streamlit apps, static visualizations, downloadable reports... even playbooks that email you or Slack you their results after they run. (Yes, it supports Teams too...)</div></div>
</div><div data-element-id="elm_RnPVsIw7Vpbvk3KbxYMfjQ" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true"><div><div>And it's observable: you can see what it's doing, how it's doing it, and thanks to inline comments, <i>why</i> it's doing what it's doing.</div></div></div>
</div><div data-element-id="elm_GHb4XYkxtdXC_RHUoiFDaQ" data-element-type="heading" class="zpelement zpelem-heading "><style></style><h3
 class="zpheading zpheading-style-none zpheading-align-left zpheading-align-mobile-left zpheading-align-tablet-left " data-editor="true">Connecting Ana to Tableau with the VDS API</h3></div>
<div data-element-id="elm_9X70gTY71aA-hwfUhl9KUQ" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true"><div>To connect Ana to Tableau's Viz Data Service, we need:<br/><ul><li>Tableau Server/Cloud with Published Data Sources</li><li>Ana (hosted by TextQL) as the AI analyst</li><li>VDS API to let Ana query Tableau's Published Data Sources</li></ul></div></div>
</div><div data-element-id="elm_r1nC5rUqZoCePiXcL-d6sw" data-element-type="heading" class="zpelement zpelem-heading "><style></style><h4
 class="zpheading zpheading-style-none zpheading-align-left zpheading-align-mobile-left zpheading-align-tablet-left " data-editor="true">Setting Up</h4></div>
<div data-element-id="elm_u4gkPzT7u2O-6KWz9FQu7w" data-element-type="heading" class="zpelement zpelem-heading "><style></style><h5
 class="zpheading zpheading-style-none zpheading-align-left zpheading-align-mobile-left zpheading-align-tablet-left " data-editor="true"><span>Teach Ana about the relevant technologies using the Context Library</span></h5></div>
<div data-element-id="elm_Aie2u0tRQWOidKHrU81ejg" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-center zptext-align-mobile-center zptext-align-tablet-center " data-editor="true"><div></div><div></div><div style="text-align:left;"><div><div><div>To start out, we gave Ana some context that makes it better at using the relevant tools. Ana has a context library where you can store information that can be dynamically added to a chat's context based on user roles or which data connectors are selected for the chat. As TextQL admins, we can add this context from the &quot;Organization Context&quot; tab on the Settings page. It's quite straightforward:</div></div></div></div></div>
</div><div data-element-id="elm_zNk0W2i2Y9mzvmpj4NjiMg" data-element-type="image" class="zpelement zpelem-image "><style> @media (min-width: 992px) { [data-element-id="elm_zNk0W2i2Y9mzvmpj4NjiMg"] .zpimage-container figure img { width: 730px ; height: 587.65px ; } } </style><div data-caption-color="" data-size-tablet="" data-size-mobile="" data-align="center" data-tablet-image-separate="false" data-mobile-image-separate="false" class="zpimage-container zpimage-align-center zpimage-tablet-align-center zpimage-mobile-align-center zpimage-size-fit zpimage-tablet-fallback-fit zpimage-mobile-fallback-fit hb-lightbox " data-lightbox-options="
                type:fullscreen,
                theme:dark"><figure role="none" class="zpimage-data-ref"><span class="zpimage-anchor" role="link" tabindex="0" aria-label="Open Lightbox" style="cursor:pointer;"><picture><img class="zpimage zpimage-style-none zpimage-space-none " src="/organization-context.gif" size="fit" alt="A visual walkthrough of creating a context snippet and associating it with the Admin and Member roles." data-lightbox="true"/></picture></span></figure></div>
</div><div data-element-id="elm_YyGFZ-tSwu6kLn_TS7d6MA" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true"><p></p><div>To be clear, this context is not strictly required! It just allows Ana to cut through some repetition and trial-and-error, which saves us time and money. It took some iterating, and we ultimately landed on context that includes:<br/><ul><li>Authenticating to Tableau - using a Personal Access Token, in this case; more on that in a moment...</li><li>Finding the data source - using the good old-fashioned REST API</li><li>Exploring fields/metadata - using the new VDS API</li><li>Constructing queries</li><li>Analyzing results and iterating</li><li>Filtering strategies</li><li>Performance tips</li><li>Common patterns:</li><ul><li>Simple query</li><li>Searching with fuzzy matching</li><li>Complex filtering</li></ul><li>Error handling</li><li>Additional resources</li></ul>Here's the raw text we use that you can easily copy and paste into your own context library:</div><p></p></div>
</div><div data-element-id="elm_VmocnN6MRhzDHCQyk3CIzw" data-element-type="buttonicon" class="zpelement zpelem-buttonicon "><style></style><div class="zpbutton-container zpbutton-align-left zpbutton-align-mobile-center zpbutton-align-tablet-center "><style type="text/css"></style><a class="zpbutton-wrapper zpbutton zpbutton-type-link zpbutton-size-md zpbutton-icon-align-left " href="/files/ana-vds-api-context.txt" download rel="nofollow noopener"><span class="zpbutton-icon "><svg viewBox="0 0 24 24" height="24" width="24" aria-label="hidden" xmlns="http://www.w3.org/2000/svg"><path d="M11 5C11 4.44772 11.4477 4 12 4C12.5523 4 13 4.44772 13 5V12.1578L16.2428 8.91501L17.657 10.3292L12.0001 15.9861L6.34326 10.3292L7.75748 8.91501L11 12.1575V5Z"></path><path d="M4 14H6V18H18V14H20V18C20 19.1046 19.1046 20 18 20H6C4.89543 20 4 19.1046 4 18V14Z"></path></svg></span><span class="zpbutton-content">Download the Code Snippet</span></a></div>
</div><div data-element-id="elm_qXEunLlEUyj4yfSZ-RItVA" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true">With that in place, we need to ensure Ana can authenticate to the Tableau APIs when it needs to.</div>
</div><div data-element-id="elm_4-OcJY6ENpJ2rIqdYhpYdQ" data-element-type="heading" class="zpelement zpelem-heading "><style></style><h4
 class="zpheading zpheading-style-none zpheading-align-left zpheading-align-mobile-left zpheading-align-tablet-left " data-editor="true">Setting Up Authentication</h4></div>
<div data-element-id="elm_Y0xFBmwQqL4dCoQNiqFs1Q" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true"><p></p><div><div>Personal Access Tokens (PATs) are credentials you create in Tableau that let you authenticate to Tableau APIs without using your own username and password. They are revocable, and they keep you from exposing your actual credentials in various systems, scripts, or configuration files. If your Tableau account uses multi-factor authentication (MFA), like Tableau Cloud accounts often do, then PATs are&nbsp;<span style="font-style:italic;">required</span>&nbsp;for programmatic access since it can be difficult to complete an MFA challenge within an automated workflow.</div></div><p></p></div>
</div><div data-element-id="elm_KVi5iyfsDPIBb5EPxL5sHw" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true"><div>Creating a PAT in Tableau is very straightforward, and you can see a step-by-step guide here if you haven't done it before, or if you need a refresher: <a href="https://help.tableau.com/current/pro/desktop/en-us/useracct.htm#create-and-manage-personal-access-tokens" title="create and manage personal access tokens" target="_blank" rel="">create and manage personal access tokens</a>.</div><div><br/></div><div><div><span style="font-weight:bold;">Note:</span> Personal Access Tokens <span style="font-weight:bold;">do</span>&nbsp;have an expiration date on them! Based on my personal experience, it's useful to create a reminder a week or two before a PAT expires so you have a chance to create a new one and switch them out before it simply stops working. &quot;A bit of prevention is worth a byte of cure&quot;, as they say.</div></div></div>
</div><div data-element-id="elm_bGLChkXbIE0bBbvY41jYcA" data-element-type="image" class="zpelement zpelem-image "><style> @media (min-width: 992px) { [data-element-id="elm_bGLChkXbIE0bBbvY41jYcA"] .zpimage-container figure img { width: 730px ; height: 385.77px ; } } </style><div data-caption-color="" data-size-tablet="" data-size-mobile="" data-align="center" data-tablet-image-separate="false" data-mobile-image-separate="false" class="zpimage-container zpimage-align-center zpimage-tablet-align-center zpimage-mobile-align-center zpimage-size-fit zpimage-tablet-fallback-fit zpimage-mobile-fallback-fit hb-lightbox " data-lightbox-options="
                type:fullscreen,
                theme:dark"><figure role="none" class="zpimage-data-ref"><span class="zpimage-anchor" role="link" tabindex="0" aria-label="Open Lightbox" style="cursor:pointer;"><picture><img class="zpimage zpimage-style-none zpimage-space-none " src="/personal-access-token.png" size="fit" alt="Example of the Personal Access Token screen displaying a new PAT." data-lightbox="true"/></picture></span></figure></div>
</div><div data-element-id="elm_Ma79foOsNaqjc1xrtUcD5g" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true">Now that we have a PAT from Tableau, we need to store that in a place Ana can find it: the Environment Secrets. As the name suggests, Environment Secrets behaves like a password manager for Ana, allowing it to reference sensitive information like credentials without having to type the details out in the code verbatim.</div>
</div><div data-element-id="elm_DrmGyRQes7RXBa1s5o_LWA" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true">These secrets don't necessarily have to follow an exact pattern, because Ana can inspect them and determine the most appropriate way to use them. I prefer to store them as JSON with a little extra helpful information. In fact, if you downloaded the context snippet from earlier in the post, you'll want to follow the same structure I do. The structure is documented in the snippet, and the snippet's code examples depend on it. Here's what that looks like:</div>
</div><div data-element-id="elm__bOcSr-vhamJCFG2nC6nNg" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true"><div></div><code>{&quot;server&quot;: &quot;https://tableau.example.com&quot;,&quot;tokenName&quot;:&quot;YOUR_TOKEN_NAME&quot;,&quot;tokenSecret&quot;:&quot;YOUR_TOKEN_SECRET&quot;}</code></div>
</div><div data-element-id="elm_CFgJDwErwrGNgyarA3DMpw" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true"><div>From here, we navigate to the Settings &gt; Configuration &gt; Manage Secrets page, and create a new secret. We'll need to fill out:</div><div><ul><li>Secret Name - we refer to it as TABLEAU_PAT in our context snippet, so if you're following along, you should too.</li><li>Secret Value - the single line of JSON above, filled out with the relevant values.</li><li>Instructions - Ana sees these instructions in every chat context so it knows when to use which secret. Be sure to give it enough info to make those decisions! In this example, the instructions were: &quot;<span>Use this PAT to authenticate to Tableau Server using their REST API; the authenticated session can be used for other Tableau APIs as well.</span>&quot;</li><li>Docs Links - Any links to reference documentation that may be helpful when using the secret.</li></ul></div></div>
</div><div data-element-id="elm_0o-2fGPjqihK_LlmVbiahA" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true">Here's what the screen looks like just before we click Create:</div>
</div><div data-element-id="elm_xBoJL4gv57460WkFBOPzmg" data-element-type="image" class="zpelement zpelem-image "><style> @media (min-width: 992px) { [data-element-id="elm_xBoJL4gv57460WkFBOPzmg"] .zpimage-container figure img { width: 730px ; height: 422.03px ; } } </style><div data-caption-color="" data-size-tablet="" data-size-mobile="" data-align="center" data-tablet-image-separate="false" data-mobile-image-separate="false" class="zpimage-container zpimage-align-center zpimage-tablet-align-center zpimage-mobile-align-center zpimage-size-fit zpimage-tablet-fallback-fit zpimage-mobile-fallback-fit hb-lightbox " data-lightbox-options="
                type:fullscreen,
                theme:dark"><figure role="none" class="zpimage-data-ref"><span class="zpimage-anchor" role="link" tabindex="0" aria-label="Open Lightbox" style="cursor:pointer;"><picture><img class="zpimage zpimage-style-none zpimage-space-none " src="/secrets-manager.png" size="fit" alt="The &quot;Environment Secrets&quot; page with an open modal for adding a new secret." data-lightbox="true"/></picture></span></figure></div>
</div><div data-element-id="elm_u2pxtZheLU7NR7y5cSgTvQ" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true"><span style="font-weight:bold;">Note:</span> After you click save, you won't be able to edit or view the secret's details again. If that's a concern for you, there's an easy and more flexible solution we typically use, but that'll have to be a different blog post!</div>
</div><div data-element-id="elm_GkY9CC5JpbNDJeT3Uhi9zQ" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true"><div>At this point, we've given Ana enough information to:</div><div><ul><li>connect to Tableau using the URL and PAT details from the secret, and</li><li>skillfully use the relevant APIs to authenticate, search for data sources, explore their metadata and data, and query!</li></ul></div></div>
</div><div data-element-id="elm_xLNFEnvddiLTI4GUzg6wfQ" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true">Let's see how it works by creating a new thread and asking a question.</div>
</div><div data-element-id="elm_9xFRh6Utgcew20LvxzRzEw" data-element-type="heading" class="zpelement zpelem-heading "><style></style><h3
 class="zpheading zpheading-style-none zpheading-align-left zpheading-align-mobile-left zpheading-align-tablet-left " data-editor="true">Chatting With Our Data</h3></div>
<div data-element-id="elm_KRqiUVpV3Cc0XT5JmYIdxA" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true"><div><div>For demo purposes, <a href="https://www.kaggle.com/datasets/bhanuthakurr/cleaned-contoso-dataset?phase=FinishSSORegistration&amp;returnUrl=%2Fdatasets%2Fbhanuthakurr%2Fcleaned-contoso-dataset%2Fversions%2F8" title="we're using a demo data set based on Microsoft's Contoso data set" rel="">we're using a demo dataset based on Microsoft's Contoso dataset</a>.</div></div><div><span style="font-weight:bold;"><br/></span></div><div><div><div><span style="font-weight:normal;">We have created a Published Data Source on Tableau Server that exposes the Online Sales fact table and all of the related dimensions, as well as some calculated metrics and KPIs. We have&nbsp;<span style="font-weight:bold;">not</span>&nbsp;included any details about the data source in Ana's context library, so it will have to explore a little bit to understand how to collect the data we ask it for. Here's that data model in Tableau, for your reference:</span></div></div></div></div>
</div><div data-element-id="elm_bRAeYxCj11TIInHQBz2D9g" data-element-type="image" class="zpelement zpelem-image "><style> @media (min-width: 992px) { [data-element-id="elm_bRAeYxCj11TIInHQBz2D9g"] .zpimage-container figure img { width: 730px ; height: 299.76px ; } } </style><div data-caption-color="" data-size-tablet="" data-size-mobile="" data-align="center" data-tablet-image-separate="false" data-mobile-image-separate="false" class="zpimage-container zpimage-align-center zpimage-tablet-align-center zpimage-mobile-align-center zpimage-size-fit zpimage-tablet-fallback-fit zpimage-mobile-fallback-fit hb-lightbox " data-lightbox-options="
                type:fullscreen,
                theme:dark"><figure role="none" class="zpimage-data-ref"><span class="zpimage-anchor" role="link" tabindex="0" aria-label="Open Lightbox" style="cursor:pointer;"><picture><img class="zpimage zpimage-style-none zpimage-space-none " src="/contoso-online-sales-model.png" size="fit" data-lightbox="true"/></picture></span></figure></div>
</div><div data-element-id="elm_TX0X0t0t9c6B-jjFIkq4BA" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true">To keep things interesting, I'm going to ask a question with an intentional but easy-to-resolve error in it:&nbsp;</div>
</div><div data-element-id="elm_cKwLENBK7PKSSZTd0ZsF2A" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true"><blockquote style="margin-left:40px;">&quot;Using the Contoso Online Sales data source, show me the sales over time for the customer named Tom Miller.&quot;</blockquote></div>
</div><div data-element-id="elm_Ckds2SdE8ATE0nu-bW01Dw" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true">The dataset has a &quot;Thomas Miller&quot; in it, so it will be interesting to see how Ana handles the discrepancy there. And with that background information out of the way, let's see the chat!</div>
</div><div data-element-id="elm_bqrmR4h9ILl_bqfVqLQcHw" data-element-type="row" class="zprow zprow-container zpalign-items-flex-start zpjustify-content-flex-start zpdefault-section zpdefault-section-bg " data-equal-column="false"><style type="text/css"></style><div data-element-id="elm_3g4K9B-7rERkCD1zf-sciQ" data-element-type="column" class="zpelem-col zpcol-12 zpcol-md-5 zpcol-sm-12 zpalign-self- zpdefault-section zpdefault-section-bg "><style type="text/css"></style><div data-element-id="elm_g7CybuC8UNxpiDlqQkjSPw" data-element-type="image" class="zpelement zpelem-image "><style> @media (min-width: 992px) { [data-element-id="elm_g7CybuC8UNxpiDlqQkjSPw"] .zpimage-container figure img { width: 200px ; height: 442.60px ; } } [data-element-id="elm_g7CybuC8UNxpiDlqQkjSPw"] .zpimage-container[class*='zpimage-overlay-effect-'] figure:hover figcaption , [data-element-id="elm_g7CybuC8UNxpiDlqQkjSPw"] .zpimage-container[class*='zpimage-overlay-effect-'] figure figcaption { background:rgba(1,58,81,0.8) ; } </style><div data-caption-color="" data-size-tablet="" data-size-mobile="" data-align="center" data-tablet-image-separate="false" data-mobile-image-separate="false" class="zpimage-container zpimage-align-center zpimage-tablet-align-center zpimage-mobile-align-center zpimage-size-small zpimage-tablet-fallback-fit zpimage-mobile-fallback-fit zpimage-overlay zpimage-overlay-effect-static-bottom hb-lightbox " data-lightbox-options="
                type:fullscreen,
                theme:dark"><figure role="none" class="zpimage-data-ref"><span class="zpimage-anchor" role="link" tabindex="0" aria-label="Open Lightbox" style="cursor:pointer;"><picture><img class="zpimage zpimage-style-none zpimage-space-none " src="/tom-miller-sales.png" size="small" data-lightbox="true"/></picture></span><figcaption class="zpimage-caption zpimage-caption-align-center"><span class="zpimage-caption-content">Click to expand</span></figcaption></figure></div>
</div></div><div data-element-id="elm_CQkDlIXbRGgyHpEjMQXGgg" data-element-type="column" class="zpelem-col zpcol-12 zpcol-md-7 zpcol-sm-12 zpalign-self- zpdefault-section zpdefault-section-bg "><style type="text/css"></style><div data-element-id="elm_m4iPkWVxJPh0qPqq2rRVqA" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true">The chat has a few components to it:</div>
</div><div data-element-id="elm_qEdz7tFmoccuuXjFjOTNBw" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true"><ul><li>The response messages</li><li>The tool call cells</li><li>Artifact previews</li><li>The summary response</li></ul></div>
</div><div data-element-id="elm_3PngwPocfEe0Ynj--cQ32g" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true">In my mind, the interesting things happen in the tool call cells. We can see Ana follow the overall structure we detailed in the context library. It:<p></p></div>
</div><div data-element-id="elm_eCd2LYtSGRfUR-IksIBqbg" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true"><ol><li>Authenticates</li><li>Lists Data Sources</li><li>Reads Metadata</li><li>Queries Data, and</li><li>Analyzes in Python</li></ol></div>
</div></div></div><div data-element-id="elm_ckS8y_zORhqXqgG-vd6xgg" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true">Overall, everything went as well as we could hope, in seven individual API calls to Tableau! It handled the name issue pretty well. It queried using filters for the First and Last name, and found no results. Since it expected results but found none, it decided to look into the customer names to find similar names. There are a handful of approaches it could have used, but in this case it decided to search for similar names, found the full list of Millers. With the relevant names in context now, it determined that &quot;Thomas&quot; was likely the one I was asking about, and proceeded from there.</div>
</div><div data-element-id="elm_W2JAWZPjozooJb-PIPaNLg" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true">It then created and saved some visualizations of the data using python, as well as wrote the underlying data to a CSV. Finally, it responded with a summary of its findings.</div>
</div><div data-element-id="elm_D5BgyOIHQnpGWkBqcWri3w" data-element-type="heading" class="zpelement zpelem-heading "><style></style><h3
 class="zpheading zpheading-style-none zpheading-align-left zpheading-align-mobile-left zpheading-align-tablet-left " data-editor="true">It Works! Now What?</h3></div>
<div data-element-id="elm_cSGdNkiQ1HPUdINLxwHl5A" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true">So we now have an agentic AI analyst capable of connecting to Tableau and querying the same Published Data Sources we use in our workbooks. We see it's capable of correcting course when things aren't exactly as it first thought. Where do we go from here? Below are a few use cases we've implemented already, and one we're about to implement:</div>
</div><div data-element-id="elm_kih1VnUoIEMggtW_TGAk-Q" data-element-type="heading" class="zpelement zpelem-heading "><style></style><h4
 class="zpheading zpheading-style-none zpheading-align-left zpheading-align-mobile-left zpheading-align-tablet-left " data-editor="true">Playbooks - Executive Summaries via Email or Slack</h4></div>
<div data-element-id="elm_RKBMBXEN2rODMNhB1kLmfA" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true">I mentioned Ana has a playbooks feature that allows you to schedule these analyses to be delivered on a recurring basis over Email, Slack, or Teams. We've implemented operational playbooks that surface actionable information around deal flow and accounts receivable aging. Users enjoy getting the relevant data delivered to them directly in Slack, and&nbsp;<span style="font-weight:bold;">really enjoy</span>&nbsp;being able to ask follow-up questions in the same thread.</div>
</div><div data-element-id="elm_y8Op77dP_RFsoon25uqIvA" data-element-type="heading" class="zpelement zpelem-heading "><style></style><h4
 class="zpheading zpheading-style-none zpheading-align-left zpheading-align-mobile-left zpheading-align-tablet-left " data-editor="true">Exploring Complicated Database Schemas</h4></div>
<div data-element-id="elm_mi5mkfs0fP0gK-axlf2CyQ" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true">We've already seen that Ana is adept at course-correction if something seems off about its findings. Similarly, it's also very good at exploring database schemas and determining how data relates. We have implemented Ana for users who need to uncover specific data without spending a ton of time digesting complicated schemas and ERDs. Ana's iterative discovery process makes easy work of that, and the context library allows users to improve Ana's efficiency over time as it discovers the relevant relationships within the data. Of course, there are several great approaches to mapping out the data models in advance, but that's out of the scope of&nbsp;<span style="font-style:italic;">this</span>&nbsp;post.</div>
</div><div data-element-id="elm_5V6VQqmHVY8z6N8_vIO7Zg" data-element-type="heading" class="zpelement zpelem-heading "><style></style><h4
 class="zpheading zpheading-style-none zpheading-align-left zpheading-align-mobile-left zpheading-align-tablet-left " data-editor="true">In-Dashboard Chats</h4></div>
<div data-element-id="elm_FqzcifuCjvMRVz1FOA5wkQ" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true">This is the one we're most excited to try next: embedding Ana&nbsp;<span style="font-style:italic;">directly inside of a dashboard<span style="font-style:normal;">, giving users a way to ask relevant questions about the data that's right in front of them at the moment.</span></span></div>
</div><div data-element-id="elm_MBGL6Lh_U0H2rDbVKVvSDA" data-element-type="heading" class="zpelement zpelem-heading zp-hidden-md zp-hidden-sm zp-hidden-xs "><style></style><h3
 class="zpheading zpheading-style-type1 zpheading-align-left zpheading-align-mobile-left zpheading-align-tablet-left " data-editor="true">Come see this demo live!</h3></div>
<div data-element-id="elm_aj298wMsmnbq39GWJOJLuQ" data-element-type="text" class="zpelement zpelem-text zp-hidden-md zp-hidden-sm zp-hidden-xs "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true"><p>Want to see a live end-to-end demonstration of what you just read above? Come see the process start-to-finish, so you can do this with your own Tableau environment! Click the big red button below to register. Even if you can't make it live, we'll send all registrants a recording of the webinar once it wraps up.</p></div>
</div><div data-element-id="elm_lA8Ml603zggkM998asKQUA" data-element-type="codeSnippet" class="zpelement zpelem-codesnippet zp-hidden-md zp-hidden-sm zp-hidden-xs "><div class="zpsnippet-container"><!-- Webinar Inline Snippet 1 - Compact Banner --><!-- For Zoho Sites: Add as Custom HTML element between paragraphs --><style> .hpt-webinar-inline-1 { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; background: #f7f7f7; border-radius: 10px; padding: 20px 24px; margin: 32px 0; display: flex; align-items: center; justify-content: space-between; gap: 20px; flex-wrap: wrap; position: relative; overflow: hidden; border: 1px solid #e8e8e8; } .hpt-webinar-inline-1::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 4px; background: rgb(235, 77, 94); } .hpt-webinar-inline-1 .content { flex: 1; min-width: 250px; } .hpt-webinar-inline-1 .label { display: inline-flex; align-items: center; gap: 6px; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.8px; color: rgb(200, 60, 75); margin-bottom: 6px; } .hpt-webinar-inline-1 .label svg { width: 14px; height: 14px; } .hpt-webinar-inline-1 h4 { font-size: 16px; font-weight: 600; color: #2a2a2a; margin: 0 0 4px 0; line-height: 1.4; } .hpt-webinar-inline-1 .meta { font-size: 13px; color: #666; } .hpt-webinar-inline-1 .cta-btn { display: inline-block; background: rgb(235, 77, 94); color: #fff; padding: 12px 24px; border-radius: 6px; text-decoration: none; font-weight: 600; font-size: 14px; white-space: nowrap; transition: background 0.2s, transform 0.2s; } .hpt-webinar-inline-1 .cta-btn:hover { background: rgb(210, 65, 80); transform: translateY(-1px); } </style><div class="hpt-webinar-inline-1"><div class="content"><div class="label"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="23 7 16 12 23 17 23 7"></polygon><rect x="1" y="5" width="15" height="14" rx="2" ry="2"></rect></svg> Live Webinar · Dec 18 </div>
<h4>Unlock AI-Powered Analytics on Your Existing Tableau Data</h4><div class="meta">Live demo of Ana + Tableau VDS Integration · 11:00 AM CST</div>
</div><a href="https://go.highperformance.tech/ana-tableau-vds-webinar-dec-2025/" class="cta-btn" target="_blank" rel="noopener"> Register Free </a></div>
</div></div><div data-element-id="elm_GyJhhcV3Cy_d45TalujkWg" data-element-type="heading" class="zpelement zpelem-heading "><style></style><h3
 class="zpheading zpheading-style-type1 zpheading-align-left zpheading-align-mobile-left zpheading-align-tablet-left " data-editor="true">Want to skip the line and get started now?</h3></div>
<div data-element-id="elm_W3wDcCHLRG0BaeKqXpwVFw" data-element-type="text" class="zpelement zpelem-text "><style></style><div class="zptext zptext-align-left zptext-align-mobile-left zptext-align-tablet-left " data-editor="true">Schedule a time to talk with us about our Ana Quick Start, where we guide you and your users through the &quot;getting started&quot; phase so you can see immediate value.</div>
</div><div data-element-id="elm_4Zz-BA8vR-CKUALhQ3IHzw" data-element-type="button" class="zpelement zpelem-button "><style></style><div class="zpbutton-container zpbutton-align-center zpbutton-align-mobile-center zpbutton-align-tablet-center"><style type="text/css"></style><a class="zpbutton-wrapper zpbutton zpbutton-type-secondary zpbutton-size-md zpbutton-style-none " href="https://calendly.com/highperformancetechnologies/quick-call?month=2025-12" target="_blank"><span class="zpbutton-content">Schedule a Call</span></a></div>
</div></div><div data-element-id="elm_r6c2HRP3XZnvwFAl4N6J-Q" data-element-type="column" class="zpelem-col zpcol-12 zpcol-md-4 zpcol-sm-12 zpalign-self- zpdefault-section zpdefault-section-bg zp-hidden-sm zp-hidden-xs zpsticky-enabled"><style type="text/css"> @media (min-width:992px) { [data-element-id="elm_r6c2HRP3XZnvwFAl4N6J-Q"].zpelem-col{ top:150px;z-index:1; } } </style><div data-element-id="elm_Q6c4tjahsq8JYJHZM403TQ" data-element-type="row" class="zprow zprow-container zpalign-items-flex-start zpjustify-content-flex-start zpdefault-section zpdefault-section-bg " data-equal-column="false"><style type="text/css"></style><div data-element-id="elm_L5JHdlowE0ZB-3t5VFQp8A" data-element-type="column" class="zpelem-col zpcol-12 zpcol-md-12 zpcol-sm-12 zpalign-self- zpdefault-section zpdefault-section-bg "><style type="text/css"></style><div data-element-id="elm_qmHZhz5pM5QLGgy5DznhfA" data-element-type="codeSnippet" class="zpelement zpelem-codesnippet zp-hidden-md zp-hidden-sm zp-hidden-xs "><div class="zpsnippet-container"><!-- Webinar Sidebar Snippet - Tableau VDS + Ana Integration --><!-- For Zoho Sites: Add as Custom HTML element in sidebar --><!-- NOTE: Replace the placeholder image src with Brad Fair's actual photo URL --><style> .hpt-webinar-sidebar { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; background: #ffffff; border-radius: 12px; padding: 24px; color: #333; position: relative; overflow: hidden; box-shadow: 0 4px 20px rgba(0,0,0,0.08); border: 1px solid #e8e8e8; } .hpt-webinar-sidebar::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 4px; background: rgb(235, 77, 94); } .hpt-webinar-sidebar .badge { display: inline-block; background: rgba(235, 77, 94, 0.1); color: rgb(200, 60, 75); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 1px; padding: 4px 10px; border-radius: 4px; margin-bottom: 12px; } .hpt-webinar-sidebar h3 { font-size: 18px; font-weight: 700; line-height: 1.35; margin: 0 0 14px 0; color: #2a2a2a; } .hpt-webinar-sidebar .info-row { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; padding: 12px; background: #f7f7f7; border-radius: 8px; } .hpt-webinar-sidebar .date-box { background: rgb(235, 77, 94); color: #fff; text-align: center; padding: 8px 12px; border-radius: 6px; min-width: 50px; flex-shrink: 0; } .hpt-webinar-sidebar .date-box .month { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 1px; } .hpt-webinar-sidebar .date-box .day { font-size: 22px; font-weight: 700; line-height: 1.1; } .hpt-webinar-sidebar .date-text { flex: 1; font-size: 14px; color: #666; } .hpt-webinar-sidebar .date-text strong { display: block; color: #2a2a2a; } .hpt-webinar-sidebar .host-photo { width: 48px; height: 48px; border-radius: 50%; object-fit: cover; border: 2px solid #fff; box-shadow: 0 2px 8px rgba(0,0,0,0.1); flex-shrink: 0; } .hpt-webinar-sidebar .description { font-size: 14px; line-height: 1.55; color: #555; margin-bottom: 18px; } .hpt-webinar-sidebar .cta-btn { display: block; width: 100%; background: rgb(235, 77, 94); color: #fff; text-align: center; padding: 14px 20px; border-radius: 6px; text-decoration: none; font-weight: 600; font-size: 15px; transition: background 0.2s, transform 0.2s; box-sizing: border-box; } .hpt-webinar-sidebar .cta-btn:hover { background: rgb(210, 65, 80); transform: translateY(-1px); } .hpt-webinar-sidebar .host { margin-top: 16px; padding-top: 14px; border-top: 1px solid #eee; font-size: 12px; color: #888; } .hpt-webinar-sidebar .host strong { color: #555; } </style><div class="hpt-webinar-sidebar"><span class="badge">Free Live Webinar</span><h3>Unlock AI-Powered Analytics on Your Existing Tableau Data</h3><div class="info-row"><div class="date-box"><div class="month">Dec</div>
<div class="day">18</div></div><div class="date-text"><strong>Thursday</strong> 11:00 AM CST </div>
<!--
		<img src="https://www.highperformance.tech/files/img/brad-fair.jpg" alt="Brad Fair" class="host-photo"> --> </div>
<p class="description"> See how to connect TextQL's Ana directly to your Tableau data sources—no rebuilding metrics or governance policies required. </p><a href="https://go.highperformance.tech/ana-tableau-vds-webinar-dec-2025/" class="cta-btn" target="_blank" rel="noopener"> Register Now → </a><div class="host"> Hosted by <strong>Brad Fair</strong>, Principal Solutions Architect </div>
</div></div></div></div></div></div></div></div></div></div> ]]></content:encoded><pubDate>Fri, 12 Dec 2025 13:17:21 -0600</pubDate></item><item><title><![CDATA[Tableau Server on Kubernetes – First Thoughts]]></title><link>https://www.highperformance.tech/blogs/post/tableau-server-on-kubernetes-first-thoughts</link><description><![CDATA[<img align="left" hspace="5" src="https://www.highperformance.tech/files/img/ts-on-k8s.png"/>First thoughts on deploying a highly available Tableau Server environment on managed Kubernetes, using spot instance pricing — without significant application downtime during node failures.]]></description><content:encoded><![CDATA[<div class="zpcontent-container blogpost-container "><div data-element-id="elm_GEYGjV0FQbSYeK2FsXKMBA" data-element-type="section" class="zpsection "><style type="text/css"></style><div class="zpcontainer-fluid zpcontainer"><div data-element-id="elm_8shgaE9JQ5WD7Hxi8Xob2g" data-element-type="row" class="zprow zprow-container zpalign-items- zpjustify-content- " data-equal-column=""><style type="text/css"></style><div data-element-id="elm_-ty8hDD6RgCzj9TzhRCGxg" data-element-type="column" class="zpelem-col zpcol-12 zpcol-md-12 zpcol-sm-12 zpalign-self- "><style type="text/css"> [data-element-id="elm_-ty8hDD6RgCzj9TzhRCGxg"].zpelem-col{ border-radius:1px; } </style><div data-element-id="elm_OxvabfgxYCgkARRMa15BHA" data-element-type="text" class="zpelement zpelem-text "><style> [data-element-id="elm_OxvabfgxYCgkARRMa15BHA"].zpelem-text { border-radius:1px; } </style><div class="zptext zptext-align-left " data-editor="true"><div><div>I'm always looking for ways to make deployment and management of Tableau Server easier, so I was super excited when the devs at Tableau announced the release of Tableau Server in a Container. Since then, I've spent a lot of time figuring out the right way to deploy Tableau Server using Kubernetes. My end goal is to deploy a highly available Tableau Server environment on any mainstream implementation of Kubernetes, using spot instance pricing — without significant application downtime during node failures. It's a tall order, but I'm getting there! Read on to find out what I've learned along the way.</div></div></div>
</div><div data-element-id="elm_sVdU0WAV4ARCXtlYDx7jZg" data-element-type="heading" class="zpelement zpelem-heading "><style> [data-element-id="elm_sVdU0WAV4ARCXtlYDx7jZg"].zpelem-heading { border-radius:1px; } </style><h2
 class="zpheading zpheading-style-type1 zpheading-align-left " data-editor="true"><div><div>Building Tableau Server Container Images</div></div></h2></div>
<div data-element-id="elm_6af3peE2QaQpzjmg1tUJPg" data-element-type="text" class="zpelement zpelem-text "><style> [data-element-id="elm_6af3peE2QaQpzjmg1tUJPg"].zpelem-text { border-radius:1px; } </style><div class="zptext zptext-align-left " data-editor="true"><div><div><div>One of the first things you'll notice when looking at Tableau Server in a Container is that it requires you to build your own container images. It seems odd at first, but it does make sense from a few perspectives:</div><div><ul><li>Customers require different drivers for their environments</li><li>Many drivers are licensed such that Tableau can't package them</li><li>Customers may be using technologies that don't offer other means of injecting drivers into containers; for instance, a Docker container doesn't allow for InitContainers.</li></ul></div></div></div></div>
</div><div data-element-id="elm_wX4f-zNUOfPs_HNKKLgMXQ" data-element-type="text" class="zpelement zpelem-text "><style> [data-element-id="elm_wX4f-zNUOfPs_HNKKLgMXQ"].zpelem-text { border-radius:1px; } </style><div class="zptext zptext-align-left " data-editor="true"><p>Still, it would be nice if Tableau provided a base image with which customers could add any necessary drivers, scripts, and other customizations. I'm likely to implement this model myself in the meantime.<br></p></div>
</div><div data-element-id="elm_Wm5Qf2kjA7jr5kd5l-P3NA" data-element-type="heading" class="zpelement zpelem-heading "><style> [data-element-id="elm_Wm5Qf2kjA7jr5kd5l-P3NA"].zpelem-heading { border-radius:1px; } </style><h2
 class="zpheading zpheading-style-type1 zpheading-align-left " data-editor="true">Tableau Server Container Image Size</h2></div>
<div data-element-id="elm_GvF7m-8aNL6mUEzfsnk2kw" data-element-type="text" class="zpelement zpelem-text "><style> [data-element-id="elm_GvF7m-8aNL6mUEzfsnk2kw"].zpelem-text { border-radius:1px; } </style><div class="zptext zptext-align-left " data-editor="true"><div><div>The next thing you'll notice immediately after building your first image is how insanely large it is! For the most recent version or two of Tableau Server, my images are nearly 6GB. Large images can be a problem when dealing with spot instances and auto-scaling because it increases the time for new nodes to take over the work from old nodes. Still, it's not too bad if the images are close to the Kubernetes cluster. In my case, I've opted to store my Tableau Server images on Amazon ECR rather than Docker Hub. That download takes just over a minute on new nodes.</div></div></div>
</div><div data-element-id="elm_gK0IfV2NfELKIJgBWUhlvQ" data-element-type="text" class="zpelement zpelem-text "><style> [data-element-id="elm_gK0IfV2NfELKIJgBWUhlvQ"].zpelem-text { border-radius:1px; } </style><div class="zptext zptext-align-left " data-editor="true"><div><div>I haven't spent much time trying to reduce the image footprint to date, though I'd like to try a couple of things. If that ever makes it to the top of my to-do list, I'll share my findings.</div></div></div>
</div><div data-element-id="elm_350mPcqc1b3mjlA03ueONQ" data-element-type="heading" class="zpelement zpelem-heading "><style> [data-element-id="elm_350mPcqc1b3mjlA03ueONQ"].zpelem-heading { border-radius:1px; } </style><h2
 class="zpheading zpheading-style-type1 zpheading-align-left " data-editor="true">Upgrading Tableau Server in a Container</h2></div>
<div data-element-id="elm_jeDZEbJhe4vNNWQwdmg1JA" data-element-type="text" class="zpelement zpelem-text "><style> [data-element-id="elm_jeDZEbJhe4vNNWQwdmg1JA"].zpelem-text { border-radius:1px; } </style><div class="zptext zptext-align-left " data-editor="true"><div><div>So far, Tableau provides a couple of upgrade options in the documentation:<br><div><div><div><ul><li>Create a special &quot;upgrade&quot; image, using both your current and desired versions of Tableau Server... yuck. And,</li><li>Take a backup of your current environment, and restore that to an environment using the new images. Also yuck.</li></ul></div></div></div></div></div></div>
</div><div data-element-id="elm_G6V3lAWDbBeWULXNQF9XYw" data-element-type="text" class="zpelement zpelem-text "><style> [data-element-id="elm_G6V3lAWDbBeWULXNQF9XYw"].zpelem-text { border-radius:1px; } </style><div class="zptext zptext-align-left " data-editor="true"><p><span style="color:inherit;">I can think of a few ways to improve this process using Kubernetes functionality, which is my next project. I'll write an article on the process once I've got something worth sharing.</span><br></p></div>
</div><div data-element-id="elm_GHgdEdg5ueKwRZiYcFtO_Q" data-element-type="heading" class="zpelement zpelem-heading "><style> [data-element-id="elm_GHgdEdg5ueKwRZiYcFtO_Q"].zpelem-heading { border-radius:1px; } </style><h2
 class="zpheading zpheading-style-type1 zpheading-align-left " data-editor="true">Tableau Server Kubernetes Manifests</h2></div>
<div data-element-id="elm_8z-7hEDkwz1rHopuxuA7UA" data-element-type="text" class="zpelement zpelem-text "><style> [data-element-id="elm_8z-7hEDkwz1rHopuxuA7UA"].zpelem-text { border-radius:1px; } </style><div class="zptext zptext-align-left " data-editor="true"><div><div><div><div>Tableau was kind enough to provide a <a href="https://github.com/tableau/tableau-server-in-kubernetes" title="GitHub repository" target="_blank" rel="">GitHub repository</a> with manifest files for running Tableau Server on Kubernetes. I appreciated the resource, and I used it as a starting point for my manifests. Here are some of the things I've added/changed so far:</div></div><div><ul><li>Combined all Tableau <span style="text-decoration-line:line-through;">nodes</span> pods into a single StatefulSet – it fits the use case almost perfectly.</li><li>Use volumeClaimTemplates for the data directory volumes instead of making the PVCs manually.</li><li>Rewrote the startup and initialization logic to account for clusters of varying sizes. There's plenty more to do here, but it's a good start.</li><li>Rewrote the readinessProbe check—it's almost right, now. Different pods will have different processes, and I want to account for those differences dynamically, whatever they might be.</li><li>Added podAntiAffinity to ensure pods are scheduled on separate Kubernetes nodes. We're trying to tolerate pod failures gracefully in order to use spot instance pricing, so we need to minimize the impact of such failures.</li><li>I wanted to avoid needing a ReadWriteMany PVC, so I rewrote the bootstrap.json file process for multi-node environments. More on this below.</li><li>Added a preStop lifecycle hook to failover the repository if it's running on a pod that's terminating. More on this below, too.</li><li>I implemented this with <a href="https://kubernetes.io/docs/tasks/manage-kubernetes-objects/kustomization/" title="kustomize" target="_blank" rel="">kustomize</a> so I can more easily deploy and manage different environments from the same base manifests.<br></li></ul></div></div></div></div>
</div><div data-element-id="elm_543mOwX8uguvkVLzKyANGQ" data-element-type="text" class="zpelement zpelem-text "><style> [data-element-id="elm_543mOwX8uguvkVLzKyANGQ"].zpelem-text { border-radius:1px; } </style><div class="zptext zptext-align-left " data-editor="true"><div><div><div>Using the custom manifests, I tested deploying several 3-node HA clusters to GKE and EKS and a couple of 7-node clusters on EKS as well. I didn't need to make any changes between GKE/EKS. The only differences between the 3-node and 7-node clusters were the config.json configmap and the number of replicas. The <a href="https://www.vizstack.io/" title="Vizstack" target="_blank" rel="">Vizstack</a> team is building a tool to help create a valid config.json file, which will make this process easier!</div></div></div></div>
</div><div data-element-id="elm_beoMxorzPjkiR_trqx2jdQ" data-element-type="heading" class="zpelement zpelem-heading "><style> [data-element-id="elm_beoMxorzPjkiR_trqx2jdQ"].zpelem-heading { border-radius:1px; } </style><h2
 class="zpheading zpheading-style-none zpheading-align-left " data-editor="true">Tableau Server Cluster Initialization</h2></div>
<div data-element-id="elm_FfqbTtJ52gAQjoyr9Mcy6Q" data-element-type="text" class="zpelement zpelem-text "><style> [data-element-id="elm_FfqbTtJ52gAQjoyr9Mcy6Q"].zpelem-text { border-radius:1px; } </style><div class="zptext zptext-align-left " data-editor="true"><div><div><div>Initializing a Tableau Server cluster is pretty straightforward after the image is built and the configuration + manifests are ready. Once the manifests are applied, a handful of things happen:</div><br><div><ul><li>The initial node (pod 0 of the StatefulSet) is deployed and goes through an initialization script. If your configuration file's topology includes appzookeeper, that's filtered out at this stage because we always need to start with exactly one instance of it.</li><li>After pod 0 initializes, the script determines how many nodes your config.json file specifies and waits for those nodes to register. All other pods in the StatefulSet will need to register at this time, which is why my manifests don't yet support <a href="https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#pod-management-policies" title="OrderedReady pod management" target="_blank" rel="">OrderedReady pod management</a> – all pods start in parallel.<br></li><li>The other pods in the StatefulSet will be waiting for a &quot;bootstrap.json&quot; file that will allow them to register with the initial <span style="text-decoration-line:line-through;">node</span> pod. Tableau's manifests implied using a ReadWriteMany volume to distribute this file, but it's easier to get directly with the tsm command-line utility. This method requires you to build your image with TSM_REMOTE_UID and TSM_REMOTE_USERNAME variables set. But it makes the whole thing way easier to deploy, in my opinion. I shared this feedback directly with the dev team at Tableau.</li><li>Once the other pods register, the initial pod continues with the cluster configuration. It configures and deploys all the services specified in your config.json file, except for the coordination service.</li><li>After the configuration is applied, the initialization script will review your config.json file for your desired coordination service config. If you've specified a 3- or 5-node coordination service ensemble, it will deploy properly using the <a href="https://help.tableau.com/current/server/en-us/distrib_ha_zk.htm#deploy-a-new-coordination-service-ensemble" title="tsm topology deploy-coordination-service" target="_blank" rel="">tsm topology deploy-coordination-service</a> command, as is tradition.<br></li><li>Finally, it starts the services and configures the initial user.</li></ul></div></div></div></div>
</div><div data-element-id="elm_tXgdxYFRbK1UiIW-q6txww" data-element-type="text" class="zpelement zpelem-text "><style> [data-element-id="elm_tXgdxYFRbK1UiIW-q6txww"].zpelem-text { border-radius:1px; } </style><div class="zptext zptext-align-left " data-editor="true"><div><div>There are a few aspects of this workflow that I'll be improving upon in my manifests. Right now, the initial node handles all configuration tasks, so we have to provide the complete configuration at the very beginning. It has no mechanism for detecting and implementing changes to the configuration. Since I've already verified that remote tsm works, I'd like to implement the initialization such that each node is responsible for the changes it introduces to the cluster. That way, if I want to scale up or down, I can do it declaratively.</div></div></div>
</div><div data-element-id="elm_7J6dtoWiXb_o3rVNI-xc1w" data-element-type="heading" class="zpelement zpelem-heading "><style> [data-element-id="elm_7J6dtoWiXb_o3rVNI-xc1w"].zpelem-heading { border-radius:1px; } </style><h2
 class="zpheading zpheading-style-type1 zpheading-align-left " data-editor="true">Tolerating Pod Failure</h2></div>
<div data-element-id="elm_O6mmHNMhzViMiQ-_EwrQXQ" data-element-type="text" class="zpelement zpelem-text "><style> [data-element-id="elm_O6mmHNMhzViMiQ-_EwrQXQ"].zpelem-text { border-radius:1px; } </style><div class="zptext zptext-align-left " data-editor="true"><div><div><div>Since one of my main goals is using spot instance pricing for Tableau Server environments, I need to address the elephant in the room. Spot instance pricing saves a significant amount of money, with one very notable drawback: cloud providers have the option of reclaiming instances with barely any notice.</div><div><ul><li><a href="https://aws.amazon.com/ec2/spot/" title="Amazon EC2 Spot Instances" target="_blank" rel="">Amazon EC2 Spot Instances</a> will give you a two-minute warning before reclaiming compute capacity.<br></li><li><a href="https://cloud.google.com/compute/docs/instances/spot" title="Google Cloud Spot VMs" target="_blank" rel="">Google Cloud Spot VMs</a> will give you a 30-second warning before preemption.<br></li><li><a href="https://docs.microsoft.com/en-us/azure/virtual-machines/spot-vms" title="Azure Spot Virtual Machines" rel="">Azure Spot Virtual Machines</a><a href="https://docs.microsoft.com/en-us/azure/virtual-machines/spot-vms" title="Azure Spot Virtual Machines" target="_blank" rel=""></a> will also give you a 30-second warning before eviction.<br></li><li>It's not totally accurate, but I'm just going to refer to this event as &quot;node failure&quot; because cloud providers seem to avoid using the same terminology.</li></ul></div></div></div></div>
</div><div data-element-id="elm_ukuuRCiRBUZyFtZgazXvaA" data-element-type="text" class="zpelement zpelem-text "><style> [data-element-id="elm_ukuuRCiRBUZyFtZgazXvaA"].zpelem-text { border-radius:1px; } </style><div class="zptext zptext-align-left " data-editor="true"><p><span style="color:inherit;">Since Tableau Server's startup time is longer than any of those timeframes, we can't just start a new pod when receiving that warning. To benefit from spot pricing, we need to make Tableau Server capable of tolerating some amount of failure. We can <a href="https://help.tableau.com/current/server/en-us/distrib_ha.htm" title="configure Tableau Server for high availability" target="_blank" rel="">configure Tableau Server for high availability</a> by ensuring we have three or more pods in a cluster and configuring instances of each process across multiple nodes. Even configured for HA, there's still some impact if the Active Repository process goes down — the application can become unavailable for <a href="https://help.tableau.com/current/server/en-us/distrib_ha_failover.htm#automatic-repository-failover" title="five minutes" target="_blank" rel="">five minutes</a> before signaling the Passive Repository to take over. We can do better than that since we have a bit of advance notice!&nbsp;</span><br></p></div>
</div><div data-element-id="elm_LVsrGO0Z5kSKU0_a1qAbtQ" data-element-type="text" class="zpelement zpelem-text "><style> [data-element-id="elm_LVsrGO0Z5kSKU0_a1qAbtQ"].zpelem-text { border-radius:1px; } </style><div class="zptext zptext-align-left " data-editor="true"><div><div><div>Here's how I've done it so far:</div><div><ul><li>Implement a <a href="https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks" title="preStop" target="_blank" rel="">preStop</a> lifecycle hook that determines whether the pod is responsible for Tableau Server's active repository and issues a failover command if so. This process takes seconds, not minutes.<br></li><li>Implement a <a href="https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity" title="podAntiAffinity" target="_blank" rel="">podAntiAffinity</a> rule to ensure that no two Tableau Server pods get scheduled on the same Kubernetes node simultaneously.<br></li><li>Use the cloud provider's auto-scaling functionality to request additional spot instances whenever we need more.</li><li>Configure the auto-scaling functionality to select from many different compatible instance types, ensuring we don't run into issues finding a machine when we need one. I do this on AWS... I'm not sure how it works for other cloud providers yet.</li></ul></div></div></div></div>
</div><div data-element-id="elm_xNQ4byXM20O53hJKYZyoGQ" data-element-type="text" class="zpelement zpelem-text "><style> [data-element-id="elm_xNQ4byXM20O53hJKYZyoGQ"].zpelem-text { border-radius:1px; } </style><div class="zptext zptext-align-left " data-editor="true"><p><span style="color:inherit;">I've tested this, and I've been pleased with how well it works. I want to do more tests, though. At worst, recovery times are still better than traditional Tableau Server HA failover recovery... but they need to be better since node failures are basically guaranteed when using spot instances.</span><br></p></div>
</div><div data-element-id="elm_i-p-MPGqDfEKNb1_ZPnmCA" data-element-type="text" class="zpelement zpelem-text "><style> [data-element-id="elm_i-p-MPGqDfEKNb1_ZPnmCA"].zpelem-text { border-radius:1px; } </style><div class="zptext zptext-align-left " data-editor="true"><div><div>Oh. There's one other thing, and this one's a doozy...</div></div></div>
</div><div data-element-id="elm_FWR7_HHSWtNl_KJV45olaA" data-element-type="heading" class="zpelement zpelem-heading "><style> [data-element-id="elm_FWR7_HHSWtNl_KJV45olaA"].zpelem-heading { border-radius:1px; } </style><h2
 class="zpheading zpheading-style-none zpheading-align-left " data-editor="true">Tableau. Still. Requires. Static. IP. Addresses.</h2></div>
<div data-element-id="elm_A-PEW1a4pZ8wM8CzZ8DJIw" data-element-type="text" class="zpelement zpelem-text "><style> [data-element-id="elm_A-PEW1a4pZ8wM8CzZ8DJIw"].zpelem-text { border-radius:1px; } </style><div class="zptext zptext-align-left " data-editor="true"><div><div>There. I said it. It doesn't feel good to acknowledge this problem, but we need to face it head-on to succeed with Tableau Server on Kubernetes.</div></div></div>
</div><div data-element-id="elm_F-m2h3r5PBfM9A3UgRmcHQ" data-element-type="text" class="zpelement zpelem-text "><style> [data-element-id="elm_F-m2h3r5PBfM9A3UgRmcHQ"].zpelem-text { border-radius:1px; } </style><div class="zptext zptext-align-left " data-editor="true"><div><div><div>If a pod terminates on one node and starts running on another, it'll get a different IP address. <a href="https://help.tableau.com/current/server-linux/en-us/distrib_requ.htm#networking-and-ports" title="That's a problem for Tableau Server" target="_blank" rel="">That's a problem for Tableau Server</a>, and if not <a href="https://tenor.com/view/it-was-a-pun-monty-python-banter-it-was-a-joke-bad-joke-gif-17007760" title="addressed appropriately" target="_blank" rel="">addressed appropriately</a>, it would result in the pod being unable to take on much of its work. There are a couple of unique ways to get past this, each with its pros and cons:</div></div></div></div>
</div><div data-element-id="elm_njjHXO-qi1XCXZa37NiOBw" data-element-type="text" class="zpelement zpelem-text "><style> [data-element-id="elm_njjHXO-qi1XCXZa37NiOBw"].zpelem-text { border-radius:1px; } </style><div class="zptext zptext-align-left " data-editor="true"><div><div><div>Use static IP addresses like Tableau tells us to:</div><div><ul><li>It's not easy to implement static IPs in Kubernetes, but it can be done.</li><li>We must use a Container Network Interface (CNI) and IP Address Management (IPAM) plugin that supports static IP addresses. I've tested Calico CNI + Calico IPAM on AWS with success.</li><li>It requires a more significant setup before deploying Tableau Server – one does not simply change a CNI and IPAM plugin.</li><li>This model keeps us from using StatefulSets because we need to annotate each pod with a static IP address.</li><li>Because the static IPs follow the pods, this model is compatible with spot instances and more frequent pod termination.</li></ul></div></div></div></div>
</div><div data-element-id="elm_c4dk9lBNXrB3fIcCi1lIpw" data-element-type="text" class="zpelement zpelem-text "><style> [data-element-id="elm_c4dk9lBNXrB3fIcCi1lIpw"].zpelem-text { border-radius:1px; } </style><div class="zptext zptext-align-left " data-editor="true"><div><div><div>Use dynamic IP addresses and instruct Tableau Server to get over it:</div><div><ul><li>Again, it's not easy to do, but it can be done.</li><li>Any time a pod changes IP addresses, we need to reconfigure Tableau Server.</li><li>We don't need a more significant setup before deploying Tableau Server.</li><li>That reconfiguration is done with &quot;tsm pending-changes apply&quot; – even if there are no pending changes. This isn't documented anywhere, but it works.</li><li>It's the &quot;tail wagging the dog,&quot; so to speak. One pod changes IP addresses, and services on every other pod need to be reconfigured because of it.</li><li>Since &quot;tsm pending-changes apply&quot; requires a server restart, there will be application downtime.</li><li>Because of that downtime, it's not ideal for using spot instances or frequent pod termination.</li></ul></div></div></div></div>
</div><div data-element-id="elm_E9UiZWxR9j4BTbPjlMZ9Qg" data-element-type="heading" class="zpelement zpelem-heading "><style> [data-element-id="elm_E9UiZWxR9j4BTbPjlMZ9Qg"].zpelem-heading { border-radius:1px; } </style><h2
 class="zpheading zpheading-style-none zpheading-align-left " data-editor="true">My Thoughts, In Summary</h2></div>
<div data-element-id="elm_0rsokILmizO72_vYcUCs-g" data-element-type="text" class="zpelement zpelem-text "><style> [data-element-id="elm_0rsokILmizO72_vYcUCs-g"].zpelem-text { border-radius:1px; } </style><div class="zptext zptext-align-left " data-editor="true"><div><div>It took quite a bit of engineering and customization, but I can now consistently and successfully deploy Tableau Server on Kubernetes. I've got some more testing regarding spot instances and node failures, but it looks like that approach will work. There's a lot of room to improve the whole experience, but I'm excited about the opportunities that Tableau Server on Kubernetes will bring!</div></div></div>
</div></div></div></div></div></div> ]]></content:encoded><pubDate>Mon, 22 Nov 2021 16:43:57 -0600</pubDate></item><item><title><![CDATA[Monitoring What Matters]]></title><link>https://www.highperformance.tech/blogs/post/monitoring-what-matters</link><description><![CDATA[<img align="left" hspace="5" src="https://www.highperformance.tech/files/img/systematic-approach.jpeg"/>I've monitored many different types of systems over the past 20 years: from modem banks, routers, and authentication systems to storage area networks, ]]></description><content:encoded><![CDATA[<div class="zpcontent-container blogpost-container "><div data-element-id="elm_AbogcubNRV--Pu_xkxfaWQ" data-element-type="section" class="zpsection "><style type="text/css"></style><div class="zpcontainer-fluid zpcontainer"><div data-element-id="elm_4-2cqjXhT9-fGnrjszM6zQ" data-element-type="row" class="zprow zprow-container zpalign-items- zpjustify-content- " data-equal-column=""><style type="text/css"></style><div data-element-id="elm_4PbGJjKHRAGU_CWYi2cPnw" data-element-type="column" class="zpelem-col zpcol-12 zpcol-md-12 zpcol-sm-12 zpalign-self- "><style type="text/css"></style><div data-element-id="elm_WTlwRss9ueRlnDCxN6oI3Q" data-element-type="text" class="zpelement zpelem-text "><style> [data-element-id="elm_WTlwRss9ueRlnDCxN6oI3Q"].zpelem-text { border-radius:1px; } </style><div class="zptext zptext-align-left " data-editor="true"><p><span style="font-size:16px;">I've monitored many different types of systems over the past 20 years: from modem banks, routers, and authentication systems to storage area networks, database clusters and analytics systems. It’s not always easy to know what to monitor, <i>especially</i> in complicated interrelated systems that each produce tons of metrics. App developers and product managers still find it hard to know where to start, even when it comes to monitoring their own creations. In lieu of a better alternative, many of them start where I once did – the four core resources:</span></p><ul><li><span style="font-size:16px;">Processor</span></li><li><span style="font-size:16px;">Memory</span></li><li><span style="font-size:16px;">Network</span></li><li><span style="font-size:16px;">Disk</span></li></ul><p style="font-size:12pt;"><span style="font-size:16px;">It's a reasonable place to start, because you can trace many performance issues back to those resources. But it can take a long time to figure out which metrics matter, and it misses an entire class of interesting issues. Now I prefer a more focused approach, which I’ve found yields better results faster.</span><br></p></div>
</div><div data-element-id="elm_txjMo6UP0AScEk-uFMHnFA" data-element-type="heading" class="zpelement zpelem-heading "><style> [data-element-id="elm_txjMo6UP0AScEk-uFMHnFA"].zpelem-heading { border-radius:1px; } </style><h3
 class="zpheading zpheading-style-none zpheading-align-left " data-editor="true">Start with the End in Mind — What's the Work?</h3></div>
<div data-element-id="elm_YyWiZ5UM1Wnn0S6RDaHXVg" data-element-type="text" class="zpelement zpelem-text "><style> [data-element-id="elm_YyWiZ5UM1Wnn0S6RDaHXVg"].zpelem-text { border-radius:1px; } </style><div class="zptext zptext-align-left " data-editor="true"><p><span style="font-size:16px;">I believe that technology should help people accomplish their goals. It should be in service of others. It should generate useful output. It should <i>just work</i>. So, I prefer to start there, with the end in mind. When analyzing any system, technical or otherwise, I ask myself &quot;what work does this system produce?&quot; Consider it<span> from the perspective of the person who consumes that work, too — that's where the system's value is captured, after all. For example:</span></span></p><ul><li><span style="font-size:16px;">For a visual analytics system such as Tableau or Power BI, the work product might be to render a dashboard, or compile a list of dashboards that a user can select from. It might be to send an email with time-sensitive information in order to give context to an executive who is making a decision.</span></li><li><span style="font-size:16px;">This method works for non-technical systems too. Consider receptionists sitting at an office's front desk. Their work might be to answer incoming phone calls, or to greet office visitors, or to sign for and distribute packages.</span></li></ul><div><span style="font-size:16px;"><br></span></div><div><span style="font-size:16px;">This &quot;work&quot; might be considered the system's purpose, and you would probably be interested in knowing when the quality/quantity of work changes.</span></div></div>
</div><div data-element-id="elm_5r2riq9HHbvklDNL9vgr8w" data-element-type="heading" class="zpelement zpelem-heading "><style> [data-element-id="elm_5r2riq9HHbvklDNL9vgr8w"].zpelem-heading { border-radius:1px; } </style><h3
 class="zpheading zpheading-style-none zpheading-align-left " data-editor="true">Measure What Matters — Work Metrics</h3></div>
<div data-element-id="elm_0lqwvvhDc6i52Xmh9RCrGw" data-element-type="text" class="zpelement zpelem-text "><style> [data-element-id="elm_0lqwvvhDc6i52Xmh9RCrGw"].zpelem-text { border-radius:1px; } </style><div class="zptext zptext-align-left " data-editor="true"><p><span style="font-size:16px;">Once you've named a system’s work product, think about how to measure that output. Common types of measures for work include:</span></p><ul><li><span style="font-size:16px;">Throughput - How much work per unit of time is the system doing?</span></li><li><span style="font-size:16px;">Success/Error Rates - What percentage of work is considered successful during a specific time frame? What percentage of work is considered erroneous?</span></li><li><span style="font-size:16px;">Duration - How long does it take to produce an output or unit of work?</span></li></ul><p><span style="font-size:16px;"><br></span></p><p><span style="font-size:16px;">You should also account for the different dimensions associated with the work, to help spot patterns that might otherwise remain hidden. In many monitoring systems, this additional info can be added to metrics as tags. For instance:</span></p><ul><li><span style="font-size:16px;">Visual Analytics System: Break the above metrics down per node, per end-user, per location, per dashboard, etc... This allows you to view the metrics across different dimensions, and quickly isolate the relevant variables.</span></li><li><span style="font-size:16px;">Receptionists: Capture metrics per receptionist, per location, or per interaction type (greeting a caller on the phone vs. greeting an office visitor).</span></li></ul><p style="font-size:12pt;"><span style="font-size:16px;"><br></span></p><p style="font-size:12pt;"><span style="font-size:16px;">You can infer quite a bit about a system's internal state given only these work metrics. For instance, tracking the &quot;duration&quot; metric would allow you to quickly see when a dashboard takes longer to load than it normally does. This allows you to get in front of problems before they spiral out of control! We can take it one step f</span>urther though, so that we can zoom in on the _cause_ of a problem. How do we zoom in? Resource metrics!</p></div>
</div><div data-element-id="elm_ySf7yF51-if86Sq9RcgIsw" data-element-type="heading" class="zpelement zpelem-heading "><style> [data-element-id="elm_ySf7yF51-if86Sq9RcgIsw"].zpelem-heading { border-radius:1px; } </style><h3
 class="zpheading zpheading-style-none zpheading-align-left " data-editor="true">Record the Resources</h3></div>
<div data-element-id="elm_5gJpNbI5_Ua9YrjCydSycg" data-element-type="text" class="zpelement zpelem-text "><style> [data-element-id="elm_5gJpNbI5_Ua9YrjCydSycg"].zpelem-text { border-radius:1px; } </style><div class="zptext zptext-align-left " data-editor="true"><p><span style="font-size:16px;">Once you know a system's output, list the resources the system uses to generate that output. It's okay if it's an incomplete list at first, because people tend to be surprisingly good about understanding the important ones. For example:</span></p><ul><li><span style="font-size:16px;">Visual Analytics System: In order to render a dashboard for an end user, the system might depend on a database that contains the dashboard definition; the data to populate the dashboard; a connection pool that maintains open connections to those databases, ready to query; a place to cache results; a way to determine which user can see what data; a way to crunch the numbers; a way to send the results to the end user; the list goes on. Notice that I've included a few of the four core resources, but I'm not focused specifically on them — <i>there's so much more to this system</i>!</span></li><li><span style="font-size:16px;">Receptionists: The phone, pen and paper, the desk, the lobby, a pushcart for packages -- all of these are resources that a receptionist might use to do their work.</span></li></ul></div>
</div><div data-element-id="elm_HG0FIixROFXkZGqI7ycg-A" data-element-type="heading" class="zpelement zpelem-heading "><style> [data-element-id="elm_HG0FIixROFXkZGqI7ycg-A"].zpelem-heading { border-radius:1px; } </style><h3
 class="zpheading zpheading-style-none zpheading-align-left " data-editor="true">Measure What Matters — Resource Metrics</h3></div>
<div data-element-id="elm_1tpGEkcXt4G54Lzv4jUW9g" data-element-type="text" class="zpelement zpelem-text "><style> [data-element-id="elm_1tpGEkcXt4G54Lzv4jUW9g"].zpelem-text { border-radius:1px; } </style><div class="zptext zptext-align-left " data-editor="true"><p><span style="font-size:16px;">After you're satisfied with your list of resources, determine which metrics would help you understand how the resource is being used:</span></p><ul><li><span style="font-size:16px;">Utilization - The percent of time the resource is not idle, or how much of a resource's finite capacity is used. For example, the percent of time that connections were querying a database; or the percent of time a phone was in use.</span></li><li><span style="font-size:16px;">Saturation - The amount of work waiting to be serviced by the resource, such as disk queue length or the number of calls in the queue for the receptionist.</span></li><li><span style="font-size:16px;">Errors - The number of errors that might not be visible in the work/output itself, like cache misses or TCP retransmits, or failed call transfers due to invalid/misdialed extensions.</span></li><li><span style="font-size:16px;">Availability - The percent of time the resource is available to respond to requests. Alternatively, the percent of time the resource&nbsp;<i>did</i>&nbsp;respond to requests. A server that can handle multiple requests at once might be non-idle but still available, leading to Utilization metrics &gt; 100%. On the other hand, a receptionist might be tending to a customer in the lobby, and therefore unable to answer an incoming phone call.</span></li></ul><p><span style="font-size:16px;">&nbsp;</span></p><p><span style="font-size:16px;">Don’t forget to tag these with the same kinds of dimensions you tagged your work metrics with!</span></p></div>
</div><div data-element-id="elm_P3zJVNojC2JyQ6ZnHMXeEw" data-element-type="heading" class="zpelement zpelem-heading "><style> [data-element-id="elm_P3zJVNojC2JyQ6ZnHMXeEw"].zpelem-heading { border-radius:1px; } </style><h3
 class="zpheading zpheading-style-none zpheading-align-left " data-editor="true">Getting Resourceful — Zooming In</h3></div>
<div data-element-id="elm_vgKHLNLOpG0AcoP68mfEgQ" data-element-type="text" class="zpelement zpelem-text "><style> [data-element-id="elm_vgKHLNLOpG0AcoP68mfEgQ"].zpelem-text { border-radius:1px; } </style><div class="zptext zptext-align-left " data-editor="true"><p><span style="font-size:16px;">You may have realized that a &quot;resource&quot; could be considered another system altogether. From the perspective of a visual analytics system, a database server is a resource it needs to do its work: its query result is one of the inputs needed to render the dashboard. But from the perspective of the database server, a query result&nbsp;<i>is</i>&nbsp;the work, and it uses different resources to generate that output. When we treat each resource as a system of its own, with its own work/resource metrics, we can zoom in and solve problems faster –&nbsp;<i>especially</i>&nbsp;in complicated systems.<br></span></p><p><span style="font-size:16px;">&nbsp;</span></p><p><span style="font-size:16px;">There are a couple notable differences between this and the traditional &quot;just watch the four core resources&quot; approach:</span></p><ul><li><span style="font-size:16px;">We can account for logical resources, so we can find issues that don't manifest themselves physically. Connection pool exhaustion is a great example of something that can affect end-user experience while being difficult to spot via traditional IT monitoring tools.&nbsp;</span></li><li><span style="font-size:16px;">We may identify resources we don't yet have visibility into. For instance, we might be aware that an application has implemented a cache, but have no way extract its metrics. We might also know that we are using shared equipment on a public cloud environment, but we don't really have a way to know when a “noisy neighbor” is affecting our work. I believe “known unknowns&quot; are better than &quot;unknown unknowns&quot;, because we can still trace issues to their probable cause, and we can focus efforts on improving observability <span style="font-style:italic;">when and where it matters most</span>.</span></li></ul></div>
</div><div data-element-id="elm_FCpPiAWibF707gpqsNOWHA" data-element-type="heading" class="zpelement zpelem-heading "><style> [data-element-id="elm_FCpPiAWibF707gpqsNOWHA"].zpelem-heading { border-radius:1px; } </style><h3
 class="zpheading zpheading-style-none zpheading-align-left " data-editor="true">Enrich with Events</h3></div>
<div data-element-id="elm_I7MFbizEDTMTuL2rHsE3uw" data-element-type="text" class="zpelement zpelem-text "><style> [data-element-id="elm_I7MFbizEDTMTuL2rHsE3uw"].zpelem-text { border-radius:1px; } </style><div class="zptext zptext-align-left " data-editor="true"><p><span style="font-size:16px;">If you've ever called tech support, you might be familiar with the question &quot;have you made any changes recently?&quot; It's a relevant question, because you can normally trace changes in a system's behavior back to a specific event. Recording events as they happen can help you quickly find an issue’s root cause. When you enrich your metrics data with events, you'll also have the added benefit of being able to measure an event's real impact! For any given system, you should list out the kinds of events that might affect the system's behavior. These commonly fall into a handful of categories:</span></p><ul><li><span style="font-size:16px;">Code Changes - updates, upgrades, and installations</span></li><li><span style="font-size:16px;">Configuration Changes - any change in a system's configuration, whether hardware or software</span></li><li><span style="font-size:16px;">Tasks - recurring tasks like backups, and any one-off tasks that administrators might cause</span></li><li><span style="font-size:16px;">Infrastructure Changes - adding or removing RAM, CPU, storage; scaling up/down/out</span></li><li><span style="font-size:16px;">Alerts - any alerts generated by this or other monitoring or management systems</span></li></ul><p><span style="font-size:16px;">&nbsp;</span></p><p><span style="font-size:16px;">After you've listed these events, think about how you can capture and record them as they occur. When capturing an event, you should also tag the events with metadata such as version numbers, task names, timestamps, commit messages, or any other details that might be relevant.</span></p></div>
</div><div data-element-id="elm_c4njm07y2e1N-CwnzSU4gQ" data-element-type="heading" class="zpelement zpelem-heading "><style> [data-element-id="elm_c4njm07y2e1N-CwnzSU4gQ"].zpelem-heading { border-radius:1px; } </style><h3
 class="zpheading zpheading-style-none zpheading-align-left " data-editor="true">Putting It All Together</h3></div>
<div data-element-id="elm_-q7iL7S1_X3uzHk_EZFJzw" data-element-type="text" class="zpelement zpelem-text "><style> [data-element-id="elm_-q7iL7S1_X3uzHk_EZFJzw"].zpelem-text { border-radius:1px; } </style><div class="zptext zptext-align-left " data-editor="true"><p><span style="font-size:16px;">By identifying your relevant work and resource metrics, tags, and events, you’ve taken the most essential step towards having your own powerful monitoring system. Where do you go from here? Here’s what we tend to do:&nbsp;</span></p><ol><li><span style="font-size:16px;">Collect the work metrics, resource metrics, tags, and events data in one place,</span></li><li><span style="font-size:16px;">Add more context by collecting and analyzing your system’s logs as well,</span></li><li><span style="font-size:16px;">Build a high-level dashboard that helps you immediately understand the state of your systems,</span></li><li><span style="font-size:16px;">Build dashboards that drill into the most important work/resource metrics for each piece of the system,</span></li><li><span style="font-size:16px;">Implement alerts to help you address problems while they’re still small,</span></li><li><span style="font-size:16px;">Instrument your custom code to add helpful context to metrics, events, and logs; and lastly,</span></li><li><span style="font-size:16px;">Iterate! As the system changes and improves, so should its monitoring. Solidify any lessons learned by integrating them into your monitoring and alerting system.</span></li></ol><p><span style="font-size:16px;">&nbsp;</span></p><p><span style="font-size:16px;">It might seem like a lot, but when you do it step by step, it feels like a natural progression. And it’s always worth the effort. If we can help you move from one stage to the next, please give us a call or send us an email!</span></p></div>
</div><div data-element-id="elm_KfhX596lcUUlOs-GgPQ03g" data-element-type="row" class="zprow zprow-container zpalign-items-flex-start zpjustify-content-flex-start zpdefault-section zpdefault-section-bg " data-equal-column=""><style type="text/css"> [data-element-id="elm_KfhX596lcUUlOs-GgPQ03g"].zprow{ border-radius:1px; margin-block-start:52px; } </style><div data-element-id="elm_dmEhgFRjkWBXueMLti6F8A" data-element-type="column" class="zpelem-col zpcol-12 zpcol-md-6 zpcol-sm-12 zpalign-self- zpdefault-section zpdefault-section-bg "><style type="text/css"> [data-element-id="elm_dmEhgFRjkWBXueMLti6F8A"].zpelem-col{ border-radius:1px; } </style><div data-element-id="elm_H3pi7Oq-5z5uPI8ZYjp1yw" data-element-type="buttonicon" class="zpelement zpelem-buttonicon "><style> [data-element-id="elm_H3pi7Oq-5z5uPI8ZYjp1yw"].zpelem-buttonicon{ border-radius:1px; } </style><div class="zpbutton-container zpbutton-align-center "><style type="text/css"></style><a class="zpbutton-wrapper zpbutton zpbutton-type-primary zpbutton-size-md zpbutton-style-none zpbutton-icon-align-left " href="tel:9189486777" rel="nofollow" title="Call Us Now"><span class="zpbutton-icon "><svg viewBox="0 0 1792 1792" height="1792" width="1792" xmlns="http://www.w3.org/2000/svg"><path d="M1600 1240q0 27-10 70.5t-21 68.5q-21 50-122 106-94 51-186 51-27 0-53-3.5t-57.5-12.5-47-14.5-55.5-20.5-49-18q-98-35-175-83-127-79-264-216T344 904q-48-77-83-175-3-9-18-49t-20.5-55.5-14.5-47-12.5-57.5-3.5-53q0-92 51-186 56-101 106-122 25-11 68.5-21t70.5-10q14 0 21 3 18 6 53 76 11 19 30 54t35 63.5 31 53.5q3 4 17.5 25t21.5 35.5 7 28.5q0 20-28.5 50t-62 55-62 53-28.5 46q0 9 5 22.5t8.5 20.5 14 24 11.5 19q76 137 174 235t235 174q2 1 19 11.5t24 14 20.5 8.5 22.5 5q18 0 46-28.5t53-62 55-62 50-28.5q14 0 28.5 7t35.5 21.5 25 17.5q25 15 53.5 31t63.5 35 54 30q70 35 76 53 3 7 3 21z"></path></svg></span><span class="zpbutton-content">Call (918) 948-6777</span></a></div>
</div></div><div data-element-id="elm_cx7fcRoeOIRHXR0seXVx_g" data-element-type="column" class="zpelem-col zpcol-12 zpcol-md-6 zpcol-sm-12 zpalign-self- zpdefault-section zpdefault-section-bg "><style type="text/css"> [data-element-id="elm_cx7fcRoeOIRHXR0seXVx_g"].zpelem-col{ border-radius:1px; } </style><div data-element-id="elm_bZ-zM13frhzaOn7gF2idHQ" data-element-type="buttonicon" class="zpelement zpelem-buttonicon "><style> [data-element-id="elm_bZ-zM13frhzaOn7gF2idHQ"].zpelem-buttonicon{ border-radius:1px; } </style><div class="zpbutton-container zpbutton-align-center "><style type="text/css"></style><a class="zpbutton-wrapper zpbutton zpbutton-type-primary zpbutton-size-md zpbutton-style-none zpbutton-icon-align-left " href="mailto:hello@highperformance.tech?subject=Need Some Info" rel="nofollow"><span class="zpbutton-icon "><svg viewBox="0 0 1792 1792" height="1792" width="1792" xmlns="http://www.w3.org/2000/svg"><path d="M1792 710v794q0 66-47 113t-113 47H160q-66 0-113-47T0 1504V710q44 49 101 87 362 246 497 345 57 42 92.5 65.5t94.5 48 110 24.5h2q51 0 110-24.5t94.5-48 92.5-65.5q170-123 498-345 57-39 100-87zm0-294q0 79-49 151t-122 123q-376 261-468 325-10 7-42.5 30.5t-54 38-52 32.5-57.5 27-50 9h-2q-23 0-50-9t-57.5-27-52-32.5-54-38T639 1015q-91-64-262-182.5T172 690q-62-42-117-115.5T0 438q0-78 41.5-130T160 256h1472q65 0 112.5 47t47.5 113z"></path></svg></span><span class="zpbutton-content">Email Us</span></a></div>
</div></div></div></div></div></div></div></div> ]]></content:encoded><pubDate>Tue, 08 Sep 2020 18:28:31 -0500</pubDate></item></channel></rss>