Back to the library

Retry at a fixed interval

Variant: Fixed interval

The same client and the same failing server, retrying every 0.8s forever. It uses the identical time axis as the backoff variant, so the evenly spaced ticks are directly comparable. The load meter sheds less between attempts than each attempt puts back, so the trough never approaches the floor — the retries are what hold the server down. Show it beside the backoff version; the contrast is the argument.

diagraminfrastructure

The source

A complete HTML document. Paste it into an empty .html file and it plays — no stylesheet, script, font, or image to fetch.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Retry at a fixed interval</title>
    <style>
      :root {
        --ivory: #faf9f5;
        --slate: #141413;
        --clay: #d97757;
        --oat: #e3dacc;
        --rust: #b04a3f;
        --gray-500: #87867f;
        --loop: 5.2s;
      }

      * {
        box-sizing: border-box;
        margin: 0;
        padding: 0;
      }

      body {
        min-height: 100vh;
        display: grid;
        place-items: center;
        background: var(--ivory);
        font-family:
          system-ui,
          -apple-system,
          'Segoe UI',
          Roboto,
          sans-serif;
        color: var(--slate);
      }

      .diagram {
        width: 260px;
        height: 112px;
      }

      .node {
        fill: none;
        stroke: var(--oat);
        stroke-width: 1.5;
      }

      .link,
      .axis {
        stroke: var(--oat);
        stroke-width: 1.5;
        stroke-linecap: round;
      }

      .cap {
        font-size: 8.5px;
        letter-spacing: 0.08em;
        fill: var(--gray-500);
        text-anchor: middle;
      }

      .meter-track {
        fill: var(--oat);
      }

      /* Same 40 user-units-per-second axis as the backoff variants, so the two
         can be read side by side. Here every gap is 32 units — 0.8s, forever. */
      .progress {
        stroke: var(--clay);
        stroke-width: 2.5;
        stroke-dasharray: 192;
        stroke-dashoffset: 192;
        animation: sweep var(--loop) linear infinite;
      }

      .tick {
        fill: var(--rust);
        transform-box: fill-box;
        transform-origin: bottom center;
        opacity: 0;
      }

      .t1 {
        animation: t1 var(--loop) linear infinite;
      }
      .t2 {
        animation: t2 var(--loop) linear infinite;
      }
      .t3 {
        animation: t3 var(--loop) linear infinite;
      }
      .t4 {
        animation: t4 var(--loop) linear infinite;
      }
      .t5 {
        animation: t5 var(--loop) linear infinite;
      }
      .t6 {
        animation: t6 var(--loop) linear infinite;
      }

      .req {
        fill: var(--clay);
        opacity: 0;
        animation: req var(--loop) linear infinite;
      }

      .rej {
        fill: var(--rust);
        opacity: 0;
        animation: rej var(--loop) linear infinite;
      }

      /* Load drains at the same 0.45/s as the backoff variants. A fixed 0.8s
         gap gives it 0.66s of draining before the next arrival lands, so it
         sheds 0.30 — and the arrival puts exactly 0.30 back. Every cycle is a
         wash: the trough never approaches the floor and the server is held
         down by the retries themselves. */
      .load {
        fill: var(--rust);
        transform-box: fill-box;
        transform-origin: left center;
        animation: load var(--loop) linear infinite;
      }

      .down {
        fill: none;
        stroke: var(--rust);
        stroke-width: 1.5;
      }

      .live {
        animation: live var(--loop) linear infinite;
      }

      @keyframes live {
        0% {
          opacity: 0;
        }
        1.54%,
        93.27% {
          opacity: 1;
        }
        99.04%,
        100% {
          opacity: 0;
        }
      }

      @keyframes sweep {
        0% {
          stroke-dashoffset: 192;
        }
        92.31%,
        100% {
          stroke-dashoffset: 0;
        }
      }

      /* Flight is linear — a packet in transit is constant motion. The ticks
         sprout from the axis in 160ms on a strong ease-out; that is the only
         thing here that is a state change rather than travel. */
      @keyframes t1 {
        0%,
        3.85% {
          opacity: 0;
          transform: scaleY(0.72);
          animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
        }
        6.93%,
        100% {
          opacity: 1;
          transform: scaleY(1);
        }
      }

      @keyframes t2 {
        0%,
        19.23% {
          opacity: 0;
          transform: scaleY(0.72);
          animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
        }
        22.31%,
        100% {
          opacity: 1;
          transform: scaleY(1);
        }
      }

      @keyframes t3 {
        0%,
        34.62% {
          opacity: 0;
          transform: scaleY(0.72);
          animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
        }
        37.7%,
        100% {
          opacity: 1;
          transform: scaleY(1);
        }
      }

      @keyframes t4 {
        0%,
        50% {
          opacity: 0;
          transform: scaleY(0.72);
          animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
        }
        53.08%,
        100% {
          opacity: 1;
          transform: scaleY(1);
        }
      }

      @keyframes t5 {
        0%,
        65.38% {
          opacity: 0;
          transform: scaleY(0.72);
          animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
        }
        68.46%,
        100% {
          opacity: 1;
          transform: scaleY(1);
        }
      }

      @keyframes t6 {
        0%,
        80.77% {
          opacity: 0;
          transform: scaleY(0.72);
          animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
        }
        83.85%,
        100% {
          opacity: 1;
          transform: scaleY(1);
        }
      }

      @keyframes req {
        0%,
        3.85% {
          transform: translateX(0);
          opacity: 0;
        }
        4.27% {
          opacity: 1;
        }
        7.77% {
          opacity: 1;
        }
        8.08% {
          transform: translateX(120px);
          opacity: 0;
        }
        8.09%,
        19.23% {
          transform: translateX(0);
          opacity: 0;
        }
        19.65% {
          opacity: 1;
        }
        23.15% {
          opacity: 1;
        }
        23.46% {
          transform: translateX(120px);
          opacity: 0;
        }
        23.47%,
        34.62% {
          transform: translateX(0);
          opacity: 0;
        }
        35.04% {
          opacity: 1;
        }
        38.54% {
          opacity: 1;
        }
        38.85% {
          transform: translateX(120px);
          opacity: 0;
        }
        38.86%,
        50% {
          transform: translateX(0);
          opacity: 0;
        }
        50.42% {
          opacity: 1;
        }
        53.92% {
          opacity: 1;
        }
        54.23% {
          transform: translateX(120px);
          opacity: 0;
        }
        54.24%,
        65.38% {
          transform: translateX(0);
          opacity: 0;
        }
        65.8% {
          opacity: 1;
        }
        69.31% {
          opacity: 1;
        }
        69.62% {
          transform: translateX(120px);
          opacity: 0;
        }
        69.63%,
        80.77% {
          transform: translateX(0);
          opacity: 0;
        }
        81.19% {
          opacity: 1;
        }
        84.69% {
          opacity: 1;
        }
        85% {
          transform: translateX(120px);
          opacity: 0;
        }
        85.01%,
        100% {
          transform: translateX(0);
          opacity: 0;
        }
      }

      @keyframes rej {
        0%,
        8.08% {
          transform: translateX(120px);
          opacity: 0;
        }
        8.5% {
          opacity: 1;
        }
        12% {
          opacity: 1;
        }
        12.31% {
          transform: translateX(0);
          opacity: 0;
        }
        12.32%,
        23.46% {
          transform: translateX(120px);
          opacity: 0;
        }
        23.88% {
          opacity: 1;
        }
        27.38% {
          opacity: 1;
        }
        27.69% {
          transform: translateX(0);
          opacity: 0;
        }
        27.7%,
        38.85% {
          transform: translateX(120px);
          opacity: 0;
        }
        39.27% {
          opacity: 1;
        }
        42.77% {
          opacity: 1;
        }
        43.08% {
          transform: translateX(0);
          opacity: 0;
        }
        43.09%,
        54.23% {
          transform: translateX(120px);
          opacity: 0;
        }
        54.65% {
          opacity: 1;
        }
        58.15% {
          opacity: 1;
        }
        58.46% {
          transform: translateX(0);
          opacity: 0;
        }
        58.47%,
        69.62% {
          transform: translateX(120px);
          opacity: 0;
        }
        70.04% {
          opacity: 1;
        }
        73.54% {
          opacity: 1;
        }
        73.85% {
          transform: translateX(0);
          opacity: 0;
        }
        73.86%,
        85% {
          transform: translateX(120px);
          opacity: 0;
        }
        85.42% {
          opacity: 1;
        }
        88.92% {
          opacity: 1;
        }
        89.23% {
          transform: translateX(0);
          opacity: 0;
        }
        89.24%,
        100% {
          transform: translateX(120px);
          opacity: 0;
        }
      }

      @keyframes load {
        0% {
          transform: scaleX(0.79);
        }
        8.08% {
          transform: scaleX(0.6);
          animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
        }
        10.77% {
          transform: scaleX(0.9);
        }
        23.46% {
          transform: scaleX(0.6);
          animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
        }
        26.15% {
          transform: scaleX(0.9);
        }
        38.85% {
          transform: scaleX(0.6);
          animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
        }
        41.54% {
          transform: scaleX(0.9);
        }
        54.23% {
          transform: scaleX(0.6);
          animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
        }
        56.92% {
          transform: scaleX(0.9);
        }
        69.62% {
          transform: scaleX(0.6);
          animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
        }
        72.31% {
          transform: scaleX(0.9);
        }
        85% {
          transform: scaleX(0.6);
          animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
        }
        87.69% {
          transform: scaleX(0.9);
        }
        99.04% {
          transform: scaleX(0.63);
        }
        100% {
          transform: scaleX(0.79);
        }
      }

      @media (prefers-reduced-motion: reduce) {
        .live,
        .progress,
        .tick,
        .req,
        .rej,
        .load {
          animation: none;
        }

        /* Reduced motion keeps the lesson: evenly spaced ticks and a meter that
           never drains. Only the down state breathes. */
        .progress {
          stroke-dashoffset: 0;
        }

        .tick {
          opacity: 1;
          transform: none;
        }

        .req,
        .rej {
          opacity: 0;
        }

        .load {
          transform: scaleX(0.9);
        }

        .down {
          animation: strain 2.6s ease-in-out infinite;
        }

        @keyframes strain {
          0%,
          100% {
            opacity: 1;
          }
          50% {
            opacity: 0.45;
          }
        }
      }
    </style>
  </head>
  <body>
    <svg
      class="diagram"
      viewBox="0 0 260 112"
      role="img"
      aria-label="A client retries a failing server at a fixed interval: the gaps never grow, load never drains, and every attempt is rejected"
    >
      <g class="structure">
        <line class="link" x1="70" y1="29" x2="190" y2="29" />
        <rect class="node" x="14" y="12" width="56" height="34" rx="7" />
        <rect class="node" x="190" y="12" width="56" height="34" rx="7" />
        <rect class="meter-track" x="198" y="36.5" width="40" height="3.5" rx="1.75" />
        <line class="axis" x1="14" y1="100" x2="246" y2="100" />
      </g>

      <g class="live">
        <rect class="load" x="198" y="36.5" width="40" height="3.5" rx="1.75" />
        <rect class="down" x="190" y="12" width="56" height="34" rx="7" />

        <line class="progress" x1="14" y1="100" x2="206" y2="100" />

        <rect class="tick t1" x="20.75" y="86" width="2.5" height="14" />
        <rect class="tick t2" x="52.75" y="86" width="2.5" height="14" />
        <rect class="tick t3" x="84.75" y="86" width="2.5" height="14" />
        <rect class="tick t4" x="116.75" y="86" width="2.5" height="14" />
        <rect class="tick t5" x="148.75" y="86" width="2.5" height="14" />
        <rect class="tick t6" x="180.75" y="86" width="2.5" height="14" />

        <circle class="req" cx="70" cy="29" r="4.5" />
        <circle class="rej" cx="70" cy="29" r="4.5" />
      </g>

      <g class="labels">
        <text class="cap" x="42" y="29">CLIENT</text>
        <text class="cap" x="218" y="29">SERVER</text>
      </g>
    </svg>
  </body>
</html>

The variant family

Same idea, one axis moved. Compare them side by side before you commit.