Files
nexus/sreweekly/articles/422/08-graceful-degradation-and-slos.html
2026-09-12 17:23:01 +08:00

468 lines
28 KiB
HTML
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<title>Graceful Degradation and SLOs</title>
<meta name="HandheldFriendly" content="True" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" type="text/css" href="https://blog.relyabilit.ie/assets/css/style.css?v=QmDy475QI3K1foi7" />
<script>
var siteUrl = 'https://blog.relyabilit.ie';
</script>
<script>
var localTheme = localStorage.getItem('attila_theme');
switch (localTheme) {
case 'dark':
document.documentElement.classList.add('theme-dark');
break;
case 'light':
document.documentElement.classList.add('theme-light');
break;
default:
break;
}
</script>
<style>
.theme-dark:root {
--ghost-accent-color: #ff6633;
}
@media (prefers-color-scheme: dark) {
html:not(.theme-light):root {
--ghost-accent-color: #ff6633;
}
}
</style>
<link rel="icon" href="https://storage.ghost.io/c/35/8b/358be036-5da7-4000-beff-df259098e18a/content/images/2021/09/App-Amethyst.ico" type="image/x-icon">
<link rel="canonical" href="https://blog.relyabilit.ie/graceful-degradation-and-slos/">
<meta name="referrer" content="no-referrer-when-downgrade">
<meta property="og:site_name" content="RelyAbility Blog">
<meta property="og:type" content="article">
<meta property="og:title" content="Graceful Degradation and SLOs">
<meta property="og:description" content="What is graceful degradation?
Graceful degradation is the idea that, when you can’t serve the user precisely what they wanted, instead of serving the user an error, you serve them some in-between thing.
The details of this depend a lot on what exactly it is you’re trying">
<meta property="og:url" content="https://blog.relyabilit.ie/graceful-degradation-and-slos/">
<meta property="og:image" content="https://storage.ghost.io/c/35/8b/358be036-5da7-4000-beff-df259098e18a/content/images/size/w1200/2024/04/Cover-her-face.jpg">
<meta property="article:published_time" content="2024-04-09T14:01:22.000Z">
<meta property="article:modified_time" content="2024-04-09T14:01:22.000Z">
<meta property="article:tag" content="SLOs">
<meta property="article:publisher" content="https://www.facebook.com/ghost">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Graceful Degradation and SLOs">
<meta name="twitter:description" content="What is graceful degradation?
Graceful degradation is the idea that, when you can’t serve the user precisely what they wanted, instead of serving the user an error, you serve them some in-between thing.
The details of this depend a lot on what exactly it is you’re trying">
<meta name="twitter:url" content="https://blog.relyabilit.ie/graceful-degradation-and-slos/">
<meta name="twitter:image" content="https://storage.ghost.io/c/35/8b/358be036-5da7-4000-beff-df259098e18a/content/images/size/w1200/2024/04/Cover-her-face.jpg">
<meta name="twitter:label1" content="Written by">
<meta name="twitter:data1" content="Niall Murphy">
<meta name="twitter:label2" content="Filed under">
<meta name="twitter:data2" content="SLOs">
<meta name="twitter:site" content="@niallm">
<meta name="twitter:creator" content="@niallm">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="800">
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"publisher": {
"@type": "Organization",
"name": "RelyAbility Blog",
"url": "https://blog.relyabilit.ie/",
"logo": {
"@type": "ImageObject",
"url": "https://storage.ghost.io/c/35/8b/358be036-5da7-4000-beff-df259098e18a/content/images/2021/09/Logo---Alternate-Layout---Black-2.png"
}
},
"author": {
"@type": "Person",
"name": "Niall Murphy",
"image": {
"@type": "ImageObject",
"url": "https://storage.ghost.io/c/35/8b/358be036-5da7-4000-beff-df259098e18a/content/images/2021/11/recent-headshot--2-.jpg",
"width": 622,
"height": 587
},
"url": "https://blog.relyabilit.ie/author/niallmurphy/",
"sameAs": [
"http://www.relyabilit.ie",
"https://x.com/niallm"
]
},
"headline": "Graceful Degradation and SLOs",
"url": "https://blog.relyabilit.ie/graceful-degradation-and-slos/",
"datePublished": "2024-04-09T14:01:22.000Z",
"dateModified": "2024-04-09T14:01:22.000Z",
"image": {
"@type": "ImageObject",
"url": "https://storage.ghost.io/c/35/8b/358be036-5da7-4000-beff-df259098e18a/content/images/size/w1200/2024/04/Cover-her-face.jpg",
"width": 1200,
"height": 800
},
"keywords": "SLOs",
"description": "What is graceful degradation?\n\nGraceful degradation is the idea that, when you can’t serve the user precisely what they wanted, instead of serving the user an error, you serve them some in-between thing.\n\nThe details of this depend a lot on what exactly it is you’re trying to do. Let’s look at a few examples.\n\nImage server\n\nGenerally speaking, an image server has a very large collection of images stored on disk of some kind, accessed via a filename or unique identifier in the URL, and the aim is",
"mainEntityOfPage": "https://blog.relyabilit.ie/graceful-degradation-and-slos/"
}
</script>
<meta name="generator" content="Ghost 6.64">
<link rel="alternate" type="application/rss+xml" title="RelyAbility Blog" href="https://blog.relyabilit.ie/rss/">
<script defer src="https://cdn.jsdelivr.net/ghost/portal@~2.71/umd/portal.min.js" data-i18n="true" data-ghost="https://blog.relyabilit.ie/" data-key="b25ad4e06b5e0cae96b3fa8e63" data-api="https://niallmurphy.ghost.io/ghost/api/content/" data-locale="en" crossorigin="anonymous"></script><style id="gh-members-styles">.gh-post-upgrade-cta-content,
.gh-post-upgrade-cta {
display: flex;
flex-direction: column;
align-items: center;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
text-align: center;
width: 100%;
color: #ffffff;
font-size: 16px;
}
.gh-post-upgrade-cta-content {
border-radius: 8px;
padding: 40px 4vw;
}
.gh-post-upgrade-cta h2 {
color: #ffffff;
font-size: 28px;
letter-spacing: -0.2px;
margin: 0;
padding: 0;
}
.gh-post-upgrade-cta p {
margin: 20px 0 0;
padding: 0;
}
.gh-post-upgrade-cta small {
font-size: 16px;
letter-spacing: -0.2px;
}
.gh-post-upgrade-cta a {
color: #ffffff;
cursor: pointer;
font-weight: 500;
box-shadow: none;
text-decoration: underline;
}
.gh-post-upgrade-cta a:hover {
color: #ffffff;
opacity: 0.8;
box-shadow: none;
text-decoration: underline;
}
.gh-post-upgrade-cta a.gh-btn {
display: block;
background: #ffffff;
text-decoration: none;
margin: 28px 0 0;
padding: 8px 18px;
border-radius: 4px;
font-size: 16px;
font-weight: 600;
}
.gh-post-upgrade-cta a.gh-btn:hover {
opacity: 0.92;
}</style>
<script defer src="https://cdn.jsdelivr.net/ghost/sodo-search@~1.8/umd/sodo-search.min.js" data-key="b25ad4e06b5e0cae96b3fa8e63" data-styles="https://cdn.jsdelivr.net/ghost/sodo-search@~1.8/umd/main.css" data-sodo-search="https://niallmurphy.ghost.io/" data-locale="en" crossorigin="anonymous"></script>
<link href="https://blog.relyabilit.ie/webmentions/receive/" rel="webmention">
<script defer src="/public/cards.min.js?v=ShRHxgy4po8zN-Wf"></script>
<link rel="stylesheet" type="text/css" href="/public/cards.min.css?v=WwnU9jw5ancNC8Gc">
<script defer src="/public/member-attribution.min.js?v=AKG4hWena9j3yX3I"></script>
<script defer src="/public/ghost-stats.min.js?v=vFcCUf6ZQ0Hyhc8h" data-stringify-payload="false" data-datasource="analytics_events" data-storage="localStorage" data-host="https://blog.relyabilit.ie/.ghost/analytics/api/v1/page_hit" tb_site_uuid="358be036-5da7-4000-beff-df259098e18a" tb_post_uuid="27008ebe-5c49-4d07-906c-504b2d124888" tb_post_type="post" tb_member_uuid="undefined" tb_member_status="undefined" tb_gift_link=""></script><style>:root {--ghost-accent-color: #b18cfe;}</style>
</head>
<body class="post-template tag-slos">
<div class="nav-header">
<nav class="nav-wrapper" aria-label="Main">
<span class="logo">
<a href="https://blog.relyabilit.ie" title="Home"><img src="https://storage.ghost.io/c/35/8b/358be036-5da7-4000-beff-df259098e18a/content/images/2021/09/Logo---Alternate-Layout---Black-2.png" alt="Logo" /></a>
</span>
<ul>
<li class="nav-main-website"><a href="https://www.relyabilit.ie/"><span>Main Website</span></a></li>
<li class="nav-blog"><a href="https://blog.relyabilit.ie/"><span>Blog</span></a></li>
<li class="nav-help"><a href="https://ghost.org/docs/"><span>Help</span></a></li>
</ul>
<ul class="nav-meta">
<li class="nav-twitter">
<a aria-label="Twitter" href="https://x.com/niallm" title="@niallm" target="_blank">
<i class="icon icon-twitter" aria-hidden="true"></i>
<span>@niallm</span>
</a>
</li>
<li class="nav-facebook">
<a aria-label="Facebook" href="https://www.facebook.com/ghost" title="ghost" target="_blank">
<i class="icon icon-facebook" aria-hidden="true"></i>
<span>ghost</span>
</a>
</li>
<li class="nav-search" style="display: none;">
<a title="Search">
<i class="icon icon-search" aria-hidden="true"></i>
<span>Search</span>
</a>
</li>
<!--
<li class="nav-subscribe">
<a href="#/portal">Subscribe</a>
</li>
-->
</ul>
</nav>
<div class="nav-wrapper-control">
<div class="inner">
<a class="nav-menu" role="button"><i class="icon icon-menu" aria-hidden="true"></i>Menu</a>
<a class="nav-search" style="display: none;" title="Search" role="button"><i class="icon icon-search" aria-hidden="true"></i></a>
</div>
</div>
</div>
<div class="nav-close" role="button" aria-label="Close"></div>
<section class="page-wrapper">
<div class="progress-container">
<span class="progress-bar"></span>
</div>
<header class="post-header has-cover ">
<div class="inner">
<span class="post-info">
<span class="post-type">Article</span>
<span class="post-count">SLOs</span>
</span>
<h1 class="post-title">Graceful Degradation and SLOs</h1>
<div class="post-meta">
<div class="post-meta-avatars">
<figure class="post-meta-avatar avatar">
<a href="/author/niallmurphy/" class="author-avatar">
<img class="author-profile-image" src="https://storage.ghost.io/c/35/8b/358be036-5da7-4000-beff-df259098e18a/content/images/2021/11/recent-headshot--2-.jpg" alt="Niall Murphy" />
</a>
</figure>
</div>
<h4 class="post-meta-author"><a href="/author/niallmurphy/">Niall Murphy</a></h4>
<time datetime="09-04-2024">09 Apr 2024</time> &bull; 6 min read
</div>
<div class="post-cover cover">
<img
srcset="https://storage.ghost.io/c/35/8b/358be036-5da7-4000-beff-df259098e18a/content/images/size/w320/2024/04/Cover-her-face.jpg 320w,
https://storage.ghost.io/c/35/8b/358be036-5da7-4000-beff-df259098e18a/content/images/size/w640/2024/04/Cover-her-face.jpg 640w,
https://storage.ghost.io/c/35/8b/358be036-5da7-4000-beff-df259098e18a/content/images/size/w960/2024/04/Cover-her-face.jpg 960w,
https://storage.ghost.io/c/35/8b/358be036-5da7-4000-beff-df259098e18a/content/images/size/w1920/2024/04/Cover-her-face.jpg 1920w"
src="https://storage.ghost.io/c/35/8b/358be036-5da7-4000-beff-df259098e18a/content/images/size/w1920/2024/04/Cover-her-face.jpg"
alt="Graceful Degradation and SLOs" />
</div>
</div>
</header>
<main class="content" role="main">
<article class="post tag-slos">
<div class="inner">
<section class="post-content">
<p><strong>What is graceful degradation?</strong></p><p>Graceful degradation is the idea that, when you can’t serve the user precisely what they wanted, instead of serving the user an error, you serve them some in-between thing.</p><p>The details of this depend&nbsp;<em>a lot</em>&nbsp;on what exactly it is you’re trying to do. Let’s look at a few examples.</p><p><em>Image server</em></p><p>Generally speaking, an image server has a very large collection of images stored on disk of some kind, accessed via a filename or unique identifier in the URL, and the aim is to serve it as quickly as possible. (A very close cousin of blob server - binary large object server - except the blobs are explicitly designated as images). If you can’t access the file (or the filesystem), or in other ways the content isn’t there, picking another image from the filesystem to serve is not tenable, for obvious random roulette reasons.</p><p>There are three possible approaches here.</p><p>Taking advantage of the fact you know it’s supposed to be an image, sometimes you have enough hints from the URL parameters or metadata to provide an image of the right size/format back, with hard-coded 404-equivalent content in the image itself. This is often useful in and of itself, because people know you received the request and processed it, but the image is missing.</p><p>Another technique relies on the situation that sometimes the image content is intended to be&nbsp;<em>versioned</em>&nbsp;- i.e. that version 2 of an image might be an evolution of version 1. In that case, you could return an old version (perhaps with out-of or in-band signalling that it’s old, or what precise version it is).&nbsp;</p><p>Finally, and this is a very generically reusable technique, it’s possible to keep a cache (usually LRU, or equivalent), so that even if you can’t find the file on disk, there’s a decent chance it can be found in a cache you’re keeping elsewhere. In cases like this, the user might not even perceive an error in most cases (unless the cache is unevenly distributed, or you need to update the image), but the fact that this happened at all should be recorded somewhere for all kinds of good operational reasons.</p><p><em>Blog</em></p><p>A blog often has a newsfeed like structure, where there are posts organised by date, each of which can be individually accessed, or some notion of accessing “the latest content for poster X”.</p><p>Here we observe that if you can’t access the content for the specific post the requestor is looking for, there is generally not much value to serving them a different one. Sometimes, rarely, posts have strong mappings to particular topics, and a user could be provided with an opportunity to pick other posts on those topics, if the desired one is not available.</p><p>There is often value in serving them the entire corpus of posts available, even if not ostensibly complete, if that’s what the user has requested - they may be interested in looking at the set in total without a commitment to any particular one of them.</p><p>As above, caching and versioning can often be deployed to good effect, but the key enabling technique is that a newsfeed architecture allows you some leeway to provide non-up-to-date content for some subset of users without them even realising things are broken. (Of course, there are some who will be very much aware…)</p><p><em>Compute provision</em></p><p>The above examples are focused on provided content which is, in some sense, already computed: files on disk, posts in databases, etc. Things for which the request/response mapping is inherently clear, and the work has already been done. Another set of services are&nbsp;<em>dynamic</em>&nbsp;<em>compute provision</em>&nbsp;services, where the answer is not to ship something static out to the network as quickly as possible, but instead to dynamically calculate it. Examples include: ML model questions and answers, fractal image calculation, lambdas/functions-as-a-service, and so on.</p><p>This is a significant change from the above, creating both constraints and opportunities. For (say) fractal image creation, if your local compute function to perform the same fails, it’s possible to retry on-site or even potentially off-site with different enough chances of failure to make it worth trying. If it succeeds, then the graceful degradation has merely come at the expense of latency, though with a large enough delay, some users just abandon the result before it’s provided. Similar opportunities for providing lower-resolution or cached images also exist.</p><p>For ML model answers, it might be that the user has a strong preference for e.g. an answer from GPT-5, but if Claude is available then perhaps that answer would be good enough for them. This is similar to certain high-availability architectures where you send a request to a number of back-ends at once, and the first one to respond wins; in this case you could imagine some matrix of response quality, latency and availability which might go towards selecting the right response, though of course it necessarily leads to wasted compute. Arguably anything other than the top ranked result, should everything work correctly, would be a graceful degradation. Again caching techniques can potentially be useful here, but the wider your query-space is, the higher the risk that the questions won’t display the kind of power-law distribution that makes caching very relevant.</p><p>Sometimes this is combined with static content provision: for example, searching and ranking, where the dynamic computation piece is to return a set of documents ordered in some way which is (generally) dynamically calculated, but the content itself is static. Caching can often work wonders here, but if your index is unavailable, a graceful degradation might be to provide some set of the documents which are known to contain the tokens in question as a consequence of previous cached results.</p><p>In general, dynamic compute provision can extend the variety of substitute results which can be provided, at the expense of other aspects of the user experience.</p><p><strong>SLOs and graceful degradation?</strong></p><p>Now to consider the question of measurement.</p><p>If we consider a simple HTTP request/response service, the classic approach of dividing the total requests by the successful results has some difficulties in the context of graceful degradation. Those difficulties can be summarised as,&nbsp;<em>what do we consider a successful request?</em></p><p>Suppose, in the image server case above, we cannot find the actual image but provide a cached version (maybe at some essentially negligible latency cost). Is that a success? Well, the user got what they want, which is good, but we have an image missing on disk, which is bad. Should we include it as a success or not? If we don’t, we’re potentially missing SLOs where the user population has actually had a totally fine user experience, which is contrary to the point of SLOs; if we&nbsp;<em>do&nbsp;</em>include them as successes, we’re operating in ignorance of key files missing on disk, and at some point when we run out of cache, we could have a nose-diving user-experience.</p><p>Furthermore, suppose we made a determination that we will include cached results as successes, but then we “upgrade” the cache system and now it operates about 50% more slowly. Do we still count them as successes, even though we’re now affecting web performance stats such as LCP (longest-content-paint) and the user experience generally? Or one popular image is much slower while the rest are fine, and so on and so forth.</p><p>You can see where I’m coming from - if there’s a set of criteria that would allow you to include something as a success, you can probably find a situation relevant to graceful degradation where a particular result could be argued to be on either side of the boundary for inclusion/exclusion.</p><p>The key problem to avoid is having different parts of your SLO system/pipeline/organisational architecture make different decisions about the semantics. That way lies perdition. Make the same decisions across your org, or centralise the decision-making, or you’ll be unable to agree on what the user experience is or should be. Bad news.</p><p>In general, though, there are two approaches to handling graceful degradation in SLO calculations.</p><p><strong>Business-as-usual.&nbsp;</strong>The first is as outlined above: to find a set of criteria allowing you to keep some set of the GD responses within the “normal” framework. Precisely how is obviously domain-specific. Handling caching is probably the easiest of these - it could also fit naturally within an overall latency SLO for your systems.</p><p><strong>Separate-by-design.</strong>&nbsp;The second is to absent&nbsp;<em>all</em>&nbsp;GD responses from the usual calculations, and to essentially pretend it’s an entirely separate serving system with its own goals, measurements, and (indeed) even SLOs. Again the details of this are very domain-specific, but the notable thing about GD responses is that typically, by the time you are engaging them, something has already gone wrong - so your time budget for responding is correspondingly constrained.</p><p>Overall, we would argue that, if the range of potential degradations is large and there is a significant infrastructure around them, keeping separate SLOs for those systems probably makes sense, though the SLOs themselves are internally-facing. (Of course, observability for those systems is required to do so.) But it doesn't mean separate SLOs for the user experience - after all, they are declarations of what you want the user experience to be, and the GD&nbsp;mechanisms are what help you to maintain that target.</p>
</section>
<section class="post-footer">
<div class="post-share">
<span class="post-info-label">Share</span>
<a title="Twitter" aria-label="Twitter" class="twitter" href="https://twitter.com/share?text=Graceful Degradation and SLOs&url=https://blog.relyabilit.ie/graceful-degradation-and-slos/" onclick="window.open(this.href, 'twitter-share', 'width=550,height=235');return false;">
<i class="icon icon-twitter" aria-hidden="true"></i>
</a>
<a title="Facebook" aria-label="Facebook" class="facebook" href="https://www.facebook.com/sharer/sharer.php?u=https://blog.relyabilit.ie/graceful-degradation-and-slos/" onclick="window.open(this.href, 'facebook-share','width=580,height=296');return false;">
<i class="icon icon-facebook" aria-hidden="true"></i>
</a>
<a title="LinkedIn" aria-label="LinkedIn" class="linkedin" href="https://www.linkedin.com/shareArticle?mini=true&amp;url=https://blog.relyabilit.ie/graceful-degradation-and-slos//&amp;title=Graceful Degradation and SLOs" onclick="window.open(this.href, 'linkedin-share', 'width=930,height=720');return false;">
<i class="icon icon-linkedin" aria-hidden="true"></i>
</a>
<a title="Email" aria-label="Email" class="email" href="mailto:?subject=Graceful Degradation and SLOs&amp;body=https://blog.relyabilit.ie/graceful-degradation-and-slos/">
<i class="icon icon-mail" aria-hidden="true"></i>
</a>
</div>
<aside class="post-tags">
<span class="post-info-label">Topic</span>
<a href="/tag/slos/">SLOs</a>
</aside>
</section>
<div id="commento"></div>
<script defer
src="https://cdn.commento.io/js/commento.js">
</script>
<aside class="post-nav">
<a class="post-nav-next" href="/rices-theorem-and-software-failures/">
<section class="post-nav-teaser">
<i class="icon icon-arrow-left" aria-label="Next post"></i>
<h2 class="post-nav-title">Rice&#x27;s Theorem and Software Failures</h2>
<p class="post-nav-excerpt">Software cannot be shown to be stable, and so it’s safer to assume it isn’t.&hellip;</p>
<p class="post-nav-meta"><time datetime="10-04-2024">10 Apr 2024</time></p>
</section>
</a>
<a class="post-nav-prev" href="/the-twinslo-proposal/">
<section class="post-nav-teaser">
<i class="icon icon-arrow-right" aria-label="Previous post"></i>
<h2 class="post-nav-title">The TwinSLO Proposal</h2>
<p class="post-nav-excerpt">Comments/Insights/Contributions from
* Niall Murphy
* Toby Burress
* Štěpán Davidovič
* Sal Furino&hellip;</p>
<p class="post-nav-meta"><time datetime="27-03-2024">27 Mar 2024</time></p>
</section>
</a>
<div class="clear"></div>
</aside>
</div>
</article>
</main>
<div class="search-wrapper">
<div class="search">
<form class="search-form">
<input class="search-field" type="text" placeholder="Search …">
<button class="search-button" type="submit">
<i class="icon icon-search" aria-hidden="true"></i>
</button>
</form>
<div class="popular-wrapper">
<h4 class="popular-title">Topics</h4>
<span class="popular-tags post-tags">
<a href='/tag/models/'>models: 6</a>
<a href='/tag/sre-identity/'>sre-identity: 4</a>
<a href='/tag/slos/'>SLOs: 4</a>
<a href='/tag/organizational/'>organizational: 3</a>
<a href='/tag/incidents/'>incidents: 3</a>
<a href='/tag/planning/'>planning: 2</a>
<a href='/tag/future/'>future: 2</a>
<a href='/tag/gatekeeping/'>gatekeeping: 1</a>
<a href='/tag/horizontal/'>horizontal: 1</a>
<a href='/tag/is-not/'>is-not: 1</a>
<a href='/tag/legibility/'>legibility: 1</a>
<a href='/tag/okrs/'>okrs: 1</a>
<a href='/tag/systems-thinking/'>systems-thinking: 1</a>
<a href='/tag/google/'>google: 1</a>
</span>
</div>
<div class="search-result"></div>
</div>
<button class="search-wrapper-close" aria-label="Close"></button>
</div>
<div class="nav-footer">
<nav class="nav-wrapper" aria-label="Footer">
<span class="nav-copy">RelyAbility Blog &copy; 2026 <a class="nav-rss" title="RSS" href="https://blog.relyabilit.ie/rss/" target="_blank"><i class="icon icon-rss" aria-hidden="true"></i></a></span>
<span class="nav-credits">Published with <a href="https://ghost.org">Ghost</a> &bull; Theme <a href="https://github.com/zutrinken/attila">Attila</a> &bull; <a class="menu-item js-theme" href="#" data-system="System theme" data-dark="Dark theme" data-light="Light theme"><span class="theme-icon"></span><span class="theme-text">System theme</span> </a> </span>
</nav>
</div>
</section>
<script type="text/javascript" src="https://blog.relyabilit.ie/assets/js/script.js?v=GM11IFKYlUnuOcir"></script>
<script>
$(document).ready(function () {
var viewport = $(window);
var post = $('.post-content');
// Responsive videos with fitVids
post.fitVids();
// Format code blocks and add line numbers
function codestyling() {
$('pre code').each(function(i, e) {
// Code highlight
hljs.highlightBlock(e);
// No lines for plain text blocks
if (!$(this).hasClass('language-text')) {
var code = $(this);
// Calculate amount of lines
var lines = code.html().split(/\n(?!$)/g).length;
var numbers = [];
if (lines > 1) {
lines++;
}
for (i = 1; i < lines; i++) {
numbers += '<span class="line" aria-hidden="true">' + i + '</span>';
}
code.parent().append('<div class="lines">' + numbers + '</div>');
}
});
}
codestyling();
// Reading progress bar on window top
function readingProgress() {
var postBottom = post.offset().top + post.height();
var viewportHeight = viewport.height();
var progress = 100 - (((postBottom - (viewport.scrollTop() + viewportHeight) + viewportHeight / 3) / (postBottom - viewportHeight + viewportHeight / 3)) * 100);
$('.progress-bar').css('width', progress + '%');
(progress > 100) ? $('.progress-container').addClass('complete'): $('.progress-container').removeClass('complete');
}
readingProgress();
// Trigger reading progress
viewport.on({
'scroll': function() {
readingProgress();
},
'resize': function() {
readingProgress();
},
'orientationchange': function() {
readingProgress();
}
});
});
</script>
<!-- 100% privacy friendly analytics -->
<script async defer src="https://scripts.simpleanalyticscdn.com/latest.js"></script>
<noscript><img src="https://queue.simpleanalyticscdn.com/noscript.gif" alt="" referrerpolicy="no-referrer-when-downgrade" /></noscript>
</body>
</html>