3637 lines
174 KiB
HTML
3637 lines
174 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<meta name="description" content="CFR measures how often changes break production. This article shows how to track it accurately using event data, confidence scoring, and real time dashboards.">
|
||
<meta name="keywords" content="Engineering, CI/CD, Database">
|
||
|
||
<meta property="og:description" content="CFR measures how often changes break production. This article shows how to track it accurately using event data, confidence scoring, and real time dashboards.">
|
||
<meta property="og:site_name" content="dzone.com">
|
||
<meta property="og:title" content="Engineering Resilience with CFR Monitoring">
|
||
<meta property="og:url" content="https://dzone.com/articles/engineering-resilience-data-cfr-monitoring">
|
||
<meta property="og:image" content="https://dz2cdn1.dzone.com/storage/article-thumb/18457521-thumb.jpg">
|
||
<meta property="og:type" content="article">
|
||
|
||
<meta name="twitter:site" content="@DZoneInc">
|
||
<meta name="twitter:image" content="https://dz2cdn1.dzone.com/storage/article-thumb/18457521-thumb.jpg">
|
||
<meta name="twitter:card" content="summary_large_image">
|
||
<meta name="twitter:description" content="CFR measures how often changes break production. This article shows how to track it accurately using event data, confidence scoring, and real time dashboards.">
|
||
<meta name="twitter:title" content="Engineering Resilience with CFR Monitoring">
|
||
|
||
|
||
|
||
<meta name="referrer" content="origin-when-cross-origin">
|
||
<meta name="google-site-verification" content="kndbhxcupfEqWmZclhCpB6vlgOs7QSmx2UHAGGnP2mA">
|
||
<meta name="df-verify" content="df0d76632b4543">
|
||
|
||
<link rel="icon" type="image/x-icon" href="https://dz2cdn1.dzone.com/themes/dz20/images/favicon.png">
|
||
<link rel="image_src" href="https://dz2cdn1.dzone.com/storage/article-thumb/18457521-thumb.jpg">
|
||
<link rel="canonical" href="https://dzone.com/articles/engineering-resilience-data-cfr-monitoring">
|
||
|
||
<title>Engineering Resilience with CFR Monitoring</title>
|
||
|
||
<link rel="preload" href="https://dz2cdn1.dzone.com/themes/dz20/font/fontello.woff?11773374" as="font" type="font/woff" crossorigin="anonymous">
|
||
|
||
<link rel="stylesheet" media="all" href="https://dz2cdn1.dzone.com/themes/dz20/ftl/icons.css">
|
||
<link rel="stylesheet" media="all" href="https://dz2cdn1.dzone.com/themes/dz20/lib/static/bootstrap/bootstrap.min.css">
|
||
<link rel="stylesheet" media="all" href="https://dz2cdn1.dzone.com/themes/dz20/ftl/article/global.css">
|
||
<link rel="stylesheet" media="all" href="https://dz2cdn1.dzone.com/themes/dz20/ftl/header-updated/styles.css">
|
||
<style>
|
||
:root {
|
||
--xs-size: 2px;
|
||
--sm-size: 5px;
|
||
--md-size: 10px;
|
||
--lg-size: 15px;
|
||
--xl-size: 25px;
|
||
--sm-border-size: 8px;
|
||
--sm-font-size: 13px;
|
||
--sm-plus-font-size: 16px;
|
||
--md-font-size: 18px;
|
||
--lg-font-size: 22px;
|
||
--xl-font-size: 26px;
|
||
}
|
||
|
||
/* Display Quick-Access Classes */
|
||
.display-none { display: none; }
|
||
.display-block { display: block; }
|
||
.display-inline { display: inline; }
|
||
.display-inline-block { display: inline-block; }
|
||
|
||
/* Margin Quick-Access Classes */
|
||
.m-xs { margin: var(--xs-size); }
|
||
.m-sm { margin: var(--sm-size); }
|
||
.m-md { margin: var(--md-size); }
|
||
.m-lg { margin: var(--lg-size); }
|
||
.m-xl { margin: var(--xl-size); }
|
||
|
||
.mt-xs, .my-xs { margin-top: var(--xs-size); }
|
||
.mr-xs, .mx-xs { margin-right: var(--xs-size); }
|
||
.mb-xs, .my-xs { margin-bottom: var(--xs-size); }
|
||
.ml-xs, .mx-xs { margin-left: var(--xs-size); }
|
||
|
||
.mt-sm, .my-sm { margin-top: var(--sm-size); }
|
||
.mr-sm, .mx-sm { margin-right: var(--sm-size); }
|
||
.mb-sm, .my-sm { margin-bottom: var(--sm-size); }
|
||
.ml-sm, .mx-sm { margin-left: var(--sm-size); }
|
||
|
||
.mt-md, .my-md { margin-top: var(--md-size); }
|
||
.mr-md, .mx-md { margin-right: var(--md-size); }
|
||
.mb-md, .my-md { margin-bottom: var(--md-size); }
|
||
.ml-md, .mx-md { margin-left: var(--md-size); }
|
||
|
||
.mt-lg, .my-lg { margin-top: var(--lg-size); }
|
||
.mr-lg, .mx-lg { margin-right: var(--lg-size); }
|
||
.mb-lg, .my-lg { margin-bottom: var(--lg-size); }
|
||
.ml-lg, .mx-lg { margin-left: var(--lg-size); }
|
||
|
||
.mt-xl, .my-xl { margin-top: var(--xl-size); }
|
||
.mr-xl, .mx-xl { margin-right: var(--xl-size); }
|
||
.mb-xl, .my-xl { margin-bottom: var(--xl-size); }
|
||
.ml-xl, .mx-xl { margin-left: var(--xl-size); }
|
||
|
||
.ml-auto, .mx-auto { margin-left: auto; }
|
||
.mr-auto, .mx-auto { margin-right: auto; }
|
||
.mt-auto, .my-auto { margin-top: auto; }
|
||
.mb-auto, .my-auto { margin-bottom: auto; }
|
||
|
||
.my-none, .mt-none { margin-top: 0 !important; }
|
||
.mx-none, .mr-none { margin-right: 0 !important; }
|
||
.my-none, .mb-none { margin-bottom: 0 !important; }
|
||
.mx-none, .ml-none { margin-left: 0 !important; }
|
||
|
||
/* Padding Quick-Access Classes */
|
||
.p-xs { padding: var(--xs-size); }
|
||
.p-sm { padding: var(--sm-size); }
|
||
.p-md { padding: var(--md-size); }
|
||
.p-lg { padding: var(--lg-size); }
|
||
.p-xl { padding: var(--xl-size); }
|
||
|
||
.pt-xs, .py-xs { padding-top: var(--xs-size); }
|
||
.pr-xs, .px-xs { padding-right: var(--xs-size); }
|
||
.pb-xs, .py-xs { padding-bottom: var(--xs-size); }
|
||
.pl-xs, .px-xs { padding-left: var(--xs-size); }
|
||
|
||
.pt-sm, .py-sm { padding-top: var(--sm-size); }
|
||
.pr-sm, .px-sm { padding-right: var(--sm-size); }
|
||
.pb-sm, .py-sm { padding-bottom: var(--sm-size); }
|
||
.pl-sm, .px-sm { padding-left: var(--sm-size); }
|
||
|
||
.pt-md, .py-md { padding-top: var(--md-size); }
|
||
.pr-md, .px-md { padding-right: var(--md-size); }
|
||
.pb-md, .py-md { padding-bottom: var(--md-size); }
|
||
.pl-md, .px-md { padding-left: var(--md-size); }
|
||
|
||
.pt-lg, .py-lg { padding-top: var(--lg-size); }
|
||
.pr-lg, .px-lg { padding-right: var(--lg-size); }
|
||
.pb-lg, .py-lg { padding-bottom: var(--lg-size); }
|
||
.pl-lg, .px-lg { padding-left: var(--lg-size); }
|
||
|
||
.pt-xl, .py-xl { padding-top: var(--xl-size); }
|
||
.pr-xl, .px-xl { padding-right: var(--xl-size); }
|
||
.pb-xl, .py-xl { padding-bottom: var(--xl-size); }
|
||
.pl-xl, .px-xl { padding-left: var(--xl-size); }
|
||
|
||
.py-none, .pt-none { padding-top: 0 !important; }
|
||
.px-none, .pr-none { padding-right: 0 !important; }
|
||
.py-none, .pb-none { padding-bottom: 0 !important; }
|
||
.px-none, .pl-none { padding-left: 0 !important; }
|
||
|
||
/* Flex Quick-Access Classes */
|
||
.flex {
|
||
display: flex;
|
||
}
|
||
|
||
.flex-column {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.flex-column-reverse {
|
||
display: flex;
|
||
flex-direction: column-reverse;
|
||
}
|
||
|
||
.flex-row {
|
||
display: flex;
|
||
flex-direction: row;
|
||
}
|
||
|
||
.flex-row-reverse {
|
||
display: flex;
|
||
flex-direction: row-reverse;
|
||
}
|
||
|
||
.flex-wrap {
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.flex-grow {
|
||
flex-grow: 1;
|
||
}
|
||
|
||
.align-center { align-items: center; }
|
||
.align-end { align-items: flex-end; }
|
||
|
||
.content-center {
|
||
align-content: center;
|
||
}
|
||
|
||
.justify-center { justify-content: center; }
|
||
.justify-start { justify-content: flex-start; }
|
||
.justify-end { justify-content: flex-end; }
|
||
.justify-around { justify-content: space-around; }
|
||
.justify-between { justify-content: space-between; }
|
||
.justify-evenly { justify-content: space-evenly; }
|
||
|
||
.gap-sm { gap: var(--sm-size); }
|
||
.gap-md { gap: var(--md-size); }
|
||
.gap-lg { gap: var(--lg-size); }
|
||
.gap-xl { gap: var(--xl-size); }
|
||
|
||
/* Display Quick-Access Classes */
|
||
.inline-block { display: inline-block; }
|
||
|
||
/* Font Quick-Access Classes */
|
||
.font-xl { font-size: var(--xl-font-size); }
|
||
.font-lg { font-size: var(--lg-font-size); }
|
||
.font-md { font-size: var(--md-font-size); }
|
||
.font-sm-plus { font-size: var(--sm-plus-font-size); }
|
||
.font-sm { font-size: var(--sm-font-size); }
|
||
|
||
.font-strike { text-decoration: line-through; }
|
||
.font-underline { text-decoration: underline; }
|
||
.font-overline { text-decoration: overline; }
|
||
|
||
.font-italic { font-style: italic; }
|
||
.font-bold { font-weight: bold; }
|
||
|
||
.font-black { color: #000000; }
|
||
.font-white { color: #ffffff; }
|
||
.font-gray { color: var(--brand-gray-text); }
|
||
.font-danger { color: var(--header-dropdown-button-bg-danger); }
|
||
.font-center { text-align: center; }
|
||
|
||
.no-wrap { white-space: nowrap; }
|
||
|
||
/* Consistent headings between h and non-h tags */
|
||
.heading {
|
||
margin-top: 0;
|
||
margin-bottom: var(--md-size);
|
||
}
|
||
|
||
.heading.font-xl { line-height: var(--xl-font-size); }
|
||
.heading.font-lg { line-height: var(--lg-font-size); }
|
||
.heading.font-md { line-height: var(--md-font-size); }
|
||
.heading.font-sm-plus { line-height: var(--sm-plus-font-size); }
|
||
.heading.font-sm { line-height: var(--sm-font-size); }
|
||
|
||
/* Font Clamp Quick-Access Classes */
|
||
.line-clamp,
|
||
.line-clamp-2,
|
||
.line-clamp-3,
|
||
.line-clamp-4,
|
||
.line-clamp-5 {
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 4;
|
||
-webkit-box-orient: vertical;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.line-clamp-2 { -webkit-line-clamp: 2 !important; }
|
||
.line-clamp-3 { -webkit-line-clamp: 3 !important; }
|
||
.line-clamp-4 { -webkit-line-clamp: 4 !important; }
|
||
.line-clamp-5 { -webkit-line-clamp: 5 !important; }
|
||
|
||
/* User-Select Quick-Access Classes */
|
||
.select-none { user-select: none; }
|
||
|
||
/* Position Quick-Access Classes */
|
||
.absolute { position: absolute; }
|
||
.relative { position: relative; }
|
||
.float-left { float: left; }
|
||
.float-right { float: right; }
|
||
|
||
.pos-top { top: 0; }
|
||
.pos-right { right: 0; }
|
||
.pos-bottom { bottom: 0; }
|
||
.pos-left { left: 0; }
|
||
|
||
.va-top { vertical-align: top; }
|
||
.va-middle { vertical-align: middle; }
|
||
.va-bottom { vertical-align: bottom; }
|
||
|
||
/* Transition Quick-Access Classes */
|
||
.trans-linear-quick {
|
||
transition: all 0.15s linear;
|
||
}
|
||
|
||
/* Background Quick-Access Classes */
|
||
.bg-gray { background: #cccccc; }
|
||
.bg-white { background-color: white; }
|
||
.bg-none { background: none; }
|
||
|
||
/* Border Quick-Access Classes */
|
||
.border-gray { border: 1px solid #cccccc; }
|
||
.tab-pane.border-gray { border: 1px solid #ddd; }
|
||
.no-border, .border-none { border: none !important; }
|
||
|
||
.bt-none { border-top: none !important; }
|
||
.br-none { border-right: none !important; }
|
||
.bb-none { border-bottom: none !important; }
|
||
.bl-none { border-left: none !important; }
|
||
|
||
/* Border Radius Quick-Access Classes */
|
||
|
||
.round-t-sm, .round-tl-sm { border-top-left-radius: var(--sm-border-size); }
|
||
.round-t-sm, .round-tr-sm { border-top-right-radius: var(--sm-border-size); }
|
||
.round-b-sm, .round-bl-sm { border-bottom-left-radius: var(--sm-border-size); }
|
||
.round-b-sm, .round-br-sm { border-bottom-right-radius: var(--sm-border-size); }
|
||
.round-sm { border-radius: var(--sm-border-size); }
|
||
.round-fully { border-radius: 50%; }
|
||
|
||
/* Misc Quick-Access Classes */
|
||
.haikei {
|
||
position: relative;
|
||
background-image: url('https://dz2cdn1.dzone.com/themes/dz20/images/polygon-scatter-haikei-shapes.svg');
|
||
background-repeat: no-repeat;
|
||
background-size: cover;
|
||
}
|
||
|
||
.haikei:before {
|
||
content: '';
|
||
position:absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background-color: var(--color-event-background);
|
||
z-index: -1;
|
||
}
|
||
|
||
.margin-auto-center {
|
||
display: block;
|
||
margin-left: auto;
|
||
margin-right: auto;
|
||
}
|
||
|
||
.flex-auto-center {
|
||
display: flex;
|
||
margin-left: auto;
|
||
margin-right: auto;
|
||
}
|
||
|
||
.mw-1100 { max-width: 1100px; }
|
||
.mw-1300 { max-width: 1300px; }
|
||
.full-width { width: 100%; }
|
||
|
||
.h-separator {
|
||
background-color: #cccccc;
|
||
height: 1px;
|
||
width: 100%;
|
||
margin: 5px auto;
|
||
}
|
||
|
||
.no-spacing {
|
||
margin: 0 !important;
|
||
padding: 0 !important;
|
||
}
|
||
|
||
.inherit-color { color: inherit; }
|
||
|
||
.inherit-decoration { text-decoration: inherit; }
|
||
|
||
/* Table Quick-Access Classes */
|
||
.table-border-gray tr:not(:last-child) {
|
||
border-bottom: 1px solid #cccccc;
|
||
}
|
||
|
||
table.full-width tr td:last-child {
|
||
width:100%;
|
||
}
|
||
|
||
/* Animation Quick-Access Classes */
|
||
@keyframes anim-spin {
|
||
0% { transform: rotate(0deg); }
|
||
100% { transform: rotate(359deg); }
|
||
}
|
||
|
||
.anim-spin {
|
||
display: inline-block;
|
||
animation: anim-spin 2s infinite linear;
|
||
}
|
||
|
||
.hov-pointer { cursor: pointer; }
|
||
.hov-brighten:hover { filter: brightness(1.2); }
|
||
.hov-darken:hover { filter: brightness(0.95); }
|
||
.hov-underline:hover { text-decoration: underline; }
|
||
|
||
/* Small tweaks related to components when using these styles */
|
||
.nav-tabs {
|
||
border-bottom: none;
|
||
}
|
||
.tab-content > .flex-column.active,
|
||
.tab-content > .flex-row.active {
|
||
display: flex;
|
||
}
|
||
|
||
/* Alpine components with x-cloak should not be visible by default until conditionals kick in */
|
||
[x-cloak] { display: none !important; }
|
||
</style>
|
||
<link rel="stylesheet" media="all" href="https://dz2cdn1.dzone.com/themes/dz20/ftl/alpine-components/modal.css">
|
||
<script defer src="https://dz2cdn1.dzone.com/themes/dz20/lib/alpinejs/3.13.2/cdn.min.js"></script>
|
||
<script>
|
||
document.addEventListener('alpine:init', () => {
|
||
Alpine.store('article', {
|
||
id: null,
|
||
engagement: {
|
||
open: false,
|
||
page: 1,
|
||
users: [],
|
||
additional: true,
|
||
loading: false,
|
||
initializing: false,
|
||
unauthorized: false,
|
||
enqueued: null,
|
||
dequeued: null,
|
||
|
||
reset() {
|
||
this.open = false;
|
||
this.page = 1;
|
||
this.users.splice(0);
|
||
this.additional = true;
|
||
this.loading = false;
|
||
this.initializing = false;
|
||
this.enqueued = null;
|
||
this.dequeued = null;
|
||
},
|
||
|
||
enqueue(user) {
|
||
this.dequeued = null;
|
||
this.enqueued = user;
|
||
},
|
||
|
||
dequeue(user) {
|
||
this.enqueued = null;
|
||
this.dequeued = user;
|
||
},
|
||
|
||
add(user) {
|
||
this.users.push(user);
|
||
this.enqueued = null;
|
||
},
|
||
|
||
remove(user) {
|
||
const index = this.users.findIndex(u => u.id === user.id);
|
||
|
||
if (index !== -1) {
|
||
this.users.splice(index, 1);
|
||
}
|
||
|
||
this.dequeued = null;
|
||
},
|
||
|
||
contains(user) {
|
||
return this.users.filter(u => u.id === user.id).length;
|
||
}
|
||
}
|
||
});
|
||
|
||
Alpine.effect(() => {
|
||
const store = Alpine.store('article');
|
||
if (!store.engagement.additional && store.engagement.enqueued) {
|
||
if (!store.engagement.contains(store.engagement.enqueued)) {
|
||
store.engagement.add(store.engagement.enqueued);
|
||
}
|
||
}
|
||
});
|
||
|
||
Alpine.effect(() => {
|
||
const store = Alpine.store('article');
|
||
if (store.engagement.page && store.engagement.dequeued) {
|
||
store.engagement.remove(store.engagement.dequeued);
|
||
}
|
||
})
|
||
});
|
||
</script></head>
|
||
<body x-data>
|
||
<div class="skybox skybox-closeBtn dz_skybox" data-gpt-slot="skybox" id="div-gpt-ad-1435246566686-99"></div>
|
||
<header id="ftl-header">
|
||
<div class="header-top">
|
||
<div class="header-container">
|
||
<div class="pull-left logo-container">
|
||
<div class="logo">
|
||
<a class="inner" href="/">
|
||
<picture>
|
||
<source srcset="https://dz2cdn1.dzone.com/themes/dz20/images/dz_logo_2021_cropped.webp" type="image/webp">
|
||
<source srcset="https://dz2cdn1.dzone.com/themes/dz20/images/dz_logo_2021_cropped.png" type="image/png">
|
||
<img src="https://dz2cdn1.dzone.com/themes/dz20/images/dz_logo_2021_cropped.png" width="181" height="56" alt="DZone">
|
||
</picture>
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="pull-right login-and-search">
|
||
<div id="authenticated-block" class="logged-in">
|
||
<div class="welcome-back">Thanks for visiting DZone today,</div>
|
||
<div id="user-header" class="user-info">
|
||
<button class="user-avatar">
|
||
<span id="header-username" class="username"></span>
|
||
<img id="header-avatar" src="" alt="user avatar">
|
||
</button>
|
||
<div id="user-dropdown" class="browse-user-menu">
|
||
<div class="user-content">
|
||
<a id="header-user-plug" href="#" class="user-description"></a>
|
||
<a id="header-user-edit" href="#" class="edit-profile">Edit Profile</a>
|
||
</div>
|
||
<ul class="user-actions">
|
||
<li id="first-user-action">
|
||
<a id="header-dropdown-manage-email" href="#">Manage Email Subscriptions</a>
|
||
</li>
|
||
<li>
|
||
<a href="/articles/how-to-submit-a-post-to-dzone?utm_source=DZone&utm_medium=user_dropdown&utm_campaign=how_to_post">
|
||
How to Post to DZone
|
||
</a>
|
||
</li>
|
||
<li>
|
||
<a href="/articles/dzones-article-submission-guidelines">
|
||
Article Submission Guidelines
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
<div class="bottom">
|
||
<a href="/users/logout.html" class="sign-out">Sign Out</a>
|
||
<a id="dropdown-view-profile" href="#" class="view-profile">View Profile</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="post-content">
|
||
<button id="post-button" class="post-content--button">
|
||
<span class="post-class">Post</span>
|
||
<i class="icon-plus"></i>
|
||
</button>
|
||
|
||
<div id="post-menu" class="posting-links">
|
||
<div class="posting-links-menu">
|
||
<ul>
|
||
<li>
|
||
<img src="https://dz2cdn1.dzone.com/themes/dz20/images/dz-postarticle.svg" width="15" height="18" style="width: 15px; height: 18px;">
|
||
<a href="/content/article/post.html">Post an Article</a>
|
||
</li>
|
||
<li>
|
||
<a id="drafts-link" href="#">Manage My Drafts</a>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="unauthenticated-block">
|
||
<div class="dz-intro">Over 2 million developers have joined DZone.</div>
|
||
<div class="mobile-invisible sign-in-join">
|
||
<a href="/users/login.html">Log In</a>
|
||
<span class="dz-intro-span">/</span>
|
||
<a href="/static/registration.html">Join</a>
|
||
</div>
|
||
<a class="join-icon" href="/users/login.html" aria-label="User">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide svg-user lucide-user-icon lucide-user">
|
||
<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"></path>
|
||
<circle cx="12" cy="7" r="4"></circle>
|
||
</svg>
|
||
</a>
|
||
</div>
|
||
|
||
<script>
|
||
document.addEventListener('alpine:init', () => {
|
||
Alpine.data('searchDrawer', () => ({
|
||
open: false,
|
||
query: '',
|
||
minCharsMet: false,
|
||
|
||
toggleVisibility() {
|
||
this.open = !this.open;
|
||
if (this.open) {
|
||
this.$nextTick(() => {
|
||
this.$refs.query.focus();
|
||
});
|
||
}
|
||
},
|
||
|
||
updateSearchValue() {
|
||
if (!this.query || !this.query.length || this.query.length < 3) {
|
||
this.minCharsMet = false;
|
||
return;
|
||
}
|
||
this.minCharsMet = true;
|
||
localStorage.setItem('ls.searchValue', this.query);
|
||
},
|
||
|
||
searchSite() {
|
||
if (this.minCharsMet) {
|
||
window.location = '/search';
|
||
}
|
||
}
|
||
}));
|
||
});
|
||
</script>
|
||
|
||
<div class="headerSearch" x-data="searchDrawer()" x-cloak>
|
||
<button class="btn-search dropdown-toggle" x-on:click="toggleVisibility()" aria-label="Search">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide svg-search lucide-search-icon lucide-search">
|
||
<path d="m21 21-4.34-4.34"></path>
|
||
<circle cx="11" cy="11" r="8"></circle>
|
||
</svg>
|
||
</button>
|
||
<template x-teleport=".header-container">
|
||
<div id="search-drawer" x-show="open" x-on:click.outside="open = false" x-transition>
|
||
<div class="search-input">
|
||
<input type="text"
|
||
placeholder="Search"
|
||
autofocus="autofocus"
|
||
x-model="query"
|
||
x-ref="query"
|
||
x-on:input.change="updateSearchValue()"
|
||
x-on:keyup.enter="searchSite()"
|
||
>
|
||
<button x-on:click="searchSite()" x-bind:disabled="!minCharsMet">
|
||
<span>Search</span>
|
||
</button>
|
||
</div>
|
||
<div class="search-footer">
|
||
Please enter at least three characters to search
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
</div> </div>
|
||
<div class="header-bottom">
|
||
<div class="header-bottom-container">
|
||
<a class="resource-link" href="/refcardz">Refcards</a>
|
||
<a class="resource-link" href="/trendreports">Trend Reports</a>
|
||
<div class="resource-link link-menu">
|
||
<a href="/events">Events</a>
|
||
<a href="/events/video-library">Video Library</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<nav class="header-menu-bar">
|
||
<div class="header-menu resource-category">
|
||
<a href="/refcardz">Refcards</a>
|
||
</div>
|
||
<div class="header-menu-separator resource-category-separator"></div>
|
||
<div class="header-menu resource-category">
|
||
<a href="/trendreports">Trend Reports</a>
|
||
</div>
|
||
<div class="header-menu-separator resource-category-separator"></div>
|
||
<div class="header-menu resource-category no-bottom-radius" data-click-activation>
|
||
<p class="menu-label">Events</p>
|
||
<div class="header-menu-items">
|
||
<div class="header-menu-columns">
|
||
<a class="header-menu-item" href="/events">View Events</a>
|
||
<a class="header-menu-item" href="/events/video-library">Video Library</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="header-menu zone-menu" data-click-activation>
|
||
<p class="menu-label">Zones <i class="icon-down-dir icon-closed"></i><i class="icon-right-dir icon-open"></i></p>
|
||
<div class="header-menu-items">
|
||
<div class="header-menu-columns">
|
||
<div class="header-menu-column-item">
|
||
<a class="header-menu-item" href="/culture-and-methodologies">Culture and Methodologies</a>
|
||
<a class="header-menu-item" href="/agile">Agile</a>
|
||
<a class="header-menu-item" href="/career-development">Career Development</a>
|
||
<a class="header-menu-item" href="/methodologies">Methodologies</a>
|
||
<a class="header-menu-item" href="/team-management">Team Management</a>
|
||
</div>
|
||
<div class="header-menu-column-item">
|
||
<a class="header-menu-item" href="/data-engineering">Data Engineering</a>
|
||
<a class="header-menu-item" href="/ai-ml">AI/ML</a>
|
||
<a class="header-menu-item" href="/big-data">Big Data</a>
|
||
<a class="header-menu-item" href="/data">Data</a>
|
||
<a class="header-menu-item" href="/databases">Databases</a>
|
||
<a class="header-menu-item" href="/iot">IoT</a>
|
||
</div>
|
||
<div class="header-menu-column-item">
|
||
<a class="header-menu-item" href="/software-design-and-architecture">Software Design and Architecture</a>
|
||
<a class="header-menu-item" href="/cloud-architecture">Cloud Architecture</a>
|
||
<a class="header-menu-item" href="/containers">Containers</a>
|
||
<a class="header-menu-item" href="/integration">Integration</a>
|
||
<a class="header-menu-item" href="/microservices">Microservices</a>
|
||
<a class="header-menu-item" href="/performance">Performance</a>
|
||
<a class="header-menu-item" href="/security">Security</a>
|
||
</div>
|
||
<div class="header-menu-column-item">
|
||
<a class="header-menu-item" href="/coding">Coding</a>
|
||
<a class="header-menu-item" href="/frameworks">Frameworks</a>
|
||
<a class="header-menu-item" href="/java">Java</a>
|
||
<a class="header-menu-item" href="/javascript">JavaScript</a>
|
||
<a class="header-menu-item" href="/languages">Languages</a>
|
||
<a class="header-menu-item" href="/tools">Tools</a>
|
||
</div>
|
||
<div class="header-menu-column-item">
|
||
<a class="header-menu-item" href="/testing-deployment-and-maintenance">Testing, Deployment, and Maintenance</a>
|
||
<a class="header-menu-item" href="/deployment">Deployment</a>
|
||
<a class="header-menu-item" href="/devops-and-cicd">DevOps and CI/CD</a>
|
||
<a class="header-menu-item" href="/maintenance">Maintenance</a>
|
||
<a class="header-menu-item" href="/monitoring-and-observability">Monitoring and Observability</a>
|
||
<a class="header-menu-item" href="/testing-tools-and-frameworks">Testing, Tools, and Frameworks</a>
|
||
</div>
|
||
<div class="header-menu-column-item sponsored">
|
||
<a class="header-menu-item" href="javascript:void(0)">Partner Zones</a>
|
||
<a class="header-menu-item" href="/hubs/build-ai-agents-that-are-ready-for-production/">Build AI Agents That Are Ready for Production</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="header-menu parent-category" tabindex="1">
|
||
<a href="/culture-and-methodologies">Culture and Methodologies</a>
|
||
<div class="header-menu-items">
|
||
<a class="header-menu-item" href="/agile">Agile</a>
|
||
<a class="header-menu-item" href="/career-development">Career Development</a>
|
||
<a class="header-menu-item" href="/methodologies">Methodologies</a>
|
||
<a class="header-menu-item" href="/team-management">Team Management</a>
|
||
</div>
|
||
</div>
|
||
<div class="header-menu-separator parent-category-separator"></div>
|
||
<div class="header-menu parent-category" tabindex="2">
|
||
<a href="/data-engineering">Data Engineering</a>
|
||
<div class="header-menu-items">
|
||
<a class="header-menu-item" href="/ai-ml">AI/ML</a>
|
||
<a class="header-menu-item" href="/big-data">Big Data</a>
|
||
<a class="header-menu-item" href="/data">Data</a>
|
||
<a class="header-menu-item" href="/databases">Databases</a>
|
||
<a class="header-menu-item" href="/iot">IoT</a>
|
||
</div>
|
||
</div>
|
||
<div class="header-menu-separator parent-category-separator"></div>
|
||
<div class="header-menu parent-category" tabindex="3">
|
||
<a href="/software-design-and-architecture">Software Design and Architecture</a>
|
||
<div class="header-menu-items">
|
||
<a class="header-menu-item" href="/cloud-architecture">Cloud Architecture</a>
|
||
<a class="header-menu-item" href="/containers">Containers</a>
|
||
<a class="header-menu-item" href="/integration">Integration</a>
|
||
<a class="header-menu-item" href="/microservices">Microservices</a>
|
||
<a class="header-menu-item" href="/performance">Performance</a>
|
||
<a class="header-menu-item" href="/security">Security</a>
|
||
</div>
|
||
</div>
|
||
<div class="header-menu-separator parent-category-separator"></div>
|
||
<div class="header-menu parent-category" tabindex="4">
|
||
<a href="/coding">Coding</a>
|
||
<div class="header-menu-items">
|
||
<a class="header-menu-item" href="/frameworks">Frameworks</a>
|
||
<a class="header-menu-item" href="/java">Java</a>
|
||
<a class="header-menu-item" href="/javascript">JavaScript</a>
|
||
<a class="header-menu-item" href="/languages">Languages</a>
|
||
<a class="header-menu-item" href="/tools">Tools</a>
|
||
</div>
|
||
</div>
|
||
<div class="header-menu-separator parent-category-separator"></div>
|
||
<div class="header-menu parent-category" tabindex="5">
|
||
<a href="/testing-deployment-and-maintenance">Testing, Deployment, and Maintenance</a>
|
||
<div class="header-menu-items">
|
||
<a class="header-menu-item" href="/deployment">Deployment</a>
|
||
<a class="header-menu-item" href="/devops-and-cicd">DevOps and CI/CD</a>
|
||
<a class="header-menu-item" href="/maintenance">Maintenance</a>
|
||
<a class="header-menu-item" href="/monitoring-and-observability">Monitoring and Observability</a>
|
||
<a class="header-menu-item" href="/testing-tools-and-frameworks">Testing, Tools, and Frameworks</a>
|
||
</div>
|
||
</div>
|
||
<div class="header-menu-separator parent-category-separator"></div>
|
||
<div class="header-menu parent-category sponsored" tabindex="6">
|
||
<a href="javascript:void(0)">Partner Zones</a>
|
||
<div class="header-menu-items sponsored">
|
||
<a class="header-menu-item" href="/hubs/build-ai-agents-that-are-ready-for-production/">Build AI Agents That Are Ready for Production</a>
|
||
</div>
|
||
</div>
|
||
<div class="header-menu-separator parent-category-separator"></div>
|
||
</nav>
|
||
</header>
|
||
|
||
<script>
|
||
const csrf = {
|
||
parameter: 'TH_CSRF',
|
||
header: 'X-TH-CSRF',
|
||
token: '-2198186140590351378'
|
||
}; // set csrf for auth-status script
|
||
</script>
|
||
<script>
|
||
addEventListener('DOMContentLoaded', function() {
|
||
handleRedirects();
|
||
handleMenus();
|
||
handleMobileMenuHeights();
|
||
handleGotoLinks();
|
||
});
|
||
|
||
function isHidden(element) {
|
||
try {
|
||
return window.getComputedStyle(element).display === 'none';
|
||
} catch (_) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
function getLink(element) {
|
||
if (element.hasAttribute('data-goto')) {
|
||
return element.getAttribute('data-goto');
|
||
}
|
||
return element.href;
|
||
}
|
||
|
||
function isLeftClick(event) {
|
||
if (event.altKey || event.shiftKey) {
|
||
return false;
|
||
} else if ('buttons' in event || 'which' in event) {
|
||
return event.buttons === 1 || event.which === 1;
|
||
} else {
|
||
return (event.button === 1 || (event.type === 'click'));
|
||
}
|
||
}
|
||
|
||
function handleRedirects() {
|
||
const redirections = [...document.querySelectorAll('[data-activate-menu]'), ...document.querySelectorAll('[data-click-target]')];
|
||
|
||
redirections.forEach(function(element) {
|
||
const menuSelector = element.getAttribute('data-activate-menu') || element.getAttribute('data-click-target');
|
||
const redirectingElement = document.querySelector(menuSelector);
|
||
if (redirectingElement) {
|
||
element.style.cursor = 'pointer';
|
||
const redirect = function(e) {
|
||
if (redirectingElement.hasAttribute('href') || redirectingElement.hasAttribute('data-goto')) {
|
||
if (redirectingElement.hasAttribute('data-new-window') || e.ctrlKey || e.metaKey) {
|
||
window.open(getLink(redirectingElement), '_blank');
|
||
} else {
|
||
window.open(getLink(redirectingElement), '_self');
|
||
}
|
||
} else {
|
||
const evt = new e.constructor(e.type, e);
|
||
redirectingElement.dispatchEvent(evt);
|
||
}
|
||
};
|
||
element.addEventListener('mouseup', redirect);
|
||
element.addEventListener('mousedown', (e) => e.preventDefault());
|
||
element.addEventListener('click', (e) => e.preventDefault());
|
||
}
|
||
});
|
||
}
|
||
|
||
function handleMenus() {
|
||
const menuElements = [
|
||
...document.querySelectorAll('.header-menu > a'),
|
||
...document.querySelectorAll('.header-menu > p.menu-label')
|
||
];
|
||
|
||
let scrollYMemory = -1;
|
||
|
||
function scrollToMemory() {
|
||
if (scrollYMemory !== -1) {
|
||
setTimeout(function() {
|
||
window.scrollTo(0, scrollYMemory);
|
||
scrollYMemory = -1;
|
||
}, 10);
|
||
}
|
||
}
|
||
|
||
function hideMenus() {
|
||
// unfocus menus & items, and set the menus to non-visible.
|
||
menuElements.forEach(function (element) {
|
||
element.blur();
|
||
element.parentElement.blur();
|
||
element.parentElement.classList.remove('menu-opened');
|
||
const menuItems = element.parentElement.querySelector('.header-menu-items');
|
||
if (menuItems) {
|
||
menuItems.style.display = 'none';
|
||
}
|
||
});
|
||
const wasHidden = document.body.style.overflowY === 'hidden';
|
||
document.body.style.overflowY = 'auto';
|
||
if (wasHidden) {
|
||
scrollToMemory();
|
||
}
|
||
}
|
||
|
||
function isEventOutsideMenu(e) {
|
||
return e.target.closest && !e.target.closest('.header-menu') && !e.target.closest('[data-activate-menu]');
|
||
}
|
||
|
||
// Handle mobile menu toggling
|
||
menuElements.forEach(function(element) {
|
||
const menu = element.parentElement;
|
||
const headerItems = menu.querySelector('.header-menu-items');
|
||
const menuEntries = headerItems ? headerItems.querySelectorAll('.header-menu-item') : [];
|
||
|
||
const focus = function() {
|
||
menu.focus();
|
||
menu.classList.add('menu-opened');
|
||
if (headerItems) {
|
||
headerItems.style.display = 'block';
|
||
}
|
||
if (menu.classList.contains('zone-menu')) {
|
||
scrollYMemory = window.scrollY;
|
||
document.body.style.overflowY = 'hidden';
|
||
} else {
|
||
scrollYMemory = -1;
|
||
}
|
||
};
|
||
|
||
const unfocus = function() {
|
||
menu.blur();
|
||
menu.classList.remove('menu-opened');
|
||
if (headerItems) {
|
||
headerItems.style.display = 'none';
|
||
}
|
||
const wasHidden = document.body.style.overflowY === 'hidden';
|
||
document.body.style.overflowY = 'auto';
|
||
if (wasHidden) {
|
||
scrollToMemory();
|
||
}
|
||
};
|
||
|
||
const toggleMenuVisibility = function(e) {
|
||
if ((e.type === 'click' || e.type === 'mouseup') && !isLeftClick(e)) {
|
||
e.preventDefault();
|
||
return;
|
||
}
|
||
const hidden = isHidden(headerItems);
|
||
if (menu.hasAttribute('data-click-activation')) { // handle click activated toggling
|
||
if (hidden) {
|
||
hideMenus(); // hide other open menus first
|
||
focus();
|
||
} else {
|
||
unfocus();
|
||
}
|
||
e.preventDefault();
|
||
} else if (hidden) {
|
||
hideMenus(); // hide other open menus first
|
||
focus();
|
||
e.preventDefault(); // prevent 'click' event from firing when menu is hidden
|
||
}
|
||
};
|
||
|
||
element.addEventListener('touchend', toggleMenuVisibility);
|
||
element.addEventListener('mouseup', toggleMenuVisibility);
|
||
|
||
// Add hover events to non-click-activated menus, even though CSS should cover it.
|
||
if (!menu.hasAttribute('data-click-activation')) {
|
||
menu.addEventListener('mouseover', function () {
|
||
hideMenus(); // hide other open menus first
|
||
focus();
|
||
});
|
||
|
||
menu.addEventListener('mouseout', function (e) {
|
||
if (isEventOutsideMenu(e)) {
|
||
unfocus();
|
||
}
|
||
});
|
||
}
|
||
|
||
// Hide menu when child is clicked
|
||
menuEntries.forEach(function(menuEntry) {
|
||
const linkToItem = function(e) {
|
||
if (e.type === 'mousedown' || e.type === 'click') {
|
||
e.preventDefault();
|
||
return;
|
||
}
|
||
if (e.type === 'mouseup' && !isLeftClick(e)) {
|
||
e.preventDefault();
|
||
return;
|
||
}
|
||
window.open(getLink(menuEntry), (e.ctrlKey || e.metaKey) ? '_blank' : (menuEntry.target || '_self'));
|
||
unfocus();
|
||
e.preventDefault();
|
||
};
|
||
|
||
const linkToMobileItem = function(e) {
|
||
if (e.type === 'touchstart') {
|
||
menuEntry.setAttribute('data-touchmove', false);
|
||
} else if (e.type === 'touchmove') {
|
||
menuEntry.setAttribute('data-touchmove', true);
|
||
} else if (e.type === 'touchend' && (!menuEntry.hasAttribute('data-touchmove') || menuEntry.getAttribute('data-touchmove').toLowerCase() === 'false')) {
|
||
window.open(getLink(menuEntry), (e.ctrlKey || e.metaKey) ? '_blank' : (menuEntry.target || '_self'));
|
||
unfocus();
|
||
e.preventDefault();
|
||
}
|
||
};
|
||
|
||
menuEntry.addEventListener('mousedown', linkToItem);
|
||
menuEntry.addEventListener('mouseup', linkToItem);
|
||
menuEntry.addEventListener('click', linkToItem);
|
||
menuEntry.addEventListener('touchstart', linkToMobileItem);
|
||
menuEntry.addEventListener('touchmove', linkToMobileItem);
|
||
menuEntry.addEventListener('touchend', linkToMobileItem);
|
||
});
|
||
});
|
||
|
||
function hideIfNonMenuBounds(e) {
|
||
if (isEventOutsideMenu(e)) {
|
||
hideMenus();
|
||
}
|
||
}
|
||
|
||
addEventListener('mousemove', hideIfNonMenuBounds);
|
||
addEventListener('touchend', hideIfNonMenuBounds);
|
||
addEventListener('mouseup', hideIfNonMenuBounds);
|
||
addEventListener('mousedown', hideIfNonMenuBounds);
|
||
addEventListener('click', hideIfNonMenuBounds);
|
||
}
|
||
|
||
function handleMobileMenuHeights() {
|
||
function setAppHeight() {
|
||
document.documentElement.style.setProperty('--app-height', window.innerHeight + 'px');
|
||
}
|
||
|
||
addEventListener('resize', function() {
|
||
setAppHeight();
|
||
});
|
||
|
||
setAppHeight();
|
||
}
|
||
|
||
function handleGotoLinks() {
|
||
// Add anchor mimicking to elements with data-goto attributes
|
||
// This addresses SEO concerns of linking to noindex pages by allowing JS to handle the URL
|
||
const anchorElements = document.querySelectorAll('*[data-goto]');
|
||
anchorElements.forEach((anchorElement) => {
|
||
anchorElement.addEventListener('mouseover', () => {
|
||
anchorElement.style.cursor = 'pointer';
|
||
anchorElement.style.textDecoration = 'underline';
|
||
});
|
||
anchorElement.addEventListener('mouseout', () => {
|
||
anchorElement.style.cursor = 'unset';
|
||
anchorElement.style.textDecoration = 'unset';
|
||
});
|
||
anchorElement.addEventListener('mouseup', (e) => {
|
||
e.preventDefault();
|
||
});
|
||
anchorElement.addEventListener('mousedown', (e) => {
|
||
e.preventDefault();
|
||
});
|
||
anchorElement.addEventListener('click', (e) => {
|
||
e.preventDefault();
|
||
const anchorHref = anchorElement.getAttribute('data-goto');
|
||
if (anchorElement.hasAttribute('data-new-window') || e.ctrlKey || e.metaKey) {
|
||
window.open(anchorHref, '_blank');
|
||
} else {
|
||
window.open(anchorHref, '_self');
|
||
}
|
||
});
|
||
});
|
||
}
|
||
</script><script>
|
||
const authenticatedBlock = document.querySelector('#authenticated-block');
|
||
const unauthenticatedBlock = document.querySelector('#unauthenticated-block');
|
||
|
||
let authenticated = {
|
||
isAuthenticated: false,
|
||
isAdmin: false,
|
||
user: {
|
||
id: null,
|
||
name: null,
|
||
url: null,
|
||
profileImage: null,
|
||
}
|
||
};
|
||
|
||
fetch('/services/internal/data/articles-getAuthenticationStatus', {
|
||
headers: {
|
||
'Accept': 'application/json'
|
||
}
|
||
})
|
||
.then(function (result) {
|
||
return result.json()
|
||
})
|
||
.then(function (result) {
|
||
const res = result.result.data
|
||
if (!res.authenticated) {
|
||
unauthenticatedBlock.classList.add('shown')
|
||
} else {
|
||
authenticated.user.id = res.id;
|
||
authenticated.user.name = res.realName;
|
||
authenticated.user.url = res.profileUrl;
|
||
authenticated.user.profileImage = res.avatar;
|
||
authenticated.user.jobTitle = res.jobTitle;
|
||
authenticated.user.companyName = res.companyName;
|
||
|
||
bindProps('#header-username', null, null, (res.firstName || res.username), null)
|
||
bindProps('#header-avatar', null, res.avatar, null, null)
|
||
bindProps('#header-user-plug', res.profileUrl, null, res.realName, null)
|
||
bindProps('#header-user-edit', '/users/' + res.id + '/edit.html', null, null, null)
|
||
bindProps('#header-dropdown-manage-email', '/newsletters/' + res.id + '/manage.html', null, null, null)
|
||
bindProps('#dropdown-view-profile', res.profileUrl, null, null, null)
|
||
bindProps('#drafts-link', '/users/' + res.id + '/drafts.html', null, null, null)
|
||
|
||
if (res.isAdmin) {
|
||
// Construct backwards so the #after call places elements in the correct order
|
||
const firstUserAction = document.querySelector('#first-user-action')
|
||
const adminConsoleItem = document.createElement('li')
|
||
const adminConsoleLink = createLink('/dzone/staff/index.html', 'Admin Console')
|
||
adminConsoleItem.appendChild(adminConsoleLink)
|
||
firstUserAction.after(adminConsoleItem)
|
||
|
||
const moderationItem = document.createElement('li')
|
||
const moderationLink = createLink('/moderation/list.html', 'Moderation')
|
||
moderationItem.appendChild(moderationLink)
|
||
firstUserAction.after(moderationItem)
|
||
|
||
const bountyModerationItem = document.createElement('li')
|
||
const bountyModerationLink = createLink('/moderation/bounties', 'Bounty Moderation')
|
||
bountyModerationItem.appendChild(bountyModerationLink)
|
||
firstUserAction.after(bountyModerationItem)
|
||
}
|
||
|
||
authenticated.isAuthenticated = res.authenticated;
|
||
authenticated.isAdmin = res.isAdmin;
|
||
|
||
authenticatedBlock.classList.add('shown')
|
||
}
|
||
}).catch(function (result) {
|
||
console.error(result)
|
||
})
|
||
|
||
/**
|
||
* Binds different properties to the selected element.
|
||
*
|
||
* @param selector - Selector to select the element
|
||
* @param href - href attribute value
|
||
* @param src - src attribute value
|
||
* @param innerHTML - innerHTML property value
|
||
* @param innerText - innerText property value
|
||
*/
|
||
function bindProps(selector, href, src, innerHTML, innerText) {
|
||
const element = document.querySelector(selector)
|
||
if (element) {
|
||
if (href) element.href = href
|
||
if (src) element.src = src
|
||
if (innerHTML) element.innerHTML = innerHTML
|
||
if (innerText) element.innerText = innerText
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Creates a new link element.
|
||
*
|
||
* @param href - href attribute value
|
||
* @param innerText - innerText property value
|
||
* @returns {HTMLAnchorElement} The generated link element
|
||
*/
|
||
function createLink(href, innerText) {
|
||
const link = document.createElement('a')
|
||
link.href = href
|
||
link.innerText = innerText
|
||
|
||
return link
|
||
}
|
||
</script><script>
|
||
const userHeader = document.querySelector('#user-header')
|
||
const userDropdown = document.querySelector('#user-dropdown')
|
||
const postDropdown = document.querySelector('#post-button')
|
||
const postMenu = document.querySelector('#post-menu')
|
||
|
||
let userDropdownOpen = false
|
||
let postDropdownOpen = false
|
||
|
||
document.addEventListener('click', function(event) {
|
||
if (postDropdown && postDropdown.contains(event.target)) {
|
||
setUserDropdown(false)
|
||
setPostDropdown(!postDropdownOpen)
|
||
} else if (userHeader && userHeader.contains(event.target)) {
|
||
setPostDropdown(false)
|
||
setUserDropdown(!userDropdownOpen)
|
||
} else {
|
||
setUserDropdown(false)
|
||
setPostDropdown(false)
|
||
}
|
||
})
|
||
|
||
function setUserDropdown(value) {
|
||
userDropdownOpen = value
|
||
if (userDropdownOpen) {
|
||
if (userDropdown) {
|
||
userDropdown.classList.add('open')
|
||
}
|
||
} else {
|
||
if (userDropdown) {
|
||
userDropdown.classList.remove('open')
|
||
}
|
||
}
|
||
}
|
||
|
||
function setPostDropdown(value) {
|
||
postDropdownOpen = value
|
||
if (postDropdownOpen) {
|
||
if (postMenu) {
|
||
postMenu.classList.add('open')
|
||
}
|
||
} else {
|
||
if (postMenu) {
|
||
postMenu.classList.remove('open')
|
||
}
|
||
}
|
||
}
|
||
</script><script>
|
||
document.addEventListener("alpine:init", () => {
|
||
Alpine.store('global', {
|
||
executeHttp(path, body, method) {
|
||
const options = {
|
||
method: method,
|
||
headers: {
|
||
[csrf.header]: csrf.token
|
||
}
|
||
};
|
||
|
||
if (method !== 'GET') {
|
||
options.body = JSON.stringify(body);
|
||
options.headers = {
|
||
...options.headers,
|
||
'Content-Type': 'application/json; charset=UTF-8'
|
||
};
|
||
}
|
||
|
||
return new Promise((resolve, reject) => {
|
||
fetch(path, options)
|
||
.then(res => {
|
||
if (!res.ok) {
|
||
reject(res);
|
||
} else {
|
||
resolve(res);
|
||
}
|
||
})
|
||
.catch(err => reject(err));
|
||
});
|
||
},
|
||
getFromService(path) {
|
||
return this.executeHttp(path, null, 'GET');
|
||
},
|
||
postToService(path, body = {}) {
|
||
return this.executeHttp(path, body, 'POST');
|
||
},
|
||
putToService(path, body = {}) {
|
||
return this.executeHttp(path, body, 'PUT');
|
||
}
|
||
});
|
||
});
|
||
</script>
|
||
<link rel="stylesheet" media="all" href="https://dz2cdn1.dzone.com/themes/dz20/ftl/colors.css">
|
||
<link rel="stylesheet" media="all" href="https://dz2cdn1.dzone.com/themes/dz20/ftl/article/styles.css">
|
||
|
||
|
||
|
||
|
||
|
||
<div id="body-container">
|
||
<div id="announcement-container-outer">
|
||
<div id="announcement-previous">
|
||
<i class="icon-angle-left"></i>
|
||
</div>
|
||
<div id="announcement-next">
|
||
<i class="icon-angle-right"></i>
|
||
</div>
|
||
<div id="announcement-container">
|
||
<div class="announcement announcement-count-1"
|
||
data-position="1">
|
||
<div class="body"><p><strong>Could your team report a vulnerability within 24 hours? </strong>Find out on September 23.</p></div>
|
||
<div class="spacer"></div>
|
||
<a href="https://cvent.me/O32zXR?utm_source=Announcements&utm_medium=DzoneWeb&utm_campaign=QtGroup-0923" target="_blank">
|
||
<button>Assess Your CRA Readiness</button>
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<script type="text/javascript" async>
|
||
(function() {
|
||
let announcementPosition = 1;
|
||
let minAnnouncementPosition = -1;
|
||
let maxAnnouncementPosition = -1;
|
||
|
||
const announcementPrevBtn = document.querySelector('#announcement-previous');
|
||
const announcementNextBtn = document.querySelector('#announcement-next');
|
||
|
||
function withAnnouncements(callback) {
|
||
const announcements = document.querySelectorAll('#announcement-container .announcement');
|
||
for (let announcement of announcements) {
|
||
callback(announcement);
|
||
}
|
||
}
|
||
|
||
function initAnnouncementVars() {
|
||
document.querySelector(':root').style.setProperty('--mobile-announcement-separator-width', '1px');
|
||
withAnnouncements(function(announcement) {
|
||
const pos = parseInt(announcement.getAttribute('data-position'));
|
||
minAnnouncementPosition = minAnnouncementPosition === -1 ? pos : Math.min(minAnnouncementPosition, pos);
|
||
maxAnnouncementPosition = maxAnnouncementPosition === -1 ? pos : Math.max(maxAnnouncementPosition, pos);
|
||
});
|
||
|
||
if (document.querySelector('.announcementBarContainer')) {
|
||
document.querySelector(':root').style.setProperty('--body-top-padding', '0');
|
||
}
|
||
|
||
if (maxAnnouncementPosition <= 0) {
|
||
document.querySelector(':root').style.setProperty('--body-top-padding', '0');
|
||
}
|
||
|
||
sizeToFullWhenOneEntryOnMobile();
|
||
}
|
||
|
||
function setAnnouncementPosition(position) {
|
||
if (window.outerWidth >= 890) {
|
||
return; // we do not need to change the position, since we can display everything on desktop.
|
||
}
|
||
|
||
// Make the announcement cyclical
|
||
if (minAnnouncementPosition !== -1 && maxAnnouncementPosition !== -1) {
|
||
if (position > maxAnnouncementPosition) {
|
||
position = minAnnouncementPosition; // overflow to the first announcement
|
||
} else if (position < minAnnouncementPosition) {
|
||
position = maxAnnouncementPosition; // underflow to the last announcement
|
||
}
|
||
announcementPosition = position;
|
||
}
|
||
|
||
const shownAnnouncements = [];
|
||
|
||
let reverseFlex = false; // should only be true when the first and last items are showing
|
||
|
||
// Now that the position is valid, apply the transforms
|
||
withAnnouncements(function(announcement) {
|
||
const pos = parseInt(announcement.getAttribute('data-position'));
|
||
const isWrapped = position === maxAnnouncementPosition && pos === minAnnouncementPosition; // showing first + last at same time
|
||
const doesNextQualify = window.outerWidth >= 500 && (pos === position + 1 || isWrapped);
|
||
|
||
if (pos === position || doesNextQualify) {
|
||
shownAnnouncements.push(announcement);
|
||
} else {
|
||
announcement.style.display = 'none';
|
||
}
|
||
|
||
announcement.style.opacity = 0.0;
|
||
|
||
if (isWrapped) {
|
||
reverseFlex = true;
|
||
}
|
||
});
|
||
|
||
for (let announcement of shownAnnouncements) {
|
||
announcement.style.display = 'flex';
|
||
announcement.style.opacity = 1.0;
|
||
}
|
||
|
||
const announcementContainer = document.querySelector('#announcement-container');
|
||
if (announcementContainer) {
|
||
announcementContainer.style.flexDirection = reverseFlex ? 'row-reverse' : 'row';
|
||
}
|
||
}
|
||
|
||
function resetAnnouncements() {
|
||
announcementPosition = 1;
|
||
const announcementContainer = document.querySelector('#announcement-container');
|
||
if (announcementContainer) {
|
||
announcementContainer.style.flexDirection = 'row';
|
||
}
|
||
withAnnouncements(function(announcement) {
|
||
announcement.style.opacity = 1.0;
|
||
announcement.style.display = 'flex';
|
||
});
|
||
}
|
||
|
||
function sizeToFullWhenOneEntryOnMobile() {
|
||
if (maxAnnouncementPosition <= 2 && announcementPrevBtn && announcementNextBtn) {
|
||
announcementPrevBtn.style.display = maxAnnouncementPosition === 2 && window.outerWidth < 500 ? 'flex' : 'none';
|
||
announcementNextBtn.style.display = maxAnnouncementPosition === 2 && window.outerWidth < 500 ? 'flex' : 'none';
|
||
}
|
||
if (maxAnnouncementPosition === 1 && window.outerWidth >= 500 && window.outerWidth < 890) {
|
||
document.querySelector(':root').style.setProperty('--mobile-announcement-separator-width', '0');
|
||
document.querySelector('#announcement-container .announcement').style.maxWidth = '100%';
|
||
}
|
||
}
|
||
|
||
function resetAnnouncementsOnResize() {
|
||
announcementPosition = 1; // we want to reset the announcement position every resize
|
||
if (window.outerWidth >= 890) { // 4 announcements can be shown (215px * 4) + separator padding
|
||
resetAnnouncements();
|
||
} else { // if we're still on mobile, set the position to normalize things after resize
|
||
setAnnouncementPosition(announcementPosition);
|
||
sizeToFullWhenOneEntryOnMobile();
|
||
}
|
||
}
|
||
|
||
window.addEventListener('resize', resetAnnouncementsOnResize);
|
||
|
||
initAnnouncementVars();
|
||
setAnnouncementPosition(announcementPosition); // sets the min & max positions as well as initializes transforms
|
||
|
||
if (announcementPrevBtn) {
|
||
announcementPrevBtn.onclick = function() {
|
||
setAnnouncementPosition(announcementPosition - 1);
|
||
};
|
||
}
|
||
|
||
if (announcementNextBtn) {
|
||
announcementNextBtn.onclick = function() {
|
||
setAnnouncementPosition(announcementPosition + 1);
|
||
};
|
||
}
|
||
})(); </script>
|
||
<div id="ftl-article" class="trending-article-body">
|
||
<aside class="trending-sidebar" aria-labelledby="related-sidebar-heading">
|
||
<div class="trending">
|
||
<h2 id="related-sidebar-heading">Related</h2>
|
||
<div class="trending-separator"></div>
|
||
<ul>
|
||
<li class="item">
|
||
<a href="/articles/microservices-architecture-in-production" class="related-link">Microservices Architecture in Production: 7 Engineering Decisions That Determine Success or Failure</a>
|
||
</li>
|
||
<li class="item">
|
||
<a href="/articles/a-data-first-pattern-for-legacy-modernization" class="related-link">Tired of Reverse-Engineering Code? A Data-First Pattern for Legacy Modernization</a>
|
||
</li>
|
||
<li class="item">
|
||
<a href="/articles/mastering-the-art-of-data-engineering" class="related-link">Mastering the Art of Data Engineering to Support Billion-Dollar Tech Ecosystems</a>
|
||
</li>
|
||
<li class="item">
|
||
<a href="/articles/automating-databases-for-modern-devops-practices" class="related-link">Automating Databases for Modern DevOps Practices: A Guide to Common Patterns and Anti-Patterns for Database Automation Techniques</a>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</aside>
|
||
<div class="container-fluid body trending-article-fluid">
|
||
<div class="row">
|
||
<div class="col-md-12">
|
||
<div class="articles-wrap">
|
||
<div class="ad-container">
|
||
<div id="div-gpt-ad-1435246566686-0" class="ads-billboard-article dz2_article_billboard_new" data-gpt-slot="top"></div>
|
||
</div>
|
||
|
||
|
||
<div class="article-stream widget-top-border">
|
||
<div class="article-main">
|
||
<div class="content-right-images">
|
||
<div id="div-gpt-ad-1435246566686-2" class="sidebar-ad dz2_article_halfpage_new" data-gpt-slot="sidebar1"></div>
|
||
<div class="trending-separator"></div>
|
||
<aside class="trending" aria-labelledby="trending-sidebar-heading">
|
||
<h2 id="trending-sidebar-heading">Trending</h2>
|
||
<ul>
|
||
<li class="item">
|
||
<a href="/articles/dzones-article-submission-guidelines" class="trending-link" target="_self">DZone's Article Submission Guidelines</a>
|
||
</li>
|
||
<li class="item">
|
||
<a href="/articles/enterprise-data-engineering-changes" class="trending-link" target="_self">From ETL, ELT, and EtLT to Agent: What Is Changing in Enterprise Data Engineering?</a>
|
||
</li>
|
||
<li class="item">
|
||
<a href="/articles/telemetry-volume-noise" class="trending-link" target="_self">Cutting Telemetry Volume Is Not the Same as Cutting Noise</a>
|
||
</li>
|
||
<li class="item">
|
||
<a href="/articles/large-api-responses-client" class="trending-link" target="_self">Handling Large API Responses Without Freezing the Client: A Practical Architecture With Temporal, Kafka, and RAG</a>
|
||
</li>
|
||
</ul>
|
||
</aside>
|
||
</div>
|
||
<script type="application/ld+json">
|
||
{
|
||
"@context": "https://schema.org",
|
||
"@type": "Article",
|
||
"headline": "Engineering Resilience Through Data: A Comprehensive Approach to Change Failure Rate Monitoring",
|
||
"author": [{"image":{"@type":"ImageObject","caption":"Saumen Biswas","url":"https://secure.gravatar.com/avatar/04f5de8f4e088fac84dc20a81437000e?d=identicon&r=PG"},"@type":"Person","worksFor":{"@type":"Organization","name":"Upstart"},"name":"Saumen Biswas","url":"https://dzone.com/users/5305532/tokonson.html"}],
|
||
"audience": "software developers",
|
||
"keywords": "",
|
||
"timeRequired": "PT9M",
|
||
"commentCount": 0,
|
||
"wordCount": "2161",
|
||
"accessMode": "textual, visual",
|
||
"datePublished": "2025-06-12T00:00:00Z",
|
||
|
||
"articleSection": "",
|
||
"publisher": {
|
||
"@type": "Organization",
|
||
"name": "DZone",
|
||
"url": "https://dzone.com",
|
||
"logo": {
|
||
"@type": "ImageObject",
|
||
"url": "https://dzone.com/themes/dz20/images/dz_logo_2021_cropped.png"
|
||
}
|
||
},
|
||
"articleBody": "Organizations are constantly seeking ways to measure and improve their delivery performance. Among the key metrics that have emerged from the DevOps movement, Change Failure Rate (CFR) stands as a critical indicator of software quality and operational stability. This article explores how modern teams can effectively implement, track, and leverage CFR to drive continuous improvement in their delivery pipelines. Understanding Change Failure Rate: Beyond the Basic Metric Change Failure Rate is one of the four key metrics identified by the DevOps Research and Assessment (DORA) team that correlate with high-performing engineering organizations. Simply put, CFR measures the percentage of changes to production that result in degraded service or require remediation. The formula is straightforward: CFR = (Number of Failed Changes / Total Number of Changes) × 100 Traditionally, \"failed changes\" have been detected through explicit rollbacks in CI/CD systems. However, this approach is increasingly insufficient. Teams often remediate failures via hot-fixes, config changes, or feature flag toggles—actions that don't leave behind clean rollback breadcrumbs. Although the calculation may seem simple, implementing an accurate and comprehensive CFR tracking system requires careful consideration of what constitutes a \"change\" and a \"failure\" in your specific environment. The Unified Change Model: Redefining \"Change\" In modern software environments, changes extend beyond traditional code deployments. A comprehensive CFR implementation should track: Code Deployments: Traditional artifact deployments via CI/CD pipelinesFeature Flag Toggles: State changes in production feature flagsConfiguration Changes: Modifications to application configurationSecret Rotations: Credential or secret value updatesInfrastructure Changes: CDK/Terraform infrastructure modificationsDatabase Schema Changes: Production database schema updates In particular, routine database record updates that don't affect application structure or behavior are typically excluded from the change count. To capture this diversity of changes, engineering organizations are adopting a foundational abstraction called a Unified Change Event, with a consistent schema: JSON { \"change_id\": \"uuid\", \"timestamp\": \"ISO8601\", \"change_type\": \"deployment | feature_flag | config | secret\", \"repo\": \"string\", \"commit_sha\": \"string\", \"initiator\": \"user_id\", \"labels\": [\"prod\", \"rollback\", \"hotfix\"], \"environment\": \"production\", \"related_incident_id\": \"string (optional)\" } Example of a feature flag change event from LaunchDarkly: JSON { \"flagKey\": \"new-checkout-flow\", \"environment\": \"production\", \"user\": \"engineering@yourcompany.com\", \"previous\": false, \"current\": true, \"timestamp\": \"2025-04-03T18:21:34Z\" } What Constitutes a \"Failure\"? A failure is any production change that results in service degradation or outage requiring remediation. This could include: Changes that trigger a P1/P2 incident within 48 hours of deploymentChanges requiring a rollback within 24 hoursChanges necessitating a hotfix deployed outside the standard release process within 72 hoursChanges causing documented customer impact resulting in support escalations Architecture for CFR at Scale Key Components Change Monitoring Service (CMS): Emits standardized change events from diverse sourcesMessage Broker (e.g., Kafka): Event bus for change and incident eventsDurable Storage (e.g., S3): Intermediate storage for resilience and replayData Processing (e.g., Databricks): Event normalization, enrichment, and failure correlationVisualization (e.g., Looker, Grafana): Reporting and dashboardsObservability (e.g., Datadog, Prometheus): Alerting on anomalies Instrumentation Flow GitHub PRs and CI/CD pipelines emit structured metadata (e.g., is_rollback, hotfix flags)CMS integrates with various tools to capture different change types: Feature flag systems (LaunchDarkly)Secret managers (Vault, AWS Secrets Manager)GitOps platforms (ArgoCD, Flux)Infrastructure as Code tools (Terraform, CDK)Events are normalized into the unified change schemaCorrelation engine links changes to failures The Challenge of Failure-to-Change Correlation Perhaps the most complex aspect of CFR tracking is accurately correlating failures to their originating changes. This requires sophisticated approaches that go beyond simple time-based matching. Why Simple Correlation Fails Failures are often: Multi-causal (caused by multiple changes)Indirect (propagate through dependencies)Delayed (appears hours or days after a change) Confidence Scoring: A Nuanced Approach Instead of binary attribution, implement a confidence scoring system that assigns a score (0–100) based on: Time Proximity: How close in time the change and failure occurredImpact Surface Matching: Whether the service/component of the change matches the failureMetadata Signals: Additional signals like commit messages, PR descriptions, and explicit references Python def calculate_time_proximity_score(incident_time, change_time, window_hours): # Closer changes score higher (exponential decay) hours_difference = abs((incident_time - change_time).total_hours()) if hours_difference > window_hours: return 0 return 40 * math.exp(-3 * hours_difference / window_hours) These scores are mapped into confidence levels: Low: 0–40 pointsMedium: 41–70 pointsHigh: 71–100 points Medium and high confidence correlations can be included in CFR calculations, with different weights based on confidence level. Dynamic Time Windows Rather than using a fixed time window to correlate incidents to changes, implement a service-specific dynamic window: Python def calculate_lookback_window(service_id): base_window = 24 # hours # Get service metadata avg_deploy_frequency = get_service_deploy_frequency(service_id) service_criticality = get_service_criticality(service_id) historical_mttr = get_historical_mttr(service_id) # Adjust the window based on service characteristics if avg_deploy_frequency < 1: # less than once per day deploy_factor = 2.0 elif avg_deploy_frequency > 5: # high frequency deployments deploy_factor = 0.5 else: deploy_factor = 1.0 # Critical services may need a longer lookback criticality_factor = 1.0 + (service_criticality / 10) # Consider historical recovery patterns mttr_factor = min(2.0, max(0.5, historical_mttr / 120)) return max(4, min(72, base_window * deploy_factor * criticality_factor * mttr_factor)) Failure Duration and MTTR Tracking Accurately tracking Mean Time to Recovery (MTTR) requires capturing not just when a failure occurred, but how long it persisted: failure_start_at: Earliest of alert firing, incident creation, or metric breachfailure_end_at: Resolved via incident close, metrics normalization, or deployment of a fixuser_impact_duration_minutes: Time during which user-facing metrics showed degradation Additionally, tracking resolution phases provide deeper insights: JSON \"resolution_phases\": [ { \"phase\": \"identified\", \"timestamp\": \"2023-05-15T14:55:33Z\", \"impact_reduction_percent\": 0 }, { \"phase\": \"mitigated\", \"timestamp\": \"2023-05-15T15:30:12Z\", \"impact_reduction_percent\": 80 }, { \"phase\": \"resolved\", \"timestamp\": \"2023-05-15T16:45:02Z\", \"impact_reduction_percent\": 100 } ] Leveraging CFR Data for Engineering Improvement Team Performance Benchmarking According to DORA research, teams typically fall into these performance tiers based on CFR: Elite: < 5%High: 5–15%Medium: 16–30%Low: >30% Regular reporting on CFR by the team can help identify areas for improvement and highlight successful practices. Query-Driven Analytics The rich dataset supports sophisticated analyses across time, teams, and services: Monthly CFR Trend SQL SELECT DATE_TRUNC('month', timestamp) AS month, COUNT(CASE WHEN is_rollback = true THEN 1 END) * 100.0 / COUNT(*) AS cfr FROM deployments WHERE environment = 'production' GROUP BY 1 ORDER BY 1 DESC; Top Services by CFR SQL WITH service_changes AS ( SELECT service, COUNT(DISTINCT change_id) as total_changes FROM changes WHERE timestamp >= DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY) GROUP BY service ) SELECT c.service, COUNT(DISTINCT c.correlation_id) as failures, sc.total_changes, (COUNT(DISTINCT c.correlation_id) / sc.total_changes) * 100 as change_failure_rate FROM correlations c JOIN service_changes sc ON c.service = sc.service WHERE c.failure_start_at >= DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY) AND c.changes[0].confidence_level IN ('high', 'medium') GROUP BY c.service, sc.total_changes ORDER BY change_failure_rate DESC LIMIT 10; Team-Level MTTR Analysis SQL SELECT team_name, AVG(duration_minutes) as avg_mttr, COUNT(*) as incident_count FROM correlations JOIN services ON correlations.service = services.name JOIN team_service_mapping ON services.id = team_service_mapping.service_id WHERE correlation_state IN ('confirmed', 'finalized') AND changes[0].confidence_level = 'high' AND failure_start_at >= DATE_SUB(CURRENT_DATE(), INTERVAL 90 DAY) GROUP BY team_name ORDER BY avg_mttr DESC; Risk Assessment CFR data can be used to build risk profiles for different types of changes, services, or deployment patterns: Python def calculate_deployment_risk(service_id, change_metadata): # Get historical correlation data recent_failures = correlation_client.get_recent_failures( service_id=service_id, days=30 ) # Calculate baseline risk from historical data baseline_risk = min(80, len(recent_failures) * 10) # Adjust for change size change_size_factor = min(2.0, (change_metadata.lines_changed / 1000) * 0.5 + 1) # Adjust for change type if change_metadata.is_hotfix: type_factor = 1.5 elif change_metadata.is_rollback: type_factor = 0.7 else: type_factor = 1.0 # Combine factors risk_score = min(100, baseline_risk * change_size_factor * type_factor) return { 'score': risk_score, 'level': risk_level_from_score(risk_score), 'historical_failures': len(recent_failures), 'recommendations': generate_risk_recommendations(risk_score) } Monitoring, Feedback, and Continuous Improvement System Health Monitoring Event ingestion rate and latencyProcessing pipeline performanceData quality metrics (completeness, freshness)Dashboard load performanceCorrelation accuracy Feedback Loop for Algorithm Improvement Implement a simple feedback mechanism for engineers to verify correlations: JSON \"feedback\": { \"manually_verified\": true, \"verification_user\": \"jane.engineer@example.com\", \"verification_timestamp\": \"2023-05-16T09:12:45Z\", \"notes\": \"Confirmed during postmortem that the deployment caused the outage\" } Instead of free-text feedback, categorize rejection reasons: false_positiveconfounding_changeduplicate_incidentdelayed_signal By capturing this feedback, your correlation algorithms can continuously improve, learning from both successful and unsuccessful attributions. This human-in-the-loop approach combines the scalability of automated analysis with the nuanced understanding of experienced engineers. Success Criteria and Evaluation MetricTargetDescription Data completeness >99.5% All expected events captured Correlation accuracy >85% Verified against manual review Dashboard usage >75% teams Weekly access logs CFR improvement 10% QoQ Relative reduction in failures System availability >99.9% uptime Streaming and dashboard reliability Visualization and Reporting Effective visualization is crucial for making CFR data actionable: Executive-Level Reporting Trend Analysis: Show CFR trends over time with quarterly targetsOrganization-Wide Benchmarking: Compare against industry standards from DORA researchBusiness Impact Correlation: Relate CFR to customer satisfaction or revenue metrics Team-Level Insights Service-Specific CFR: Break down by individual servicesFailure Categorization: Show distribution of failure typesChange Volume vs. Failure Rate: Identify if failures correlate with change velocity Engineer-Level Detail Recent Changes and Outcomes: Individual change history with success/failure statusPostmortem Links: Direct connections to incident reviewsFeedback Collection: Tools for submitting correlation corrections Integrations and Extensibility Service Catalog Integration A robust CFR implementation should integrate with your service catalog to: Retrieve Service Context: Access service ownership, dependency relationships, and criticality informationUpdate Service Health Metrics: Write reliability metrics back to the service catalogMap Dependency Impacts: Understand how changes in one service affect others in the ecosystem Incident Management System Integration Establish bidirectional integration with incident management: Python def update_incident_with_correlation(incident_id, correlation): \"\"\"Update incident record with correlation findings.\"\"\" if correlation['changes'] and correlation['changes'][0]['confidence_level'] in ['high', 'medium']: change = correlation['changes'][0] timeline_entry = { 'type': 'correlation_finding', 'timestamp': datetime.now().isoformat(), 'content': f\"Potential causal change identified: {change['change_id']} with {change['confidence_level']} confidence\", 'metadata': { 'correlation_id': correlation['correlation_id'], 'confidence': change['confidence_level'], 'score': change['confidence_score'] } } incident_client.add_timeline_entry(incident_id, timeline_entry) CI/CD Pipeline Integration Connect with CI/CD systems to provide risk scoring for pending deployments: Python def calculate_deployment_risk(service_id, change_metadata): \"\"\"Calculate risk score for a pending deployment.\"\"\" # Get historical correlation data recent_failures = correlation_client.get_recent_failures( service_id=service_id, days=30 ) # Calculate baseline risk from historical data baseline_risk = min(80, len(recent_failures) * 10) # Adjust for change size change_size_factor = min(2.0, (change_metadata.lines_changed / 1000) * 0.5 + 1) # Adjust for change type if change_metadata.is_hotfix: type_factor = 1.5 elif change_metadata.is_rollback: type_factor = 0.7 else: type_factor = 1.0 # Combine factors risk_score = min(100, baseline_risk * change_size_factor * type_factor) return { 'score': risk_score, 'level': risk_level_from_score(risk_score), 'historical_failures': len(recent_failures), 'recommendations': generate_risk_recommendations(risk_score) } Common Implementation Challenges Data Quality Issues Inconsistent or missing data can significantly impact CFR accuracy. Common challenges include: Inconsistent Rollback Labeling: Changes not properly marked as rollbacks or hotfixesMissing Incident Correlation: Failures not linked to their triggering changesDuplicate or Missed Data: Events counted multiple times or not at all Mitigate these issues by: Automating labeling via CI checks and GitHub ActionsUsing time-window correlation with reasonable defaultsImplementing data validation and deduplication logic Organizational Adoption Technical implementation is only half the battle. To drive organizational adoption: Education: Hold team workshops on CFR interpretation and improvement strategiesIncentives: Align team goals with CFR improvementAccessibility: Make dashboards intuitive and easily accessibleContext: Provide context around the numbers with recommended actions Future Directions: The Road to ML and Predictive CFR As your CFR implementation matures, consider these advanced applications: Machine Learning for Correlation As your dataset grows, consider implementing machine learning models to improve correlation accuracy: Supervised Learning: Train models on verified correlationsFeature Extraction: Extract meaningful signals from change metadataService-Specific Patterns: Develop correlation patterns unique to each service Causal Inference Move from correlation to causation modeling: Implement counterfactual analysisDevelop directed acyclic graphs (DAGs) using service dependency mappingApply temporal event sequencing models (e.g., Hawkes processes) Predictive Analytics Use historical CFR data to build predictive models that can: Identify high-risk changes before deploymentSuggest optimal deployment windowsRecommend additional testing for risky changesGenerate proactive alerts for changes with high failure probability Business Impact Analysis Extend CFR monitoring to include business impact metrics: Revenue impact per failureCustomer experience degradationBrand reputation effects Low-Code Automation for CFR Implementation For organizations looking to quickly implement CFR, several low-code automation frameworks can help: Datadog's Service Catalog: Provides change tracking and incident managementHarness Continuous Insights: Offers DORA metric trackingGitHub Actions Templates: Workflows for emitting standardized deployment eventsGrafana's DORA dashboard: Visualization for DORA metrics including CFR These tools significantly reduce the time required to set up a functioning CFR tracking system, allowing teams to focus on using the insights rather than building the infrastructure. Conclusion In the era of low-code automation, real-time delivery pipelines, and complex deployments, observability into change impact is a competitive advantage. Change Failure Rate is more than just a metric—it's a lens into your organization's software delivery health and a powerful tool for continuous improvement. By implementing a comprehensive CFR tracking system built on a unified change model and enriched by confidence-based correlation, organizations gain insights that drive technical excellence, reduce customer-impacting incidents, and ultimately deliver more value to users. The journey to effective CFR tracking may seem complex, but the rewards are substantial: greater reliability, faster recovery times, and more confident deployments. Whether you're using sophisticated custom solutions or low-code automation frameworks, the key is to start measuring, establish a baseline, and commit to data-driven improvement. As the software industry continues to evolve toward more frequent deployments and complex distributed systems, robust CFR monitoring will become increasingly critical for maintaining stability while accelerating innovation. Organizations that excel at understanding and improving their Change Failure Rate will be better positioned to deliver reliable software at scale.",
|
||
"mainEntityOfPage": {
|
||
"@type": "WebPage",
|
||
"@id": "https://dzone.com/articles/engineering-resilience-data-cfr-monitoring"
|
||
},
|
||
"image": {
|
||
"@type": "ImageObject",
|
||
"url": "https://dz2cdn1.dzone.com/storage/article-thumb/18457521-thumb.jpg"
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<script type="application/ld+json">
|
||
{
|
||
"@context": "https://schema.org",
|
||
"@type": "BreadcrumbList",
|
||
"itemListElement": [
|
||
{
|
||
"@type": "ListItem",
|
||
"position": 1,
|
||
"name": "DZone",
|
||
"item": "https://dzone.com"
|
||
},
|
||
{
|
||
"@type": "ListItem",
|
||
"position": 2,
|
||
"name": "Testing, Deployment, and Maintenance",
|
||
"item": "https://dzone.com/testing-deployment-and-maintenance"
|
||
},
|
||
{
|
||
"@type": "ListItem",
|
||
"position": 3,
|
||
"name": "DevOps and CI/CD",
|
||
"item": "https://dzone.com/devops-and-cicd"
|
||
},
|
||
{
|
||
"@type": "ListItem",
|
||
"position": 4,
|
||
"name": "Engineering Resilience Through Data: A Comprehensive Approach to Change Failure Rate Monitoring",
|
||
"item": "https://dzone.com/articles/engineering-resilience-data-cfr-monitoring"
|
||
}
|
||
]
|
||
}
|
||
|
||
</script>
|
||
|
||
<article>
|
||
<div class="content">
|
||
<div class="header">
|
||
<ol class="breadcrumb">
|
||
<li class="server"><a href="https://dzone.com">DZone</a></li>
|
||
<li class="server"><a href="https://dzone.com/testing-deployment-and-maintenance">Testing, Deployment, and Maintenance</a></li>
|
||
<li class="server"><a href="https://dzone.com/devops-and-cicd">DevOps and CI/CD</a></li>
|
||
<li class="active server">Engineering Resilience Through Data: A Comprehensive Approach to Change Failure Rate Monitoring</li>
|
||
</ol>
|
||
|
||
|
||
<div class="header-title">
|
||
<div class="title">
|
||
<h1 class="article-title">Engineering Resilience Through Data: A Comprehensive Approach to Change Failure Rate Monitoring</h1>
|
||
</div>
|
||
|
||
<div class="subhead">
|
||
<p>CFR measures how often changes break production. This article shows how to track it accurately using event data, confidence scoring, and real-time dashboards.</p>
|
||
</div>
|
||
|
||
<div class="publish-meta">
|
||
By<span style="user-select: none;"> </span>
|
||
<div class="article-author-meta">
|
||
<img src="https://secure.gravatar.com/avatar/04f5de8f4e088fac84dc20a81437000e?d=identicon&r=PG" class="avatar" alt="Saumen Biswas user avatar" width="40">
|
||
<div class="author-info">
|
||
<span class="author-name">
|
||
<a href="/users/5305532/tokonson.html" rel="nofollow" data-core-user="false">Saumen Biswas</a>
|
||
</span>
|
||
</div>
|
||
|
||
|
||
|
||
·
|
||
</div>
|
||
<span class="author-date">
|
||
Jun. 12, 25
|
||
</span>
|
||
·
|
||
<span>Analysis</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="author-n-useraction">
|
||
<div class="like action" x-data="engagementModal(articleId)">
|
||
<span id="activity-like-icon" class="dz-like icon-thumbs-up" x-on:click="updateStore()"></span>
|
||
<span class="action-label like-text" x-on:click="open()" x-cloak>
|
||
<span>Likes</span>
|
||
<span id="activity-like-counter" class="like-count">(3)</span>
|
||
</span>
|
||
<template x-teleport="body">
|
||
<div class="engagement-overlay" x-show="$store.article.engagement.open && nodeId === $store.article.id" x-on:keyup.escape.window="close()">
|
||
<div class="engagement-modal">
|
||
<div class="inner" x-on:click.outside="close()">
|
||
<div class="header">
|
||
<div class="title">Likes</div>
|
||
<button class="close" x-on:click="close()"></button>
|
||
</div>
|
||
<div class="content">
|
||
<div x-show="$store.article.engagement.initializing" class="loading-screen">
|
||
<i class="icon icon-spin3 anim-spin"></i>
|
||
</div>
|
||
<div x-show="!$store.article.engagement.initializing">
|
||
<template x-for="user in $store.article.engagement.users" :key="user.id">
|
||
<div class="media">
|
||
<div class="media-left">
|
||
<img class="avatar"
|
||
x-bind:src="user.profile.profileImage"
|
||
loading="lazy"
|
||
width="50"
|
||
height="50"
|
||
>
|
||
</div>
|
||
<div class="media-right">
|
||
<a x-bind:href="user.computed.url" x-html="user.profile.name"></a>
|
||
<div x-show="formatJobData(user)" x-html="formatJobData(user)"></div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
<div x-show="isDataUnavailable()" class="center-screen">
|
||
<div x-show="!$store.article.engagement.unauthorized">
|
||
<div>There are no likes...yet! 👀</div>
|
||
<div>Be the first to like this post!</div>
|
||
</div>
|
||
<div x-show="$store.article.engagement.unauthorized">
|
||
<div>It looks like you're not logged in.</div>
|
||
<div><a href="/users/login.html">Sign in</a> to see who liked this post!</div>
|
||
</div>
|
||
</div>
|
||
<div x-show="isFooterShowing()">
|
||
<div class="footer">
|
||
<button class="btn btn-primary" x-on:click="fetchEngagements()"
|
||
x-bind:disabled="$store.article.engagement.loading"
|
||
>
|
||
Load More
|
||
<i x-show="$store.article.engagement.loading" class="icon icon-spin3 anim-spin"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template> </div>
|
||
|
||
<div class="action comment-action">
|
||
<span class="comment">
|
||
<i class="icon-comment"></i>
|
||
<span class="action-label">Comment</span>
|
||
<span id="activity-comment-counter" class="comment-count"></span>
|
||
</span>
|
||
</div>
|
||
|
||
<div class="save action">
|
||
<div id="activity-save-icon" class="save icon-star-empty">
|
||
<span id="activity-save-text" class="action-label">Save</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="tweet action">
|
||
<a id="tweet-link" href="" class="title" target="_blank">
|
||
<span><i class="icon-twitter"></i></span>
|
||
<span class="action-label">Tweet</span>
|
||
</a>
|
||
</div>
|
||
|
||
<div class="linkedin action">
|
||
<a id="linkedin-link" href="https://www.linkedin.com/sharing/share-offsite/?url=https://dzone.com/articles/engineering-resilience-data-cfr-monitoring" class="title" target="_blank">
|
||
<span><i class="icon-linkedin-1"></i></span>
|
||
<span class="action-label">Share</span>
|
||
</a>
|
||
</div>
|
||
|
||
<div class="right-most">
|
||
<div id="activity-view-container" class="article-views action">
|
||
<i class="icon-eye"></i>
|
||
<span class="action-label view-count">2.9K Views</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="signin-prompt">
|
||
<p>Join the DZone community and get the full member experience.</p>
|
||
<a id="article-signin-prompt" href="/static/registration.html">Join For Free</a>
|
||
</div>
|
||
<div class="arrow-down"></div>
|
||
|
||
<div id="top-bumper-container"></div>
|
||
|
||
<div>
|
||
<div class="content-html"><p>Organizations are constantly seeking ways to measure and improve their delivery performance. Among the key metrics that have emerged from the DevOps movement, <a href="https://dzone.com/articles/metrics-part-2-the-dora-keys">Change Failure Rate (CFR)</a> stands as a critical indicator of software quality and operational stability. This article explores how modern teams can effectively implement, track, and leverage CFR to drive continuous improvement in their delivery pipelines.</p>
|
||
<h2>Understanding Change Failure Rate: Beyond the Basic Metric</h2>
|
||
<p>Change Failure Rate is one of the four key metrics identified by the <a href="https://dzone.com/articles/dora-metrics-tracking-and-observability-with-jenki">DevOps Research and Assessment (DORA)</a> team that correlate with high-performing engineering organizations. Simply put, CFR measures the percentage of changes to production that result in degraded service or require remediation.</p>
|
||
<p>The formula is straightforward:</p>
|
||
<p><code>CFR = (Number of Failed Changes / Total Number of Changes) × 100</code></p>
|
||
<p>Traditionally, "failed changes" have been detected through explicit rollbacks in CI/CD systems. However, this approach is increasingly insufficient. Teams often remediate failures via hot-fixes, config changes, or feature flag toggles—actions that don't leave behind clean rollback breadcrumbs.</p>
|
||
<p>Although the calculation may seem simple, implementing an accurate and comprehensive CFR tracking system requires careful consideration of what constitutes a "change" and a "failure" in your specific environment.</p>
|
||
<h2>The Unified Change Model: Redefining "Change"</h2>
|
||
<p>In modern software environments, changes extend beyond traditional code deployments. A comprehensive CFR implementation should track:</p>
|
||
<ol>
|
||
<li><strong>Code Deployments</strong>: Traditional artifact deployments via CI/CD pipelines</li>
|
||
<li><strong>Feature Flag Toggles</strong>: State changes in production feature flags</li>
|
||
<li><strong>Configuration Changes</strong>: Modifications to application configuration</li>
|
||
<li><strong>Secret Rotations</strong>: Credential or secret value updates</li>
|
||
<li><strong>Infrastructure Changes</strong>: CDK/Terraform infrastructure modifications</li>
|
||
<li><strong>Database Schema Changes</strong>: Production database schema updates</li>
|
||
</ol>
|
||
<p>In particular, routine database record updates that don't affect application structure or behavior are typically excluded from the change count.</p>
|
||
<p>To capture this diversity of changes, engineering organizations are adopting a foundational abstraction called a Unified Change Event, with a consistent schema:</p>
|
||
<div class="codeMirror-wrapper" contenteditable="false">
|
||
<div contenteditable="false">
|
||
<div class="codeHeader">
|
||
<div class="nameLanguage">
|
||
JSON
|
||
</div><i class="icon-cancel-circled-1 cm-remove"> </i>
|
||
</div>
|
||
<div class="codeMirror-code--wrapper" data-code=" {
|
||
"change_id": "uuid",
|
||
"timestamp": "ISO8601",
|
||
"change_type": "deployment | feature_flag | config | secret",
|
||
"repo": "string",
|
||
"commit_sha": "string",
|
||
"initiator": "user_id",
|
||
"labels": ["prod", "rollback", "hotfix"],
|
||
"environment": "production",
|
||
"related_incident_id": "string (optional)"
|
||
}" data-lang="application/json">
|
||
<pre><code lang="application/json"> {
|
||
"change_id": "uuid",
|
||
"timestamp": "ISO8601",
|
||
"change_type": "deployment | feature_flag | config | secret",
|
||
"repo": "string",
|
||
"commit_sha": "string",
|
||
"initiator": "user_id",
|
||
"labels": ["prod", "rollback", "hotfix"],
|
||
"environment": "production",
|
||
"related_incident_id": "string (optional)"
|
||
}</code></pre>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p><br></p>
|
||
<p>Example of a feature flag change event from LaunchDarkly:</p>
|
||
<div class="codeMirror-wrapper" contenteditable="false">
|
||
<div contenteditable="false">
|
||
<div class="codeHeader">
|
||
<div class="nameLanguage">
|
||
JSON
|
||
</div><i class="icon-cancel-circled-1 cm-remove"> </i>
|
||
</div>
|
||
<div class="codeMirror-code--wrapper" data-code=" {
|
||
"flagKey": "new-checkout-flow",
|
||
"environment": "production",
|
||
"user": "engineering@yourcompany.com",
|
||
"previous": false,
|
||
"current": true,
|
||
"timestamp": "2025-04-03T18:21:34Z"
|
||
}" data-lang="application/json">
|
||
<pre><code lang="application/json"> {
|
||
"flagKey": "new-checkout-flow",
|
||
"environment": "production",
|
||
"user": "<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="13767d747a7d7676617a7d74536a7c6661707c7e63727d6a3d707c7e">[email protected]</a>",
|
||
"previous": false,
|
||
"current": true,
|
||
"timestamp": "2025-04-03T18:21:34Z"
|
||
}</code></pre>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<h2><br></h2>
|
||
<h2>What Constitutes a "Failure"?</h2>
|
||
<p>A failure is any production change that results in service degradation or outage requiring remediation. This could include:</p>
|
||
<ol>
|
||
<li>Changes that trigger a P1/P2 incident within 48 hours of deployment</li>
|
||
<li>Changes requiring a rollback within 24 hours</li>
|
||
<li>Changes necessitating a hotfix deployed outside the standard release process within 72 hours</li>
|
||
<li>Changes causing documented customer impact resulting in support escalations</li>
|
||
</ol>
|
||
<h2>Architecture for CFR at Scale</h2>
|
||
<h3>Key Components</h3>
|
||
<ol>
|
||
<li><strong>Change Monitoring Service (CMS)</strong>: Emits standardized change events from diverse sources</li>
|
||
<li><strong>Message Broker (e.g., Kafka)</strong>: Event bus for change and incident events</li>
|
||
<li><strong>Durable Storage (e.g., S3)</strong>: Intermediate storage for resilience and replay</li>
|
||
<li><strong>Data Processing (e.g., Databricks)</strong>: Event normalization, enrichment, and failure correlation</li>
|
||
<li><strong>Visualization (e.g., Looker, Grafana)</strong>: Reporting and dashboards</li>
|
||
<li><strong>Observability (e.g., Datadog, Prometheus)</strong>: Alerting on anomalies</li>
|
||
</ol>
|
||
<h3>Instrumentation Flow</h3>
|
||
<ol>
|
||
<li>GitHub PRs and CI/CD pipelines emit structured metadata (e.g., is_rollback, hotfix flags)</li>
|
||
<li>CMS integrates with various tools to capture different change types:
|
||
<ul>
|
||
<li>Feature flag systems (LaunchDarkly)</li>
|
||
<li>Secret managers (Vault, AWS Secrets Manager)</li>
|
||
<li>GitOps platforms (ArgoCD, Flux)</li>
|
||
<li>Infrastructure as Code tools (Terraform, CDK)</li>
|
||
</ul></li>
|
||
<li>Events are normalized into the unified change schema</li>
|
||
<li>Correlation engine links changes to failures</li>
|
||
</ol>
|
||
<h2>The Challenge of Failure-to-Change Correlation</h2>
|
||
<p>Perhaps the most complex aspect of CFR tracking is accurately correlating failures to their originating changes. This requires sophisticated approaches that go beyond simple time-based matching.</p>
|
||
<h3>Why Simple Correlation Fails</h3>
|
||
<p>Failures are often:</p>
|
||
<ul>
|
||
<li>Multi-causal (caused by multiple changes)</li>
|
||
<li>Indirect (propagate through dependencies)</li>
|
||
<li>Delayed (appears hours or days after a change)</li>
|
||
</ul>
|
||
<h3>Confidence Scoring: A Nuanced Approach</h3>
|
||
<p>Instead of binary attribution, implement a confidence scoring system that assigns a score (0–100) based on:</p>
|
||
<ol>
|
||
<li><strong>Time Proximity</strong>: How close in time the change and failure occurred</li>
|
||
<li><strong>Impact Surface Matching</strong>: Whether the service/component of the change matches the failure</li>
|
||
<li><strong>Metadata Signals</strong>: Additional signals like commit messages, PR descriptions, and explicit references</li>
|
||
</ol>
|
||
<div class="codeMirror-wrapper" contenteditable="false">
|
||
<div contenteditable="false">
|
||
<div class="codeHeader">
|
||
<div class="nameLanguage">
|
||
Python
|
||
</div><i class="icon-cancel-circled-1 cm-remove"> </i>
|
||
</div>
|
||
<div class="codeMirror-code--wrapper" data-code=" def calculate_time_proximity_score(incident_time, change_time, window_hours):
|
||
# Closer changes score higher (exponential decay)
|
||
hours_difference = abs((incident_time - change_time).total_hours())
|
||
if hours_difference > window_hours:
|
||
return 0
|
||
return 40 * math.exp(-3 * hours_difference / window_hours)" data-lang="text/x-python">
|
||
<pre><code lang="text/x-python"> def calculate_time_proximity_score(incident_time, change_time, window_hours):
|
||
# Closer changes score higher (exponential decay)
|
||
hours_difference = abs((incident_time - change_time).total_hours())
|
||
if hours_difference > window_hours:
|
||
return 0
|
||
return 40 * math.exp(-3 * hours_difference / window_hours)</code></pre>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p>These scores are mapped into confidence levels:</p>
|
||
<ul>
|
||
<li><strong>Low</strong>: 0–40 points</li>
|
||
<li><strong>Medium</strong>: 41–70 points</li>
|
||
<li><strong>High</strong>: 71–100 points</li>
|
||
</ul>
|
||
<p>Medium and high confidence correlations can be included in CFR calculations, with different weights based on confidence level.</p>
|
||
<h3>Dynamic Time Windows</h3>
|
||
<p>Rather than using a fixed time window to correlate incidents to changes, implement a service-specific dynamic window:</p>
|
||
<div class="codeMirror-wrapper" contenteditable="false">
|
||
<div contenteditable="false">
|
||
<div class="codeHeader">
|
||
<div class="nameLanguage">
|
||
Python
|
||
</div><i class="icon-cancel-circled-1 cm-remove"> </i>
|
||
</div>
|
||
<div class="codeMirror-code--wrapper" data-code=" def calculate_lookback_window(service_id):
|
||
base_window = 24 # hours
|
||
|
||
# Get service metadata
|
||
avg_deploy_frequency = get_service_deploy_frequency(service_id)
|
||
service_criticality = get_service_criticality(service_id)
|
||
historical_mttr = get_historical_mttr(service_id)
|
||
|
||
# Adjust the window based on service characteristics
|
||
if avg_deploy_frequency < 1: # less than once per day
|
||
deploy_factor = 2.0
|
||
elif avg_deploy_frequency > 5: # high frequency deployments
|
||
deploy_factor = 0.5
|
||
else:
|
||
deploy_factor = 1.0
|
||
|
||
# Critical services may need a longer lookback
|
||
criticality_factor = 1.0 + (service_criticality / 10)
|
||
|
||
# Consider historical recovery patterns
|
||
mttr_factor = min(2.0, max(0.5, historical_mttr / 120))
|
||
|
||
return max(4, min(72, base_window * deploy_factor * criticality_factor * mttr_factor))" data-lang="text/x-python">
|
||
<pre><code lang="text/x-python"> def calculate_lookback_window(service_id):
|
||
base_window = 24 # hours
|
||
|
||
# Get service metadata
|
||
avg_deploy_frequency = get_service_deploy_frequency(service_id)
|
||
service_criticality = get_service_criticality(service_id)
|
||
historical_mttr = get_historical_mttr(service_id)
|
||
|
||
# Adjust the window based on service characteristics
|
||
if avg_deploy_frequency < 1: # less than once per day
|
||
deploy_factor = 2.0
|
||
elif avg_deploy_frequency > 5: # high frequency deployments
|
||
deploy_factor = 0.5
|
||
else:
|
||
deploy_factor = 1.0
|
||
|
||
# Critical services may need a longer lookback
|
||
criticality_factor = 1.0 + (service_criticality / 10)
|
||
|
||
# Consider historical recovery patterns
|
||
mttr_factor = min(2.0, max(0.5, historical_mttr / 120))
|
||
|
||
return max(4, min(72, base_window * deploy_factor * criticality_factor * mttr_factor))</code></pre>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<h2><br></h2>
|
||
<h2>Failure Duration and MTTR Tracking</h2>
|
||
<p>Accurately tracking Mean Time to Recovery (MTTR) requires capturing not just when a failure occurred, but how long it persisted:</p>
|
||
<ul>
|
||
<li><strong>failure_start_at</strong>: Earliest of alert firing, incident creation, or metric breach</li>
|
||
<li><strong>failure_end_at</strong>: Resolved via incident close, metrics normalization, or deployment of a fix</li>
|
||
<li><strong>user_impact_duration_minutes</strong>: Time during which user-facing metrics showed degradation</li>
|
||
</ul>
|
||
<p>Additionally, tracking resolution phases provide deeper insights:</p>
|
||
<div class="codeMirror-wrapper" contenteditable="false">
|
||
<div contenteditable="false">
|
||
<div class="codeHeader">
|
||
<div class="nameLanguage">
|
||
JSON
|
||
</div><i class="icon-cancel-circled-1 cm-remove"> </i>
|
||
</div>
|
||
<div class="codeMirror-code--wrapper" data-code=" "resolution_phases": [
|
||
{
|
||
"phase": "identified",
|
||
"timestamp": "2023-05-15T14:55:33Z",
|
||
"impact_reduction_percent": 0
|
||
},
|
||
{
|
||
"phase": "mitigated",
|
||
"timestamp": "2023-05-15T15:30:12Z",
|
||
"impact_reduction_percent": 80
|
||
},
|
||
{
|
||
"phase": "resolved",
|
||
"timestamp": "2023-05-15T16:45:02Z",
|
||
"impact_reduction_percent": 100
|
||
}
|
||
] " data-lang="application/json">
|
||
<pre><code lang="application/json"> "resolution_phases": [
|
||
{
|
||
"phase": "identified",
|
||
"timestamp": "2023-05-15T14:55:33Z",
|
||
"impact_reduction_percent": 0
|
||
},
|
||
{
|
||
"phase": "mitigated",
|
||
"timestamp": "2023-05-15T15:30:12Z",
|
||
"impact_reduction_percent": 80
|
||
},
|
||
{
|
||
"phase": "resolved",
|
||
"timestamp": "2023-05-15T16:45:02Z",
|
||
"impact_reduction_percent": 100
|
||
}
|
||
] </code></pre>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<h2><br></h2>
|
||
<h2>Leveraging CFR Data for Engineering Improvement</h2>
|
||
<h3>Team Performance Benchmarking</h3>
|
||
<p>According to DORA research, teams typically fall into these performance tiers based on CFR:</p>
|
||
<ul>
|
||
<li><strong>Elite</strong>: < 5%</li>
|
||
<li><strong>High</strong>: 5–15%</li>
|
||
<li><strong>Medium</strong>: 16–30%</li>
|
||
<li><strong>Low</strong>: >30%</li>
|
||
</ul>
|
||
<p>Regular reporting on CFR by the team can help identify areas for improvement and highlight successful practices.</p>
|
||
<h3>Query-Driven Analytics</h3>
|
||
<p>The rich dataset supports sophisticated analyses across time, teams, and services:</p>
|
||
<h4>Monthly CFR Trend</h4>
|
||
<div class="codeMirror-wrapper" contenteditable="false">
|
||
<div contenteditable="false">
|
||
<div class="codeHeader">
|
||
<div class="nameLanguage">
|
||
SQL
|
||
</div><i class="icon-cancel-circled-1 cm-remove"> </i>
|
||
</div>
|
||
<div class="codeMirror-code--wrapper" data-code=" SELECT
|
||
DATE_TRUNC('month', timestamp) AS month,
|
||
COUNT(CASE WHEN is_rollback = true THEN 1 END) * 100.0 / COUNT(*) AS cfr
|
||
FROM deployments
|
||
WHERE environment = 'production'
|
||
GROUP BY 1
|
||
ORDER BY 1 DESC;" data-lang="text/x-sql">
|
||
<pre><code lang="text/x-sql"> SELECT
|
||
DATE_TRUNC('month', timestamp) AS month,
|
||
COUNT(CASE WHEN is_rollback = true THEN 1 END) * 100.0 / COUNT(*) AS cfr
|
||
FROM deployments
|
||
WHERE environment = 'production'
|
||
GROUP BY 1
|
||
ORDER BY 1 DESC;</code></pre>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<h4><br></h4>
|
||
<h4>Top Services by CFR</h4>
|
||
<div class="codeMirror-wrapper" contenteditable="false">
|
||
<div contenteditable="false">
|
||
<div class="codeHeader">
|
||
<div class="nameLanguage">
|
||
SQL
|
||
</div><i class="icon-cancel-circled-1 cm-remove"> </i>
|
||
</div>
|
||
<div class="codeMirror-code--wrapper" data-code=" WITH service_changes AS (
|
||
SELECT
|
||
service,
|
||
COUNT(DISTINCT change_id) as total_changes
|
||
FROM changes
|
||
WHERE timestamp >= DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY)
|
||
GROUP BY service
|
||
)
|
||
SELECT
|
||
c.service,
|
||
COUNT(DISTINCT c.correlation_id) as failures,
|
||
sc.total_changes,
|
||
(COUNT(DISTINCT c.correlation_id) / sc.total_changes) * 100 as change_failure_rate
|
||
FROM correlations c
|
||
JOIN service_changes sc ON c.service = sc.service
|
||
WHERE
|
||
c.failure_start_at >= DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY)
|
||
AND c.changes[0].confidence_level IN ('high', 'medium')
|
||
GROUP BY c.service, sc.total_changes
|
||
ORDER BY change_failure_rate DESC
|
||
LIMIT 10;" data-lang="text/x-sql">
|
||
<pre><code lang="text/x-sql"> WITH service_changes AS (
|
||
SELECT
|
||
service,
|
||
COUNT(DISTINCT change_id) as total_changes
|
||
FROM changes
|
||
WHERE timestamp >= DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY)
|
||
GROUP BY service
|
||
)
|
||
SELECT
|
||
c.service,
|
||
COUNT(DISTINCT c.correlation_id) as failures,
|
||
sc.total_changes,
|
||
(COUNT(DISTINCT c.correlation_id) / sc.total_changes) * 100 as change_failure_rate
|
||
FROM correlations c
|
||
JOIN service_changes sc ON c.service = sc.service
|
||
WHERE
|
||
c.failure_start_at >= DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY)
|
||
AND c.changes[0].confidence_level IN ('high', 'medium')
|
||
GROUP BY c.service, sc.total_changes
|
||
ORDER BY change_failure_rate DESC
|
||
LIMIT 10;</code></pre>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<h4><br></h4>
|
||
<h4>Team-Level MTTR Analysis</h4>
|
||
<div class="codeMirror-wrapper" contenteditable="false">
|
||
<div contenteditable="false">
|
||
<div class="codeHeader">
|
||
<div class="nameLanguage">
|
||
SQL
|
||
</div><i class="icon-cancel-circled-1 cm-remove"> </i>
|
||
</div>
|
||
<div class="codeMirror-code--wrapper" data-code=" SELECT
|
||
team_name,
|
||
AVG(duration_minutes) as avg_mttr,
|
||
COUNT(*) as incident_count
|
||
FROM correlations
|
||
JOIN services ON correlations.service = services.name
|
||
JOIN team_service_mapping ON services.id = team_service_mapping.service_id
|
||
WHERE
|
||
correlation_state IN ('confirmed', 'finalized')
|
||
AND changes[0].confidence_level = 'high'
|
||
AND failure_start_at >= DATE_SUB(CURRENT_DATE(), INTERVAL 90 DAY)
|
||
GROUP BY team_name
|
||
ORDER BY avg_mttr DESC;" data-lang="text/x-sql">
|
||
<pre><code lang="text/x-sql"> SELECT
|
||
team_name,
|
||
AVG(duration_minutes) as avg_mttr,
|
||
COUNT(*) as incident_count
|
||
FROM correlations
|
||
JOIN services ON correlations.service = services.name
|
||
JOIN team_service_mapping ON services.id = team_service_mapping.service_id
|
||
WHERE
|
||
correlation_state IN ('confirmed', 'finalized')
|
||
AND changes[0].confidence_level = 'high'
|
||
AND failure_start_at >= DATE_SUB(CURRENT_DATE(), INTERVAL 90 DAY)
|
||
GROUP BY team_name
|
||
ORDER BY avg_mttr DESC;</code></pre>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<h3><br></h3>
|
||
<h3>Risk Assessment</h3>
|
||
<p>CFR data can be used to build risk profiles for different types of changes, services, or deployment patterns:</p>
|
||
<div class="codeMirror-wrapper" contenteditable="false">
|
||
<div contenteditable="false">
|
||
<div class="codeHeader">
|
||
<div class="nameLanguage">
|
||
Python
|
||
</div><i class="icon-cancel-circled-1 cm-remove"> </i>
|
||
</div>
|
||
<div class="codeMirror-code--wrapper" data-code=" def calculate_deployment_risk(service_id, change_metadata):
|
||
# Get historical correlation data
|
||
recent_failures = correlation_client.get_recent_failures(
|
||
service_id=service_id,
|
||
days=30
|
||
)
|
||
|
||
# Calculate baseline risk from historical data
|
||
baseline_risk = min(80, len(recent_failures) * 10)
|
||
|
||
# Adjust for change size
|
||
change_size_factor = min(2.0, (change_metadata.lines_changed / 1000) * 0.5 + 1)
|
||
|
||
# Adjust for change type
|
||
if change_metadata.is_hotfix:
|
||
type_factor = 1.5
|
||
elif change_metadata.is_rollback:
|
||
type_factor = 0.7
|
||
else:
|
||
type_factor = 1.0
|
||
|
||
# Combine factors
|
||
risk_score = min(100, baseline_risk * change_size_factor * type_factor)
|
||
|
||
return {
|
||
'score': risk_score,
|
||
'level': risk_level_from_score(risk_score),
|
||
'historical_failures': len(recent_failures),
|
||
'recommendations': generate_risk_recommendations(risk_score)
|
||
}" data-lang="text/x-python">
|
||
<pre><code lang="text/x-python"> def calculate_deployment_risk(service_id, change_metadata):
|
||
# Get historical correlation data
|
||
recent_failures = correlation_client.get_recent_failures(
|
||
service_id=service_id,
|
||
days=30
|
||
)
|
||
|
||
# Calculate baseline risk from historical data
|
||
baseline_risk = min(80, len(recent_failures) * 10)
|
||
|
||
# Adjust for change size
|
||
change_size_factor = min(2.0, (change_metadata.lines_changed / 1000) * 0.5 + 1)
|
||
|
||
# Adjust for change type
|
||
if change_metadata.is_hotfix:
|
||
type_factor = 1.5
|
||
elif change_metadata.is_rollback:
|
||
type_factor = 0.7
|
||
else:
|
||
type_factor = 1.0
|
||
|
||
# Combine factors
|
||
risk_score = min(100, baseline_risk * change_size_factor * type_factor)
|
||
|
||
return {
|
||
'score': risk_score,
|
||
'level': risk_level_from_score(risk_score),
|
||
'historical_failures': len(recent_failures),
|
||
'recommendations': generate_risk_recommendations(risk_score)
|
||
}</code></pre>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<h2><br></h2>
|
||
<h2>Monitoring, Feedback, and Continuous Improvement</h2>
|
||
<h3>System Health Monitoring</h3>
|
||
<ul>
|
||
<li>Event ingestion rate and latency</li>
|
||
<li>Processing pipeline performance</li>
|
||
<li>Data quality metrics (completeness, freshness)</li>
|
||
<li>Dashboard load performance</li>
|
||
<li>Correlation accuracy</li>
|
||
</ul>
|
||
<h3>Feedback Loop for Algorithm Improvement</h3>
|
||
<p>Implement a simple feedback mechanism for engineers to verify correlations:</p>
|
||
<div class="codeMirror-wrapper" contenteditable="false">
|
||
<div contenteditable="false">
|
||
<div class="codeHeader">
|
||
<div class="nameLanguage">
|
||
JSON
|
||
</div><i class="icon-cancel-circled-1 cm-remove"> </i>
|
||
</div>
|
||
<div class="codeMirror-code--wrapper" data-code=" "feedback": {
|
||
"manually_verified": true,
|
||
"verification_user": "jane.engineer@example.com",
|
||
"verification_timestamp": "2023-05-16T09:12:45Z",
|
||
"notes": "Confirmed during postmortem that the deployment caused the outage"
|
||
}" data-lang="application/json">
|
||
<pre><code lang="application/json"> "feedback": {
|
||
"manually_verified": true,
|
||
"verification_user": "<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="dbb1bab5bef5beb5bcb2b5bebea99bbea3bab6abb7bef5b8b4b6">[email protected]</a>",
|
||
"verification_timestamp": "2023-05-16T09:12:45Z",
|
||
"notes": "Confirmed during postmortem that the deployment caused the outage"
|
||
}</code></pre>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p><br></p>
|
||
<p>Instead of free-text feedback, categorize rejection reasons:</p>
|
||
<ul>
|
||
<li>false_positive</li>
|
||
<li>confounding_change</li>
|
||
<li>duplicate_incident</li>
|
||
<li>delayed_signal</li>
|
||
</ul>
|
||
<p>By capturing this feedback, your correlation algorithms can continuously improve, learning from both successful and unsuccessful attributions. This human-in-the-loop approach combines the scalability of automated analysis with the nuanced understanding of experienced engineers.</p>
|
||
<h3>Success Criteria and Evaluation</h3>
|
||
<div class="table-responsive" style="border: none;">
|
||
<table border="1" cellpadding="0" cellspacing="0" class="fr-alternate-rows" style="max-width: 100%; width: auto; table-layout: fixed; display: table;" width="auto">
|
||
<thead>
|
||
<tr style="overflow-wrap: break-word; width: auto;" width="auto">
|
||
<th style="overflow-wrap: break-word; width: auto;" width="auto"><strong>Metric</strong></th>
|
||
<th style="overflow-wrap: break-word; width: auto;" width="auto"><strong>Target</strong></th>
|
||
<th style="overflow-wrap: break-word; width: auto;" width="auto"><strong>Description</strong></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr style="overflow-wrap: break-word; width: auto;" width="auto">
|
||
<td style="overflow-wrap: break-word; width: auto;" valign="top" width="auto">
|
||
<p>Data completeness</p></td>
|
||
<td style="overflow-wrap: break-word; width: auto;" valign="top" width="auto">
|
||
<p>>99.5%</p></td>
|
||
<td style="overflow-wrap: break-word; width: auto;" valign="top" width="auto">
|
||
<p>All expected events captured</p></td>
|
||
</tr>
|
||
<tr style="overflow-wrap: break-word; width: auto;" width="auto">
|
||
<td style="overflow-wrap: break-word; width: auto;" valign="top" width="auto">
|
||
<p>Correlation accuracy</p></td>
|
||
<td style="overflow-wrap: break-word; width: auto;" valign="top" width="auto">
|
||
<p>>85%</p></td>
|
||
<td style="overflow-wrap: break-word; width: auto;" valign="top" width="auto">
|
||
<p>Verified against manual review</p></td>
|
||
</tr>
|
||
<tr style="overflow-wrap: break-word; width: auto;" width="auto">
|
||
<td style="overflow-wrap: break-word; width: auto;" valign="top" width="auto">
|
||
<p>Dashboard usage</p></td>
|
||
<td style="overflow-wrap: break-word; width: auto;" valign="top" width="auto">
|
||
<p>>75% teams</p></td>
|
||
<td style="overflow-wrap: break-word; width: auto;" valign="top" width="auto">
|
||
<p>Weekly access logs</p></td>
|
||
</tr>
|
||
<tr style="overflow-wrap: break-word; width: auto;" width="auto">
|
||
<td style="overflow-wrap: break-word; width: auto;" valign="top" width="auto">
|
||
<p>CFR improvement</p></td>
|
||
<td style="overflow-wrap: break-word; width: auto;" valign="top" width="auto">
|
||
<p>10% QoQ</p></td>
|
||
<td style="overflow-wrap: break-word; width: auto;" valign="top" width="auto">
|
||
<p>Relative reduction in failures</p></td>
|
||
</tr>
|
||
<tr style="overflow-wrap: break-word; width: auto;" width="auto">
|
||
<td style="overflow-wrap: break-word; width: auto;" valign="top" width="auto">
|
||
<p>System availability</p></td>
|
||
<td style="overflow-wrap: break-word; width: auto;" valign="top" width="auto">
|
||
<p>>99.9% uptime</p></td>
|
||
<td style="overflow-wrap: break-word; width: auto;" valign="top" width="auto">
|
||
<p>Streaming and dashboard reliability</p></td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<h2><br></h2>
|
||
<h2>Visualization and Reporting</h2>
|
||
<p>Effective visualization is crucial for making CFR data actionable:</p>
|
||
<h3>Executive-Level Reporting</h3>
|
||
<ol>
|
||
<li><strong>Trend Analysis</strong>: Show CFR trends over time with quarterly targets</li>
|
||
<li><strong>Organization-Wide Benchmarking</strong>: Compare against industry standards from DORA research</li>
|
||
<li><strong>Business Impact Correlation</strong>: Relate CFR to customer satisfaction or revenue metrics</li>
|
||
</ol>
|
||
<h3>Team-Level Insights</h3>
|
||
<ol>
|
||
<li><strong>Service-Specific CFR</strong>: Break down by individual services</li>
|
||
<li><strong>Failure Categorization</strong>: Show distribution of failure types</li>
|
||
<li><strong>Change Volume vs. Failure Rate</strong>: Identify if failures correlate with change velocity</li>
|
||
</ol>
|
||
<h3>Engineer-Level Detail</h3>
|
||
<ol>
|
||
<li><strong>Recent Changes and Outcomes</strong>: Individual change history with success/failure status</li>
|
||
<li><strong>Postmortem Links</strong>: Direct connections to incident reviews</li>
|
||
<li><strong>Feedback Collection</strong>: Tools for submitting correlation corrections</li>
|
||
</ol>
|
||
<h2>Integrations and Extensibility</h2>
|
||
<h3>Service Catalog Integration</h3>
|
||
<p>A robust CFR implementation should integrate with your service catalog to:</p>
|
||
<ol>
|
||
<li><strong>Retrieve Service Context</strong>: Access service ownership, dependency relationships, and criticality information</li>
|
||
<li><strong>Update Service Health Metrics</strong>: Write reliability metrics back to the service catalog</li>
|
||
<li><strong>Map Dependency Impacts</strong>: Understand how changes in one service affect others in the ecosystem</li>
|
||
</ol>
|
||
<h3>Incident Management System Integration</h3>
|
||
<p>Establish bidirectional integration with incident management:</p>
|
||
<div class="codeMirror-wrapper" contenteditable="false">
|
||
<div contenteditable="false">
|
||
<div class="codeHeader">
|
||
<div class="nameLanguage">
|
||
Python
|
||
</div><i class="icon-cancel-circled-1 cm-remove"> </i>
|
||
</div>
|
||
<div class="codeMirror-code--wrapper" data-code=" def update_incident_with_correlation(incident_id, correlation):
|
||
"""Update incident record with correlation findings."""
|
||
if correlation['changes'] and correlation['changes'][0]['confidence_level'] in ['high', 'medium']:
|
||
change = correlation['changes'][0]
|
||
timeline_entry = {
|
||
'type': 'correlation_finding',
|
||
'timestamp': datetime.now().isoformat(),
|
||
'content': f"Potential causal change identified: {change['change_id']} with {change['confidence_level']} confidence",
|
||
'metadata': {
|
||
'correlation_id': correlation['correlation_id'],
|
||
'confidence': change['confidence_level'],
|
||
'score': change['confidence_score']
|
||
}
|
||
}
|
||
incident_client.add_timeline_entry(incident_id, timeline_entry)" data-lang="text/x-python">
|
||
<pre><code lang="text/x-python"> def update_incident_with_correlation(incident_id, correlation):
|
||
"""Update incident record with correlation findings."""
|
||
if correlation['changes'] and correlation['changes'][0]['confidence_level'] in ['high', 'medium']:
|
||
change = correlation['changes'][0]
|
||
timeline_entry = {
|
||
'type': 'correlation_finding',
|
||
'timestamp': datetime.now().isoformat(),
|
||
'content': f"Potential causal change identified: {change['change_id']} with {change['confidence_level']} confidence",
|
||
'metadata': {
|
||
'correlation_id': correlation['correlation_id'],
|
||
'confidence': change['confidence_level'],
|
||
'score': change['confidence_score']
|
||
}
|
||
}
|
||
incident_client.add_timeline_entry(incident_id, timeline_entry)</code></pre>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<h3><br></h3>
|
||
<h3>CI/CD Pipeline Integration</h3>
|
||
<p>Connect with <a href="https://dzone.com/articles/everything-you-need-to-know-about-cicd-pipeline-pa">CI/CD</a> systems to provide risk scoring for pending deployments:</p>
|
||
<div class="codeMirror-wrapper newest" contenteditable="false">
|
||
<div contenteditable="false">
|
||
<div class="codeHeader">
|
||
<div class="nameLanguage">
|
||
Python
|
||
</div><i class="icon-cancel-circled-1 cm-remove"> </i>
|
||
</div>
|
||
<div class="codeMirror-code--wrapper" data-code=" def calculate_deployment_risk(service_id, change_metadata):
|
||
"""Calculate risk score for a pending deployment."""
|
||
# Get historical correlation data
|
||
recent_failures = correlation_client.get_recent_failures(
|
||
service_id=service_id,
|
||
days=30
|
||
)
|
||
|
||
# Calculate baseline risk from historical data
|
||
baseline_risk = min(80, len(recent_failures) * 10)
|
||
|
||
# Adjust for change size
|
||
change_size_factor = min(2.0, (change_metadata.lines_changed / 1000) * 0.5 + 1)
|
||
|
||
# Adjust for change type
|
||
if change_metadata.is_hotfix:
|
||
type_factor = 1.5
|
||
elif change_metadata.is_rollback:
|
||
type_factor = 0.7
|
||
else:
|
||
type_factor = 1.0
|
||
|
||
# Combine factors
|
||
risk_score = min(100, baseline_risk * change_size_factor * type_factor)
|
||
|
||
return {
|
||
'score': risk_score,
|
||
'level': risk_level_from_score(risk_score),
|
||
'historical_failures': len(recent_failures),
|
||
'recommendations': generate_risk_recommendations(risk_score)
|
||
}" data-lang="text/x-python">
|
||
<pre><code lang="text/x-python"> def calculate_deployment_risk(service_id, change_metadata):
|
||
"""Calculate risk score for a pending deployment."""
|
||
# Get historical correlation data
|
||
recent_failures = correlation_client.get_recent_failures(
|
||
service_id=service_id,
|
||
days=30
|
||
)
|
||
|
||
# Calculate baseline risk from historical data
|
||
baseline_risk = min(80, len(recent_failures) * 10)
|
||
|
||
# Adjust for change size
|
||
change_size_factor = min(2.0, (change_metadata.lines_changed / 1000) * 0.5 + 1)
|
||
|
||
# Adjust for change type
|
||
if change_metadata.is_hotfix:
|
||
type_factor = 1.5
|
||
elif change_metadata.is_rollback:
|
||
type_factor = 0.7
|
||
else:
|
||
type_factor = 1.0
|
||
|
||
# Combine factors
|
||
risk_score = min(100, baseline_risk * change_size_factor * type_factor)
|
||
|
||
return {
|
||
'score': risk_score,
|
||
'level': risk_level_from_score(risk_score),
|
||
'historical_failures': len(recent_failures),
|
||
'recommendations': generate_risk_recommendations(risk_score)
|
||
}</code></pre>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<h2><br></h2>
|
||
<h2>Common Implementation Challenges</h2>
|
||
<h3>Data Quality Issues</h3>
|
||
<p>Inconsistent or missing data can significantly impact CFR accuracy. Common challenges include:</p>
|
||
<ol>
|
||
<li><strong>Inconsistent Rollback Labeling</strong>: Changes not properly marked as rollbacks or hotfixes</li>
|
||
<li><strong>Missing Incident Correlation</strong>: Failures not linked to their triggering changes</li>
|
||
<li><strong>Duplicate or Missed Data</strong>: Events counted multiple times or not at all</li>
|
||
</ol>
|
||
<p>Mitigate these issues by:</p>
|
||
<ul>
|
||
<li>Automating labeling via CI checks and GitHub Actions</li>
|
||
<li>Using time-window correlation with reasonable defaults</li>
|
||
<li>Implementing data validation and deduplication logic</li>
|
||
</ul>
|
||
<h3>Organizational Adoption</h3>
|
||
<p>Technical implementation is only half the battle. To drive organizational adoption:</p>
|
||
<ol>
|
||
<li><strong>Education</strong>: Hold team workshops on CFR interpretation and improvement strategies</li>
|
||
<li><strong>Incentives</strong>: Align team goals with CFR improvement</li>
|
||
<li><strong>Accessibility</strong>: Make dashboards intuitive and easily accessible</li>
|
||
<li><strong>Context</strong>: Provide context around the numbers with recommended actions</li>
|
||
</ol>
|
||
<h2>Future Directions: The Road to ML and Predictive CFR</h2>
|
||
<p>As your CFR implementation matures, consider these advanced applications:</p>
|
||
<h3>Machine Learning for Correlation</h3>
|
||
<p>As your dataset grows, consider implementing machine learning models to improve correlation accuracy:</p>
|
||
<ol>
|
||
<li><strong>Supervised Learning</strong>: Train models on verified correlations</li>
|
||
<li><strong>Feature Extraction</strong>: Extract meaningful signals from change metadata</li>
|
||
<li><strong>Service-Specific Patterns</strong>: Develop correlation patterns unique to each service</li>
|
||
</ol>
|
||
<h3>Causal Inference</h3>
|
||
<p>Move from correlation to causation modeling:</p>
|
||
<ul>
|
||
<li>Implement counterfactual analysis</li>
|
||
<li>Develop directed acyclic graphs (DAGs) using service dependency mapping</li>
|
||
<li>Apply temporal event sequencing models (e.g., Hawkes processes)</li>
|
||
</ul>
|
||
<h3>Predictive Analytics</h3>
|
||
<p>Use historical CFR data to build predictive models that can:</p>
|
||
<ul>
|
||
<li>Identify high-risk changes before deployment</li>
|
||
<li>Suggest optimal deployment windows</li>
|
||
<li>Recommend additional testing for risky changes</li>
|
||
<li>Generate proactive alerts for changes with high failure probability</li>
|
||
</ul>
|
||
<h3>Business Impact Analysis</h3>
|
||
<p>Extend CFR monitoring to include business impact metrics:</p>
|
||
<ul>
|
||
<li>Revenue impact per failure</li>
|
||
<li>Customer experience degradation</li>
|
||
<li>Brand reputation effects</li>
|
||
</ul>
|
||
<h2>Low-Code Automation for CFR Implementation</h2>
|
||
<p>For organizations looking to quickly implement CFR, several low-code automation frameworks can help:</p>
|
||
<ol>
|
||
<li><strong>Datadog's Service Catalog</strong>: Provides change tracking and incident management</li>
|
||
<li><strong>Harness Continuous Insights</strong>: Offers DORA metric tracking</li>
|
||
<li><strong>GitHub Actions Templates</strong>: Workflows for emitting standardized deployment events</li>
|
||
<li><strong>Grafana's DORA dashboard</strong>: Visualization for DORA metrics including CFR</li>
|
||
</ol>
|
||
<p>These tools significantly reduce the time required to set up a functioning CFR tracking system, allowing teams to focus on using the insights rather than building the infrastructure.</p>
|
||
<h2>Conclusion</h2>
|
||
<p>In the era of low-code automation, real-time delivery pipelines, and complex deployments, observability into change impact is a competitive advantage. Change Failure Rate is more than just a metric—it's a lens into your organization's software delivery health and a powerful tool for continuous improvement.</p>
|
||
<p>By implementing a comprehensive CFR tracking system built on a unified change model and enriched by confidence-based correlation, organizations gain insights that drive technical excellence, reduce customer-impacting incidents, and ultimately deliver more value to users.</p>
|
||
<p>The journey to effective CFR tracking may seem complex, but the rewards are substantial: greater reliability, faster recovery times, and more confident deployments. Whether you're using sophisticated custom solutions or low-code automation frameworks, the key is to start measuring, establish a baseline, and commit to data-driven improvement.</p>
|
||
<p>As the software industry continues to evolve toward more frequent deployments and complex distributed systems, robust CFR monitoring will become increasingly critical for maintaining stability while accelerating innovation. Organizations that excel at understanding and improving their Change Failure Rate will be better positioned to deliver reliable software at scale.</p></div>
|
||
</div>
|
||
|
||
<div id="bottom-bumper-container"></div>
|
||
<div class="article-tag-pill-container">
|
||
<span class="article-tag-pill">Engineering</span>
|
||
<span class="article-tag-pill">Continuous Integration/Deployment</span>
|
||
<span class="article-tag-pill">Database</span>
|
||
</div>
|
||
|
||
<div class="attribution">
|
||
<p>Opinions expressed by DZone contributors are their own.</p>
|
||
</div>
|
||
</div>
|
||
</article>
|
||
</div>
|
||
|
||
<div class="trending-separator goto"></div>
|
||
<aside class="trending goto" aria-labelledby="related-mobile-heading">
|
||
<h2 id="related-mobile-heading">Related</h2>
|
||
<ul>
|
||
<li class="item">
|
||
<a href="/articles/microservices-architecture-in-production" class="goto-link related-link">Microservices Architecture in Production: 7 Engineering Decisions That Determine Success or Failure</a>
|
||
</li>
|
||
<li class="item">
|
||
<a href="/articles/a-data-first-pattern-for-legacy-modernization" class="goto-link related-link">Tired of Reverse-Engineering Code? A Data-First Pattern for Legacy Modernization</a>
|
||
</li>
|
||
<li class="item">
|
||
<a href="/articles/mastering-the-art-of-data-engineering" class="goto-link related-link">Mastering the Art of Data Engineering to Support Billion-Dollar Tech Ecosystems</a>
|
||
</li>
|
||
<li class="item">
|
||
<a href="/articles/automating-databases-for-modern-devops-practices" class="goto-link related-link">Automating Databases for Modern DevOps Practices: A Guide to Common Patterns and Anti-Patterns for Database Automation Techniques</a>
|
||
</li>
|
||
</ul>
|
||
</aside>
|
||
</div>
|
||
|
||
<div id="div-gpt-ad-1435246566686-11" class="ads-bottom-leaderboard dz2_article_bottom" data-gpt-slot="bottom"></div>
|
||
<div class="layout-card widget-top-border partner-resources-block" style="width:100%; margin-bottom: 1em;">
|
||
<div class="main-container">
|
||
<div class="featured-header">
|
||
<h2>
|
||
<span>Partner Resources</span>
|
||
</h2>
|
||
</div>
|
||
<div class="partner-resources-container">
|
||
<div id="div-gpt-ad-1435246566686-5" class="resource-block dz2_partner_resource_link" data-gpt-slot="partner" data-gpt-position="pr1"></div>
|
||
<div id="div-gpt-ad-1435246566686-6" class="resource-block dz2_partner_resource_link" data-gpt-slot="partner" data-gpt-position="pr2"></div>
|
||
<div id="div-gpt-ad-1435246566686-7" class="resource-block dz2_partner_resource_link" data-gpt-slot="partner" data-gpt-position="pr3"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="bottom-sticky-ad-container">
|
||
<span class="bottom-sticky-ad-close" title="close"
|
||
x-on:click="$el.parentElement.classList.add('closed')">×</span>
|
||
<div id="div-gpt-ad-1635294790718-12" class="bottom-sticky-ad dz2_sticky_footer_leaderboard" data-gpt-slot="bottomStickyFooter"></div>
|
||
</div>
|
||
|
||
<div class="modal fade bd-example-modal-lg" id="modal-message" tabindex="-1" role="dialog" aria-hidden="true">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header"></div>
|
||
<div class="modal-body"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="comments-overlay"></div>
|
||
<div id="comment-box">
|
||
<div class="comment-box-wrapper">
|
||
<div id="comment-input-editor"></div>
|
||
</div>
|
||
<div class="info hidden"></div>
|
||
<div class="comments-content" style="display: none;">
|
||
<div class="comment-header">
|
||
<hr />
|
||
<span class="icon-comment">
|
||
<span class="numOfComments"></span> Comments
|
||
</span>
|
||
</div>
|
||
<div class="comments"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js"></script><script async>var articleTitle = 'Engineering Resilience Through Data: A Comprehensive Approach to Change Failure Rate Monitoring';
|
||
var articleUrl = 'https://dzone.com/articles/engineering-resilience-data-cfr-monitoring';
|
||
|
||
var retweetLink = document.querySelector('#tweet-link');
|
||
|
||
function retweet(event) {
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
|
||
var twitter = 'https://twitter.com/intent/tweet';
|
||
var params = '?text=' + encodeURIComponent(articleTitle) + '&url=' + articleUrl + '&ref=dzone.com&via=DZoneInc';
|
||
var win = window.open(twitter + params, '_blank');
|
||
win.focus();
|
||
}
|
||
|
||
retweetLink.addEventListener('click', retweet);
|
||
|
||
function showStatusMessage(options) {
|
||
var modal = document.getElementById('modal-message');
|
||
|
||
if(modal) {
|
||
modal.classList.add(options.type);
|
||
var modalBody = modal.querySelector('.modal-content .modal-body');
|
||
var modalHeader = modal.querySelector('.modal-content .modal-header');
|
||
|
||
modalHeader.innerText = options.header ? options.header : '';
|
||
modalBody.innerText = options.body ? options.body : '';
|
||
|
||
$(modal).modal('show');
|
||
|
||
$(modal).on('hidden.bs.modal', function () {
|
||
modal.classList.remove(options.type);
|
||
});
|
||
}
|
||
}
|
||
|
||
function showConfirmMessage(options) {
|
||
var modal = document.getElementById('modal-message');
|
||
|
||
if(modal) {
|
||
modal.classList.add(options.type);
|
||
var modalBody = modal.querySelector('.modal-content .modal-body');
|
||
var modalHeader = modal.querySelector('.modal-content .modal-header');
|
||
|
||
modalHeader.innerText = options.header ? options.header : '';
|
||
modalBody.innerText = options.body ? options.body : '';
|
||
|
||
if (options.textarea) {
|
||
var textareaDiv = document.createElement('div');
|
||
var textareaLabel = document.createElement('label');
|
||
textareaLabel.setAttribute('for', 'modal-textarea');
|
||
textareaLabel.innerText = options.textarea.label;
|
||
|
||
var textarea = document.createElement('textarea');
|
||
textarea.id = 'modal-textarea';
|
||
textarea.placeholder = (options.textarea.placeholder || '');
|
||
textarea.setAttribute('rows', (options.textarea.rows || 3));
|
||
textarea.classList.add('form-control', 'not-resizable');
|
||
|
||
if (options.textarea.maxlength) {
|
||
textarea.maxLength = options.textarea.maxlength;
|
||
}
|
||
|
||
textareaDiv.appendChild(textareaLabel);
|
||
textareaDiv.appendChild(textarea);
|
||
modalBody.appendChild(textareaDiv);
|
||
}
|
||
|
||
var btnContainer = document.createElement('div');
|
||
btnContainer.classList.add('btn-container');
|
||
|
||
var noBtn = document.createElement('button');
|
||
noBtn.innerText = options.noBtnText ? options.noBtnText : 'No';
|
||
noBtn.classList.add('no-btn');
|
||
|
||
var yesBtn = document.createElement('button');
|
||
yesBtn.innerText = options.yesBtnText ? options.yesBtnText : 'Yes';
|
||
yesBtn.classList.add('yes-btn');
|
||
|
||
btnContainer.appendChild(noBtn);
|
||
btnContainer.appendChild(yesBtn);
|
||
|
||
modalBody.appendChild(btnContainer);
|
||
|
||
$(modal).modal('show');
|
||
|
||
$(noBtn).one('click', function() {
|
||
$(modal).modal('hide');
|
||
|
||
if(options.noCallback) {
|
||
$(modal).one('hidden.bs.modal', function () {
|
||
options.noCallback();
|
||
});
|
||
}
|
||
});
|
||
|
||
$(yesBtn).one('click', function() {
|
||
$(modal).modal('hide');
|
||
|
||
if(options.yesCallback) {
|
||
$(modal).one('hidden.bs.modal', function () {
|
||
options.yesCallback();
|
||
});
|
||
}
|
||
});
|
||
|
||
$(modal).on('hidden.bs.modal', function () {
|
||
modal.classList.remove(options.type);
|
||
});
|
||
}
|
||
}
|
||
|
||
function getSuspensionBody() {
|
||
return 'We regret to inform you that your DZone account has been suspended for violating our Guidelines. ' +
|
||
'Please reach out to editors@dzone.com if you would like additional information.';
|
||
}</script>
|
||
<style>.engagement-overlay {
|
||
width: 100%;
|
||
height: 100%;
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
background: rgb(0, 0, 0, 0.5);
|
||
z-index: 10000;
|
||
}
|
||
|
||
.engagement-modal {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
position: fixed;
|
||
z-index: 10;
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.engagement-modal .inner {
|
||
background-color: white;
|
||
border-radius: 0.5em;
|
||
margin: auto;
|
||
border: 1px solid #cccccc;
|
||
}
|
||
|
||
.engagement-modal .header {
|
||
padding: 20px 50px 10px 30px;
|
||
position: relative;
|
||
}
|
||
|
||
.engagement-modal .title {
|
||
font-size: 20px;
|
||
font-weight: bold;
|
||
color: #333333;
|
||
}
|
||
|
||
.engagement-modal .title:hover {
|
||
text-decoration: none;
|
||
}
|
||
|
||
.engagement-modal .close {
|
||
font-size: 18px;
|
||
position: absolute;
|
||
top: 18px;
|
||
right: 20px;
|
||
width: 32px;
|
||
height: 32px;
|
||
background: none;
|
||
border: none;
|
||
opacity: 0.8;
|
||
font-weight: bold;
|
||
text-shadow: 0 1px 0 #fff;
|
||
}
|
||
|
||
.engagement-modal .close:hover {
|
||
background-color: rgba(146, 148, 151, 0.2);
|
||
color: #000;
|
||
border-radius: 50%;
|
||
}
|
||
|
||
.engagement-modal .close:after {
|
||
content: '\2715';
|
||
}
|
||
|
||
.engagement-modal .content {
|
||
padding: 0 30px 20px;
|
||
width: min(450px, 90vw);
|
||
height: min(450px, 90vw);
|
||
scrollbar-width: thin;
|
||
overflow: auto;
|
||
overscroll-behavior: contain;
|
||
}
|
||
|
||
.engagement-modal .loading-screen,
|
||
.engagement-modal .content .center-screen {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
height: 100%;
|
||
font-size: 18px;
|
||
}
|
||
|
||
.engagement-modal .content .center-screen a {
|
||
color: var(--acc-ffffff-blue-1);
|
||
font-weight: bold;
|
||
}
|
||
|
||
.engagement-modal .loading-screen i {
|
||
font-size: 24px;
|
||
}
|
||
|
||
.engagement-modal .media-left,
|
||
.engagement-modal .media-right {
|
||
display: table-cell;
|
||
vertical-align: top;
|
||
}
|
||
|
||
.engagement-modal .media-left {
|
||
padding-right: 10px;
|
||
}
|
||
|
||
.engagement-modal .media-right {
|
||
padding-left: 10px;
|
||
}
|
||
|
||
.engagement-modal .media a {
|
||
color: black;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.engagement-modal .avatar {
|
||
border-radius: 50%;
|
||
}
|
||
|
||
.engagement-modal .footer {
|
||
margin: 20px 0 0;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
background-color: unset !important;
|
||
border-top: none !important;
|
||
}
|
||
|
||
.engagement-modal .footer .btn {
|
||
border: 1px solid transparent;
|
||
}
|
||
|
||
.engagement-modal .footer .btn-primary {
|
||
color: #fff;
|
||
border-color: #2e6da4;
|
||
}
|
||
|
||
.engagement-modal .footer button {
|
||
background-color: var(--acc-ffffff-blue-2);
|
||
padding: 6px 12px;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.engagement-modal .footer button:hover {
|
||
filter: brightness(1.2);
|
||
}
|
||
|
||
@keyframes anim-spin {
|
||
0% {
|
||
transform: rotate(0deg);
|
||
}
|
||
100% {
|
||
transform: rotate(359deg);
|
||
}
|
||
}
|
||
|
||
.engagement-modal .anim-spin {
|
||
display: inline-block;
|
||
animation: anim-spin 2s infinite linear;
|
||
}</style>
|
||
<script>document.addEventListener('alpine:init', () => {
|
||
Alpine.data('engagementModal', (nodeId) => ({
|
||
|
||
nodeId: null,
|
||
|
||
init() {
|
||
this.nodeId = nodeId;
|
||
},
|
||
|
||
open() {
|
||
this.updateStore();
|
||
this.$store.article.engagement.open = true;
|
||
if (this.$store.article.engagement.page === 1 && !this.$store.article.engagement.unauthorized) {
|
||
this.$store.article.engagement.initializing = true;
|
||
this.fetchEngagements();
|
||
}
|
||
},
|
||
|
||
close() {
|
||
if (this.$store.article.engagement.open) {
|
||
this.$store.article.engagement.open = false;
|
||
}
|
||
},
|
||
|
||
updateStore() {
|
||
if (this.nodeId !== this.$store.article.id) {
|
||
this.$store.article.engagement.reset();
|
||
this.$store.article.id = this.nodeId;
|
||
}
|
||
},
|
||
|
||
fetchEngagements() {
|
||
if (this.$store.article.engagement.loading || !this.$store.article.engagement.additional) {
|
||
return;
|
||
}
|
||
|
||
this.$store.article.engagement.loading = true;
|
||
this.$store.global.getFromService('/services/nodes/' + this.$store.article.id + '/engagements?page=' + this.$store.article.engagement.page)
|
||
.then(res => {
|
||
res.json().then(data => {
|
||
for (let user of data.engagements) {
|
||
this.$store.article.engagement.users.push(user);
|
||
}
|
||
|
||
this.$store.article.engagement.additional = data.additional;
|
||
this.$store.article.engagement.page++;
|
||
});
|
||
})
|
||
.catch(err => {
|
||
err.json().then(data => {
|
||
if (data.error?.code === 4011) {
|
||
this.$store.article.engagement.unauthorized = true;
|
||
} else {
|
||
this.$store.modal_engagement_error.open();
|
||
}
|
||
});
|
||
})
|
||
.finally(() => {
|
||
// Allow the loader to gracefully exit
|
||
setTimeout(() => {
|
||
this.$store.article.engagement.loading = false;
|
||
this.$store.article.engagement.initializing = false;
|
||
}, 200);
|
||
});
|
||
},
|
||
|
||
formatJobData(user) {
|
||
if (user.profile.jobTitle && user.profile.companyName) {
|
||
return user.profile.jobTitle + ', ' + user.profile.companyName;
|
||
}
|
||
|
||
if (user.profile.jobTitle) {
|
||
return user.profile.jobTitle;
|
||
}
|
||
|
||
if (user.profile.companyName) {
|
||
return user.profile.companyName;
|
||
}
|
||
|
||
return null;
|
||
},
|
||
|
||
isDataUnavailable() {
|
||
return !this.$store.article.engagement.initializing && !this.$store.article.engagement.users.length;
|
||
},
|
||
|
||
isFooterShowing() {
|
||
return !this.$store.article.engagement.initializing
|
||
&& this.$store.article.engagement.additional
|
||
&& this.$store.article.engagement.page > 1;
|
||
}
|
||
}));
|
||
});
|
||
</script>
|
||
|
||
<div class="alpine-overlay" x-cloak x-show="$store.modal_engagement_error._open">
|
||
<div id="modal_engagement_error"
|
||
class="alpine-modal"
|
||
role="dialog"
|
||
tabindex="-1"
|
||
x-show="$store.modal_engagement_error._open"
|
||
x-on:click.self="$store.modal_engagement_error.dismissible && $store.modal_engagement_error.close()"
|
||
x-cloak
|
||
x-transition
|
||
>
|
||
<div class="dz-style modal-inner centered relative">
|
||
<button x-show="$store.modal_engagement_error.closeable"
|
||
class="btn-close"
|
||
x-on:click="$store.modal_engagement_error.close()"
|
||
aria-label="Close">
|
||
</button>
|
||
|
||
<div x-show="$store.modal_engagement_error.title" class="modal-header p-sm mt-sm mb-md">
|
||
<div class="title " x-text="$store.modal_engagement_error.title"></div>
|
||
</div>
|
||
|
||
<div class="modal-content py-md px-lg" x-bind:class="{ 'has-top-border': $store.modal_engagement_error.title }">
|
||
<p x-show="$store.modal_engagement_error.bodyText" class="my-none" x-text="$store.modal_engagement_error.bodyText"></p>
|
||
<p>The likes didn't load as expected. Please refresh the page and try again.</p>
|
||
</div>
|
||
|
||
<div x-show="$store.modal_engagement_error.options.length" class="modal-footer p-sm">
|
||
<template x-for="option in $store.modal_engagement_error.options">
|
||
<button x-show="option.canDisplay" x-text="option.text"
|
||
type="button"
|
||
x-bind:id="'button_modal_engagement_error_' + option.index"
|
||
x-bind:class="option.classList"
|
||
x-on:click="option.onClick"
|
||
x-bind:disabled="option.isDisabled"
|
||
></button>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
document.addEventListener("alpine:init", () => {
|
||
Alpine.store("modal_engagement_error", {
|
||
_open: false,
|
||
title: "Oops! Something Went Wrong",
|
||
bodyText: null,
|
||
options: [{"action":"dismiss","text":"Close"}],
|
||
closeable: true,
|
||
dismissible: true,
|
||
open(data) {
|
||
if (data) {
|
||
if (data.title) {
|
||
this.title = data.title;
|
||
}
|
||
if (data.bodyText) {
|
||
this.bodyText = data.bodyText;
|
||
}
|
||
if (data.options) {
|
||
this.options = data.options;
|
||
}
|
||
if (data.dismissAfterMs) {
|
||
setTimeout(() => this._open = false, data.dismissAfterMs);
|
||
}
|
||
}
|
||
this.updateOptions();
|
||
this._open = true;
|
||
},
|
||
close() {
|
||
// Manually click the buttons in which should run on dismiss.
|
||
// This is to keep the context. Calling this[option.action]() will not work.
|
||
for (let option of this.options) {
|
||
if (option.runOnDismiss) {
|
||
const element = document.querySelector('#button_modal_engagement_error_' + option.index);
|
||
element.click();
|
||
}
|
||
}
|
||
this._open = false;
|
||
},
|
||
updateOptions() {
|
||
const btnStyle = this.options.length === 2 ? ' btn-default' : '';
|
||
this.options = this.options.map((o, idx) => ({
|
||
...o,
|
||
index: idx,
|
||
classList: o.classList ? o.classList : (idx === 0 ? "btn btn-primary hov-brighten" : "btn hov-underline" + btnStyle),
|
||
onClick: function() {
|
||
if (o.action !== "dismiss") {
|
||
this[o.action]();
|
||
}
|
||
this.$store["modal_engagement_error"].close();
|
||
},
|
||
isDisabled: function() {
|
||
return Object.hasOwn(o, 'disabled') && this[o.disabled]();
|
||
},
|
||
canDisplay: function() {
|
||
return !Object.hasOwn(o, 'condition') || this[o.condition]();
|
||
}
|
||
}));
|
||
},
|
||
init() {
|
||
this.updateOptions();
|
||
}
|
||
});
|
||
});
|
||
</script>
|
||
|
||
<link rel="stylesheet" media="all" href="https://dz2cdn1.dzone.com/themes/dz20/ftl/footer/styles.css">
|
||
|
||
<div id="ftl-footer">
|
||
<div class="container-fluid footerOuter" style="padding-bottom: 90px;">
|
||
<div class="row">
|
||
<div class="col-md-12">
|
||
<div class="container">
|
||
<div class="row footer">
|
||
<div class="col-md-12 footerWidget">
|
||
<div class="row footerContainer footer">
|
||
<div class="left col-xs-12 col-sm-7">
|
||
<div class="col-xs-12 social-media-icons footer-mobile">
|
||
<ul class="icons-only">
|
||
<li class="rss-icon" id="rss-footer-1">
|
||
<a href="/pages/feeds" target="_blank" rel="noreferrer noopener">
|
||
<svg role="img" viewBox="0 0 24 24" class="w-4 h-4 mt-1.75 fill-current" aria-label="Follow our RSS feeds">
|
||
<title>RSS</title>
|
||
<path d="M19.199 24C19.199 13.467 10.533 4.8 0 4.8V0c13.165 0 24 10.835 24 24h-4.801zM3.291 17.415c1.814 0 3.293 1.479 3.293 3.295 0 1.813-1.485 3.29-3.301 3.29C1.47 24 0 22.526 0 20.71s1.475-3.294 3.291-3.295zM15.909 24h-4.665c0-6.169-5.075-11.245-11.244-11.245V8.09c8.727 0 15.909 7.184 15.909 15.91z"></path>
|
||
</svg>
|
||
</a>
|
||
</li>
|
||
<li class="twitter-icon">
|
||
<a href="https://twitter.com/DZoneInc" target="_blank" rel="noreferrer noopener">
|
||
<svg role="img" viewBox="0 0 24 24" aria-label="Follow us on X">
|
||
<title>X</title>
|
||
<path d="M14.234 10.162 22.977 0h-2.072l-7.591 8.824L7.251 0H.258l9.168 13.343L.258 24H2.33l8.016-9.318L16.749 24h6.993zm-2.837 3.299-.929-1.329L3.076 1.56h3.182l5.965 8.532.929 1.329 7.754 11.09h-3.182z"></path>
|
||
</svg>
|
||
</a>
|
||
</li>
|
||
<li class="facebook-icon">
|
||
<a href="https://www.facebook.com/DZoneInc" target="_blank" rel="noreferrer noopener">
|
||
<svg role="img" viewBox="0 0 24 24" aria-label="Follow us on Facebook">
|
||
<title>Facebook</title>
|
||
<path d="M9.101 23.691v-7.98H6.627v-3.667h2.474v-1.58c0-4.085 1.848-5.978 5.858-5.978.401 0 .955.042 1.468.103a8.68 8.68 0 0 1 1.141.195v3.325a8.623 8.623 0 0 0-.653-.036 26.805 26.805 0 0 0-.733-.009c-.707 0-1.259.096-1.675.309a1.686 1.686 0 0 0-.679.622c-.258.42-.374.995-.374 1.752v1.297h3.919l-.386 2.103-.287 1.564h-3.246v8.245C19.396 23.238 24 18.179 24 12.044c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.628 3.874 10.35 9.101 11.647Z"></path>
|
||
</svg>
|
||
</a>
|
||
</li>
|
||
<li class="linkedin-icon">
|
||
<a href="https://www.linkedin.com/company/dzone/" target="_blank"
|
||
rel="noreferrer noopener">
|
||
<svg viewBox="0 0 24 25" fill="none" aria-label="Follow us on LinkedIn">
|
||
<path d="M6.20062 21.2143H1.84688V7.194H6.20062V21.2143ZM4.02141 5.2815C2.62922 5.2815 1.5 4.12838 1.5 2.73619C1.5 2.06747 1.76565 1.42614 2.2385 0.953285C2.71136 0.48043 3.35269 0.214783 4.02141 0.214783C4.69012 0.214783 5.33145 0.48043 5.80431 0.953285C6.27716 1.42614 6.54281 2.06747 6.54281 2.73619C6.54281 4.12838 5.413 5.2815 4.02141 5.2815ZM22.4953 21.2143H18.1509V14.3893C18.1509 12.7628 18.1181 10.6768 15.8873 10.6768C13.6237 10.6768 13.2769 12.444 13.2769 14.2721V21.2143H8.92781V7.194H13.1034V9.1065H13.1644C13.7456 8.00494 15.1655 6.84244 17.2838 6.84244C21.69 6.84244 22.5 9.744 22.5 13.5128V21.2143H22.4953Z" fill="currentColor"></path>
|
||
</svg>
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="top-section col-xs-12">
|
||
<div class="col-xs-12 col-sm-6">
|
||
<p class="section-header">ABOUT US</p>
|
||
<ul class="link-group">
|
||
<li><a href="/pages/about" rel="noreferrer noopener">About DZone</a></li>
|
||
<li><a href="/cdn-cgi/l/email-protection#30434540405f424470544a5f5e551e535f5d" rel="noreferrer noopener">Support and feedback</a></li>
|
||
<li><a href="/pages/dzone-community-research">Community research</a></li>
|
||
</ul>
|
||
</div>
|
||
<div class="col-xs-12 col-sm-6">
|
||
<p class="section-header">ADVERTISE</p>
|
||
<ul class="link-group">
|
||
<li><a href="https://advertise.dzone.com" target="_blank" rel="noreferrer noopener">Advertise with DZone</a></li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="bottom-section col-xs-12">
|
||
<div class="col-xs-12 col-sm-6">
|
||
<p class="section-header">CONTRIBUTE ON DZONE</p>
|
||
<ul class="bottom-top-list link-group">
|
||
<li><a href="/articles/dzones-article-submission-guidelines">Article Submission Guidelines</a></li>
|
||
<li><a href="/pages/contribute" rel="noreferrer noopener">Become a Contributor</a></li>
|
||
<li><a href="/pages/core" rel="noreferrer noopener">Core Program</a></li>
|
||
<li><a href="/writers-zone" rel="noreferrer noopener">Visit the Writers' Zone</a></li>
|
||
</ul>
|
||
|
||
<p class="section-header">LEGAL</p>
|
||
<ul class="link-group">
|
||
<li><a href="https://technologyadvice.com/terms-conditions/" target="_blank" rel="noreferrer noopener">Terms of Service</a></li>
|
||
<li><a href="https://technologyadvice.com/privacy-policy/" target="_blank" rel="noreferrer noopener">Privacy Policy</a></li>
|
||
</ul>
|
||
</div>
|
||
<div class="col-xs-12 col-sm-6">
|
||
<p class="section-header">CONTACT US</p>
|
||
<ul class="link-group">
|
||
<li>3343 Perimeter Hill Drive</li>
|
||
<li>Suite 215</li>
|
||
<li>Nashville, TN 37211</li>
|
||
<li><a href="/cdn-cgi/l/email-protection#89fafcf9f9e6fbfdc9edf3e6e7eca7eae6e4" rel="noreferrer noopener"><span class="__cf_email__" data-cfemail="e99a9c9999869b9da98d9386878cc78a8684">[email protected]</span></a></li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="right col-xs-12 col-sm-5">
|
||
|
||
<p class="connect-text">Let's be friends:</p>
|
||
<div class="col-xs-12 social-media-icons footer-wide">
|
||
<ul class="icons-only">
|
||
<li class="rss-icon" id="rss-footer-1">
|
||
<a href="/pages/feeds" target="_blank" rel="noreferrer noopener">
|
||
<svg role="img" viewBox="0 0 24 24" aria-label="Follow our RSS feeds">
|
||
<title>RSS</title>
|
||
<path d="M19.199 24C19.199 13.467 10.533 4.8 0 4.8V0c13.165 0 24 10.835 24 24h-4.801zM3.291 17.415c1.814 0 3.293 1.479 3.293 3.295 0 1.813-1.485 3.29-3.301 3.29C1.47 24 0 22.526 0 20.71s1.475-3.294 3.291-3.295zM15.909 24h-4.665c0-6.169-5.075-11.245-11.244-11.245V8.09c8.727 0 15.909 7.184 15.909 15.91z"></path>
|
||
</svg>
|
||
</a>
|
||
</li>
|
||
<li class="twitter-icon">
|
||
<a href="https://twitter.com/DZoneInc" target="_blank" rel="noreferrer noopener">
|
||
<svg role="img" viewBox="0 0 24 24" aria-label="Follow us on X">
|
||
<title>X</title>
|
||
<path d="M14.234 10.162 22.977 0h-2.072l-7.591 8.824L7.251 0H.258l9.168 13.343L.258 24H2.33l8.016-9.318L16.749 24h6.993zm-2.837 3.299-.929-1.329L3.076 1.56h3.182l5.965 8.532.929 1.329 7.754 11.09h-3.182z"></path>
|
||
</svg>
|
||
</a>
|
||
</li>
|
||
<li class="facebook-icon">
|
||
<a href="https://www.facebook.com/DZoneInc" target="_blank" rel="noreferrer noopener">
|
||
<svg role="img" viewBox="0 0 24 24" aria-label="Follow us on Facebook">
|
||
<title>Facebook</title>
|
||
<path d="M9.101 23.691v-7.98H6.627v-3.667h2.474v-1.58c0-4.085 1.848-5.978 5.858-5.978.401 0 .955.042 1.468.103a8.68 8.68 0 0 1 1.141.195v3.325a8.623 8.623 0 0 0-.653-.036 26.805 26.805 0 0 0-.733-.009c-.707 0-1.259.096-1.675.309a1.686 1.686 0 0 0-.679.622c-.258.42-.374.995-.374 1.752v1.297h3.919l-.386 2.103-.287 1.564h-3.246v8.245C19.396 23.238 24 18.179 24 12.044c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.628 3.874 10.35 9.101 11.647Z"></path>
|
||
</svg>
|
||
</a>
|
||
</li>
|
||
<li class="linkedin-icon">
|
||
<a href="https://www.linkedin.com/company/dzone/" target="_blank"
|
||
rel="noreferrer noopener">
|
||
<svg viewBox="0 0 24 25" fill="none" aria-label="Follow us on LinkedIn">
|
||
<path d="M6.20062 21.2143H1.84688V7.194H6.20062V21.2143ZM4.02141 5.2815C2.62922 5.2815 1.5 4.12838 1.5 2.73619C1.5 2.06747 1.76565 1.42614 2.2385 0.953285C2.71136 0.48043 3.35269 0.214783 4.02141 0.214783C4.69012 0.214783 5.33145 0.48043 5.80431 0.953285C6.27716 1.42614 6.54281 2.06747 6.54281 2.73619C6.54281 4.12838 5.413 5.2815 4.02141 5.2815ZM22.4953 21.2143H18.1509V14.3893C18.1509 12.7628 18.1181 10.6768 15.8873 10.6768C13.6237 10.6768 13.2769 12.444 13.2769 14.2721V21.2143H8.92781V7.194H13.1034V9.1065H13.1644C13.7456 8.00494 15.1655 6.84244 17.2838 6.84244C21.69 6.84244 22.5 9.744 22.5 13.5128V21.2143H22.4953Z" fill="currentColor"></path>
|
||
</svg>
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js"></script><script>
|
||
const articleId = 3566073;
|
||
const likes = 3;
|
||
const assetDomain = 'https://dz2cdn1.dzone.com';
|
||
const codemirrorVars = {
|
||
modeURI: 'https://dz2cdn1.dzone.com/themes/dz20/lib/codemirror/mode/',
|
||
requiredScripts: [
|
||
'https://dz2cdn1.dzone.com/themes/dz20/lib/codemirror/lib/codemirror.js',
|
||
'https://dz2cdn1.dzone.com/themes/dz20/lib/codemirror/addon/mode/overlay.js',
|
||
'https://dz2cdn1.dzone.com/themes/dz20/lib/codemirror/addon/mode/multiplex.js',
|
||
'https://dz2cdn1.dzone.com/themes/dz20/lib/codemirror/mode/meta.js'
|
||
]
|
||
};
|
||
|
||
const gptTags = {
|
||
'zone': 'DevOps and CI/CD',
|
||
'topicTag': 'Engineering,CI/CD,Database',
|
||
'company': '',
|
||
'siteSection': 'Zones',
|
||
'articleCategory': 'analysis',
|
||
'nodeID': '3566073',
|
||
'authorID': '5305532',
|
||
'publishYear': '2025',
|
||
'publishMonth': '06',
|
||
'jobRole': '',
|
||
'companySize': '',
|
||
'env': 'prod'
|
||
};
|
||
|
||
const minCommentChar = 10;
|
||
</script>
|
||
<script async>const width = window.innerWidth;
|
||
|
||
const metadata = {
|
||
'top': {
|
||
'position': 'top',
|
||
'slot': 'dz2_article_billboard_new',
|
||
},
|
||
'sponsorLogo': {
|
||
'position': 'zoneHomepage',
|
||
'slot': 'dz2_homepage_sponsor_logo',
|
||
'refreshable': false
|
||
},
|
||
'sidebar1': {
|
||
'position': 'sidebar',
|
||
'slot': 'dz2_article_halfpage_new',
|
||
'minWidthToShow': 1024
|
||
},
|
||
'topBumper': {
|
||
'position': 'top',
|
||
'slot': 'dz2_bumper_text_ad',
|
||
'minWidthToShow': 1024
|
||
},
|
||
'bottomBumper': {
|
||
'position': 'bottom',
|
||
'slot': 'dz2_bumper_text_ad',
|
||
'minWidthToShow': 1024
|
||
},
|
||
'bottom': {
|
||
'position': 'bottom',
|
||
'slot': 'dz2_article_bottom',
|
||
},
|
||
'bottomStickyFooter': {
|
||
'position': 'sticky',
|
||
'slot': 'dz2_sticky_footer_leaderboard',
|
||
},
|
||
'partner': {
|
||
'slot': 'dz2_partner_resource_link',
|
||
},
|
||
'branded': {
|
||
'slot': 'dz2_branded_content',
|
||
'refreshable': false
|
||
},
|
||
'topicBillboard': {
|
||
'position': ['top', 'zoneHomepage'],
|
||
'slot': 'dz2_topic_billboard',
|
||
},
|
||
'listPageSidebar': {
|
||
'position': 'sidebar',
|
||
'slot': 'dz2_list_page_sidebar',
|
||
'minWidthToShow': 1024
|
||
},
|
||
'topListPageLeaderboard2': {
|
||
'position': ['top', 'zoneList'],
|
||
'slot': 'dz2_list_page_leaderboard_2',
|
||
},
|
||
'bottomListPageLeaderboard2': {
|
||
'position': ['bottom', 'zoneList'],
|
||
'slot': 'dz2_list_page_leaderboard_2',
|
||
},
|
||
'homepageLeaderboard': {
|
||
'position': 'top',
|
||
'slot': 'dz2_homepage_leaderboard',
|
||
},
|
||
'homepageLeaderboard2': {
|
||
'position': 'bottom',
|
||
'slot': 'dz2_homepage_leaderboard_2',
|
||
},
|
||
'skybox': {
|
||
'position': 'above nav',
|
||
'slot': 'dz_skybox',
|
||
'refreshable': false
|
||
},
|
||
'inline': {
|
||
'position': 'inline',
|
||
'slot': 'dz2_inline-article-display',
|
||
}
|
||
};
|
||
|
||
var campaign = new URLSearchParams(window.location.search).get("adTargeting_campaign");
|
||
|
||
window.googletag = window.googletag || { cmd: [] };
|
||
|
||
if (campaign) {
|
||
window.googletag.cmd.push(function() {
|
||
window.googletag.setConfig({ targeting: { campaign }});
|
||
});
|
||
}
|
||
|
||
let lastHeader = null;
|
||
|
||
const topContainer = document.querySelector('#top-bumper-container');
|
||
const bottomContainer = document.querySelector('#bottom-bumper-container');
|
||
|
||
function GAM_getPersistentValue(key) {
|
||
const stored = JSON.parse(localStorage.getItem(key));
|
||
|
||
if (!stored) {
|
||
return null;
|
||
}
|
||
|
||
const { value, expiration } = stored;
|
||
|
||
if (expiration && Date.now() >= expiration) {
|
||
localStorage.removeItem(key);
|
||
return null;
|
||
}
|
||
|
||
return value;
|
||
}
|
||
|
||
function GAM_setPersistentValue(key, value, expiration) {
|
||
// 5 minutes from now
|
||
if (!expiration) {
|
||
expiration = Date.now() + 300000;
|
||
}
|
||
|
||
const storedValue = JSON.stringify({
|
||
value: value,
|
||
expiration: expiration
|
||
});
|
||
|
||
localStorage.setItem(key, storedValue);
|
||
|
||
return value;
|
||
}
|
||
|
||
function GAM_synchronousRequest(params) {
|
||
const xhr = new XMLHttpRequest();
|
||
xhr.open('GET', params.url, false);
|
||
xhr.setRequestHeader("Content-Type", "application/json");
|
||
|
||
if (typeof params.auth_header !== "undefined") {
|
||
xhr.setRequestHeader("Authorization", params.auth_header);
|
||
}
|
||
|
||
xhr.send(null);
|
||
if (xhr.status === 200) {
|
||
return xhr.responseText;
|
||
} else {
|
||
throw new Error('Request failed: ' + xhr.statusText);
|
||
}
|
||
}
|
||
|
||
function GAM_fetch_data(params) {
|
||
let stored_data = GAM_getPersistentValue(params.storage_key);
|
||
|
||
if (stored_data === null) {
|
||
try {
|
||
const response = GAM_synchronousRequest(params);
|
||
const data = JSON.parse(response);
|
||
|
||
// Store and expire after 30 minutes
|
||
return GAM_setPersistentValue(params.storage_key, data, Date.now() + 1800000);
|
||
} catch (error) {
|
||
console.error('Could not get ' + params.storage_key + ' data: ' + error);
|
||
return null;
|
||
}
|
||
} else {
|
||
return stored_data;
|
||
}
|
||
}
|
||
|
||
function GAM_setUpSixSenseTargeting() {
|
||
let meData = GAM_fetch_data({
|
||
url: "https://link.technologyadvice.com/_me",
|
||
storage_key: "ta_me_data"
|
||
});
|
||
let sixSenseData = GAM_fetch_data({
|
||
url: "https://epsilon.6sense.com/v3/company/details",
|
||
auth_header: "Token d20a1b0e892442270cbc4cb6801c0160d28af04c",
|
||
storage_key: "ta_6s_data"
|
||
});
|
||
|
||
function meDataIncludes(i) {
|
||
return meData.tags.includes(i);
|
||
}
|
||
|
||
if (typeof meData !== "undefined" && meData !== null) {
|
||
window.googletag.pubads().setTargeting("visitor_id", meData.vid);
|
||
window.googletag.pubads().setTargeting("user_agent", meData.user_agent);
|
||
|
||
var tags_mapping = {
|
||
"is_datacenter": ["site.is-datacenter"],
|
||
"is_suspected_bot": ["site.suspected-bad-bot", "site.bad-bot"],
|
||
"is_ta_user": ["site.is-ta-user"],
|
||
"is_crawler": ["site.user-agent-blocked"],
|
||
"is_ad_blocked": ["site.is-ad-blocked"],
|
||
};
|
||
|
||
for (var key in tags_mapping) {
|
||
if (tags_mapping[key].some(meDataIncludes)) {
|
||
window.googletag.pubads().setTargeting(key, 'true');
|
||
}
|
||
}
|
||
}
|
||
|
||
if (typeof sixSenseData !== "undefined" && sixSenseData !== null) {
|
||
var segment_ids = [];
|
||
if (sixSenseData.segments && sixSenseData.segments.ids && sixSenseData.segments.ids.length) {
|
||
sixSenseData.segments.ids.forEach(function (v) {
|
||
segment_ids.push(v.toString());
|
||
});
|
||
}
|
||
if (typeof segment_ids !== "undefined" && segment_ids.length > 0) {
|
||
window.googletag.pubads().setTargeting("segment_ids_6si", segment_ids);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (width >= 1024 && (topContainer || bottomContainer)) {
|
||
// Dynamically create bumper ad slots for non-mobile devices.
|
||
// Prevents ugly dividers being rendered on empty ad slots (mobile)
|
||
const topBumper = document.createElement('div');
|
||
topBumper.id = 'div-gpt-ad-1435246566686-3';
|
||
topBumper.classList.add('article-bumper', 'article-bumper-top', 'dz2_bumper_text_ad');
|
||
topBumper.setAttribute('data-gpt-slot', 'topBumper');
|
||
|
||
topContainer.appendChild(topBumper);
|
||
|
||
const bottomBumper = document.createElement('div');
|
||
bottomBumper.id = 'div-gpt-ad-1435246566686-4';
|
||
bottomBumper.classList.add('article-bumper', 'article-bumper-bottom', 'dz2_bumper_text_ad');
|
||
bottomBumper.setAttribute('data-gpt-slot', 'bottomBumper');
|
||
|
||
bottomContainer.appendChild(bottomBumper);
|
||
}
|
||
|
||
if (gptTags.zone) {
|
||
gptTags.zone = gptTags.zone.replaceAll(/[\s/]/g, '_')
|
||
.replaceAll(/[^a-zA-Z0-9_]/g, '')
|
||
.toLowerCase();
|
||
}
|
||
|
||
makeAds();
|
||
|
||
function handleSkybox() {
|
||
const skybox = document.querySelector('div.skybox');
|
||
if (skybox) {
|
||
document.body.classList.add('skybox-auto-collapse');
|
||
// observe classlist changes for offsetting sticky ads
|
||
const observer = new MutationObserver((mutations) => {
|
||
mutations.forEach((mutation) => {
|
||
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
|
||
let offset = 0;
|
||
if (!document.body.classList.contains('skybox-closed') && (
|
||
document.body.classList.contains('ccad-skybox-manualcollapse')
|
||
|| document.body.classList.contains('ccad-skybox-manualexpand')
|
||
)) {
|
||
offset = skybox.getBoundingClientRect().height;
|
||
}
|
||
let eligibleAds = [
|
||
{ element: document.querySelector('.content-right-images'), offset: 108 },
|
||
{ element: document.querySelector('.trending-sidebar'), offset: 88 },
|
||
{ element: document.querySelector('.trending'), offset: 88 }
|
||
];
|
||
for (let ad of eligibleAds) {
|
||
if (ad.element) {
|
||
ad.element.style.top = (ad.offset + offset) + 'px';
|
||
}
|
||
}
|
||
}
|
||
});
|
||
});
|
||
observer.observe(document.body, { attributes: true });
|
||
}
|
||
}
|
||
|
||
function isHeaderEligible(header) {
|
||
const range = document.createRange();
|
||
range.setStartAfter(lastHeader);
|
||
range.setEndBefore(header);
|
||
|
||
return range.toString().trim().length >= 600;
|
||
}
|
||
|
||
function placeInlineAds() {
|
||
const excludedContent = document.querySelectorAll('#ftl-article.branded-content, #ftl-article.sponsored');
|
||
|
||
if (excludedContent.length > 0) {
|
||
return;
|
||
}
|
||
|
||
const headers = Array.from(document.querySelectorAll('.content-html h2')).splice(1);
|
||
|
||
let globalIndex = 12;
|
||
|
||
for (let i = 0; i < headers.length; i++) {
|
||
if (!lastHeader || isHeaderEligible(headers[i])) {
|
||
const element = document.createElement('div');
|
||
element.id = 'div-gpt-ad-1435246566686-' + globalIndex;
|
||
element.classList.add('inline-display', 'dz2_inline-article-display');
|
||
element.setAttribute('data-gpt-slot', 'inline');
|
||
headers[i].before(element);
|
||
lastHeader = element;
|
||
globalIndex++;
|
||
}
|
||
}
|
||
}
|
||
|
||
function makeAds() {
|
||
handleSkybox();
|
||
placeInlineAds();
|
||
|
||
const script = document.createElement('script');
|
||
script.src = 'https://i6ByW9Zmz4ncxhHkb.ay.delivery/manager/i6ByW9Zmz4ncxhHkb';
|
||
script.type = 'text/javascript';
|
||
script.referrerPolicy = 'no-referrer-when-downgrade';
|
||
|
||
document.head.appendChild(script);
|
||
|
||
window.googletag.cmd.push(function() {
|
||
const containers = document.querySelectorAll('div[data-gpt-slot]');
|
||
|
||
for (let container of containers) {
|
||
const div = container.getAttribute('data-gpt-slot');
|
||
const meta = metadata[div];
|
||
|
||
if (meta.minWidthToShow && width < meta.minWidthToShow) {
|
||
continue;
|
||
}
|
||
|
||
window.googletag.pubads().setTargeting('hostname', window.location.hostname);
|
||
|
||
Object.keys(gptTags).forEach(function(key) {
|
||
window.googletag.pubads().setTargeting(key, gptTags[key]);
|
||
});
|
||
|
||
window.googletag.pubads().addEventListener('slotRenderEnded', (event) => {
|
||
window.requestAnimationFrame(() => {
|
||
var slotId = event.slot.getSlotElementId();
|
||
|
||
if (!slotId.includes('__ayManagerEnv__')) {
|
||
return;
|
||
}
|
||
|
||
var className = slotId.split('__ayManagerEnv__')[0];
|
||
var slotName = className.replace('dz2_', '').replace('dz_', '').replace(/_\d+$/, '');
|
||
var unitName = meta.slot.replace('dz2_', '').replace('dz_', '').replace(/_\d+$/, '');
|
||
|
||
if (slotName !== unitName) {
|
||
return;
|
||
}
|
||
|
||
var elem = document.getElementById(slotId);
|
||
|
||
if (!elem) {
|
||
console.warn('Ad element missing', slotId);
|
||
return;
|
||
}
|
||
|
||
// Ad unit did not fill, collapse slot
|
||
if (event.isEmpty && elem.parentElement) {
|
||
elem.parentElement.style.display = 'none';
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
GAM_setUpSixSenseTargeting();
|
||
});
|
||
}
|
||
</script>
|
||
<script async>// InMobi Choice. Consent Manager Tag v3.0 (for TCF 2.2)
|
||
!function(){var e=window.location.hostname,t=document.createElement("script"),n=document.getElementsByTagName("script")[0],a="https://cmp.inmobi.com".concat("/choice/","vPn77x7pBG57Y","/","dzone.com","/choice.js?tag_version=V3"),p=0;t.async=!0,t.type="text/javascript",t.src=a,n.parentNode.insertBefore(t,n),function(){for(var e,t="__tcfapiLocator",n=[],a=window;a;){try{if(a.frames[t]){e=a;break}}catch(e){}if(a===window.top)break;a=a.parent}e||(!function e(){var n=a.document,p=!!a.frames[t];if(!p)if(n.body){var s=n.createElement("iframe");s.style.cssText="display:none",s.name=t,n.body.appendChild(s)}else setTimeout(e,5);return!p}(),a.__tcfapi=function(){var e,t=arguments;if(!t.length)return n;if("setGdprApplies"===t[0])t.length>3&&2===t[2]&&"boolean"==typeof t[3]&&(e=t[3],"function"==typeof t[2]&&t[2]("set",!0));else if("ping"===t[0]){var a={gdprApplies:e,cmpLoaded:!1,cmpStatus:"stub"};"function"==typeof t[2]&&t[2](a)}else"init"===t[0]&&"object"==typeof t[3]&&(t[3]=Object.assign(t[3],{tag_version:"V3"})),n.push(t)},a.addEventListener("message",(function(e){var t="string"==typeof e.data,n={};try{n=t?JSON.parse(e.data):e.data}catch(e){}var a=n.__tcfapiCall;a&&window.__tcfapi(a.command,a.version,(function(n,p){var s={__tcfapiReturn:{returnValue:n,success:p,callId:a.callId}};t&&(s=JSON.stringify(s)),e&&e.source&&e.source.postMessage&&e.source.postMessage(s,"*")}),a.parameter)}),!1))}(),function(){const e=["2:tcfeuv2","6:uspv1","7:usnatv1","8:usca","9:usvav1","10:uscov1","11:usutv1","12:usctv1"];window.__gpp_addFrame=function(e){if(!window.frames[e])if(document.body){var t=document.createElement("iframe");t.style.cssText="display:none",t.name=e,document.body.appendChild(t)}else window.setTimeout(window.__gpp_addFrame,10,e)},window.__gpp_stub=function(){var t=arguments;if(__gpp.queue=__gpp.queue||[],__gpp.events=__gpp.events||[],!t.length||1==t.length&&"queue"==t[0])return __gpp.queue;if(1==t.length&&"events"==t[0])return __gpp.events;var n=t[0],a=t.length>1?t[1]:null,p=t.length>2?t[2]:null;if("ping"===n)a({gppVersion:"1.1",cmpStatus:"stub",cmpDisplayStatus:"hidden",signalStatus:"not ready",supportedAPIs:e,cmpId:10,sectionList:[],applicableSections:[-1],gppString:"",parsedSections:{}},!0);else if("addEventListener"===n){"lastId"in __gpp||(__gpp.lastId=0),__gpp.lastId++;var s=__gpp.lastId;__gpp.events.push({id:s,callback:a,parameter:p}),a({eventName:"listenerRegistered",listenerId:s,data:!0,pingData:{gppVersion:"1.1",cmpStatus:"stub",cmpDisplayStatus:"hidden",signalStatus:"not ready",supportedAPIs:e,cmpId:10,sectionList:[],applicableSections:[-1],gppString:"",parsedSections:{}}},!0)}else if("removeEventListener"===n){for(var i=!1,o=0;o<__gpp.events.length;o++)if(__gpp.events[o].id==p){__gpp.events.splice(o,1),i=!0;break}a({eventName:"listenerRemoved",listenerId:p,data:i,pingData:{gppVersion:"1.1",cmpStatus:"stub",cmpDisplayStatus:"hidden",signalStatus:"not ready",supportedAPIs:e,cmpId:10,sectionList:[],applicableSections:[-1],gppString:"",parsedSections:{}}},!0)}else"hasSection"===n?a(!1,!0):"getSection"===n||"getField"===n?a(null,!0):__gpp.queue.push([].slice.apply(t))},window.__gpp_msghandler=function(e){var t="string"==typeof e.data;try{var n=t?JSON.parse(e.data):e.data}catch(e){n=null}if("object"==typeof n&&null!==n&&"__gppCall"in n){var a=n.__gppCall;window.__gpp(a.command,(function(n,p){var s={__gppReturn:{returnValue:n,success:p,callId:a.callId}};e.source.postMessage(t?JSON.stringify(s):s,"*")}),"parameter"in a?a.parameter:null,"version"in a?a.version:"1.1")}},"__gpp"in window&&"function"==typeof window.__gpp||(window.__gpp=window.__gpp_stub,window.addEventListener("message",window.__gpp_msghandler,!1),window.__gpp_addFrame("__gppLocator"))}();var s=function(){var e=arguments;typeof window.__uspapi!==s&&setTimeout((function(){void 0!==window.__uspapi&&window.__uspapi.apply(window.__uspapi,e)}),500)};if(void 0===window.__uspapi){window.__uspapi=s;var i=setInterval((function(){p++,window.__uspapi===s&&p<3?console.warn("USP is not accessible"):clearInterval(i)}),6e3)}}();
|
||
</script>
|
||
|
||
<script async>
|
||
(function(w, d, s, l, i) {
|
||
w[l] = w[l] || [];
|
||
w[l].push({'gtm.start': new Date().getTime(), event: 'gtm.js'});
|
||
var f = d.getElementsByTagName(s)[0], j = d.createElement(s), dl = l != 'dataLayer' ? '&l=' + l : '';
|
||
j.async = true;
|
||
j.src = 'https://www.googletagmanager.com/gtm.js?id=' + i + dl;
|
||
f.parentNode.insertBefore(j,f);
|
||
})(window, document, 'script', 'dataLayer', 'GTM-K25QL22');
|
||
</script>
|
||
|
||
<script>
|
||
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
|
||
ga('create', 'UA-410289-1', 'auto');
|
||
ga('require', 'linkid', 'linkid.js');
|
||
ga('require', 'GTM-TSD9TZP');
|
||
ga('set', 'siteSpeedSampleRate', 25);
|
||
</script>
|
||
<script async src="https://www.google-analytics.com/analytics.js"></script>
|
||
<script async>var analytics = {
|
||
'dimension1': 'DevOps and CI/CD',
|
||
'dimension2': 'article/analysis',
|
||
'dimension3': '2025-06-12',
|
||
'dimension4': '0',
|
||
'dimension5': '',
|
||
'dimension7': 'Engineering, CI/CD, Database',
|
||
'dimension8': 'tokonson',
|
||
'dimension9': 'undefined',
|
||
'dimension10': 'Upstart'
|
||
};
|
||
|
||
if (window.ga) {
|
||
Object.keys(analytics).forEach(function(key) {
|
||
window.ga('set', key, analytics[key]);
|
||
});
|
||
|
||
window.ga('send', 'pageview');
|
||
}</script>
|
||
|
||
<script src="https://dz2cdn1.dzone.com/themes/dz20/lib/static/jquery/jquery.min.js"></script>
|
||
<script async src="https://dz2cdn1.dzone.com/themes/dz20/lib/static/bootstrap/bootstrap.min.js"></script>
|
||
|
||
<script>
|
||
function loadScript(src) {
|
||
return new Promise(function (resolve, reject) {
|
||
const s = document.createElement('script');
|
||
s.src = src;
|
||
s.onload = resolve;
|
||
s.onerror = reject;
|
||
document.head.appendChild(s);
|
||
});
|
||
}
|
||
|
||
function loadStyle(href) {
|
||
const link = document.createElement('link');
|
||
link.rel = 'stylesheet';
|
||
link.href = href;
|
||
document.head.appendChild(link);
|
||
}
|
||
|
||
function loadScriptsSync(deferred) {
|
||
var p = Promise.resolve();
|
||
for (var i = 0; i < deferred.length; i++) {
|
||
let script = deferred[i];
|
||
p = p.then(function() {
|
||
return loadScript(script);
|
||
});
|
||
}
|
||
return p;
|
||
}
|
||
|
||
function loadStyles() {
|
||
const deferred = [
|
||
'https://dz2cdn1.dzone.com/themes/dz20/lib/codemirror/lib/codemirror.css',
|
||
'https://dz2cdn1.dzone.com/themes/dz20/ftl/comments/styles.css',
|
||
'https://dz2cdn1.dzone.com/themes/dz20/lib/froala3/css/froala_editor.pkgd.min.css',
|
||
'https://dz2cdn1.dzone.com/themes/dz20/lib/froala3/css/themes/gray.min.css',
|
||
'https://dz2cdn1.dzone.com/themes/dz20/ftl/article/mini-profile.css'
|
||
];
|
||
|
||
for (var i = 0; i < deferred.length; i++) {
|
||
loadStyle(deferred[i]);
|
||
}
|
||
}
|
||
|
||
window.addEventListener('load', function(_event) {
|
||
loadStyles();
|
||
|
||
loadScriptsSync([
|
||
'https://dz2cdn1.dzone.com/themes/dz20/lib/lazysizes.min.js',
|
||
'https://dz2cdn1.dzone.com/themes/dz20/ftl/article/codeblocks.js',
|
||
'https://dz2cdn1.dzone.com/themes/dz20/ftl/article/activity-bar.js',
|
||
'https://dz2cdn1.dzone.com/themes/dz20/lib/froala3/js/froala_editor.pkgd.min.js',
|
||
'https://dz2cdn1.dzone.com/themes/dz20/ftl/froala/content.js',
|
||
'https://dz2cdn1.dzone.com/themes/dz20/ftl/comments/content.js',
|
||
'https://dz2cdn1.dzone.com/themes/dz20/ftl/article/content.js'
|
||
]);
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|