Back to the library

Query plan, early exit

Variant: Early exit

The same scan at the same 280ms cadence, but the query has a LIMIT of three. The result set fills at row five, its outline snaps closed, and the cursor stops dead instead of walking off the end; the five rows below the cut drop to 45% because they are never read. The halt is the whole animation — a scan is not slow, finishing one is.

diagramdatabasesvg

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>Query plan — early exit</title>
    <style>
      :root {
        --ivory: #faf9f5;
        --slate: #141413;
        --clay: #d97757;
        --oat: #e3dacc;
        --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
        --cycle: 3s;
      }

      * {
        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);
      }

      svg {
        width: 280px;
        max-width: 100%;
        height: auto;
        display: block;
      }

      .row {
        fill: var(--oat);
      }

      .read,
      .hit {
        fill: var(--clay);
        opacity: 0;
      }

      /* Same 280ms-per-row cadence as the full scan — this plan is not faster
         per row, it simply stops sooner. Keeping the pitch identical is what
         makes the two readable side by side. */
      .read {
        animation: read-flash var(--cycle) linear infinite;
      }

      .read:nth-child(1) {
        animation-delay: 0.3s;
      }
      .read:nth-child(2) {
        animation-delay: 0.58s;
      }
      .read:nth-child(3) {
        animation-delay: 0.86s;
      }
      .read:nth-child(4) {
        animation-delay: 1.14s;
      }
      .read:nth-child(5) {
        animation-delay: 1.42s;
      }

      @keyframes read-flash {
        0% {
          opacity: 0;
          animation-timing-function: var(--ease-out);
        }
        4.1% {
          opacity: 0.55;
        }
        22.7%,
        100% {
          opacity: 0;
        }
      }

      .hit-a {
        animation: hit-a var(--cycle) linear infinite;
      }

      .hit-b {
        animation: hit-b var(--cycle) linear infinite;
      }

      .hit-c {
        animation: hit-c var(--cycle) linear infinite;
      }

      @keyframes hit-a {
        0%,
        14.1% {
          opacity: 0;
          animation-timing-function: var(--ease-out);
        }
        18%,
        88.3% {
          opacity: 1;
        }
        95%,
        100% {
          opacity: 0;
        }
      }

      @keyframes hit-b {
        0%,
        32.7% {
          opacity: 0;
          animation-timing-function: var(--ease-out);
        }
        36.6%,
        88.3% {
          opacity: 1;
        }
        95%,
        100% {
          opacity: 0;
        }
      }

      @keyframes hit-c {
        0%,
        51.3% {
          opacity: 0;
          animation-timing-function: var(--ease-out);
        }
        55.4%,
        88.3% {
          opacity: 1;
        }
        95%,
        100% {
          opacity: 0;
        }
      }

      /* The five rows below the cut are never read. Dropping them to 45% after
         the halt is the payoff: the plan bought its speed by not looking. */
      .unread {
        animation: unread-dim var(--cycle) linear infinite;
      }

      @keyframes unread-dim {
        0%,
        66% {
          opacity: 1;
          animation-timing-function: var(--ease-out);
        }
        74.6%,
        88.3% {
          opacity: 0.45;
        }
        95%,
        100% {
          opacity: 1;
        }
      }

      .probe path {
        fill: var(--clay);
      }

      .probe-fade {
        animation: probe-fade var(--cycle) linear infinite;
      }

      @keyframes probe-fade {
        0% {
          opacity: 0;
          animation-timing-function: var(--ease-out);
        }
        10%,
        88.3% {
          opacity: 1;
        }
        95%,
        100% {
          opacity: 0;
        }
      }

      /* steps(5, jump-none) lands the cursor on rows 1 through 5 and leaves it
         parked on the fifth. It does not walk off the end — that stop is the
         whole animation. */
      .probe-step {
        animation: probe-walk var(--cycle) linear infinite;
      }

      @keyframes probe-walk {
        0%,
        10% {
          transform: translateY(0);
          animation-timing-function: steps(5, jump-none);
        }
        47.4%,
        100% {
          transform: translateY(56px);
        }
      }

      .stop-bar {
        fill: var(--clay);
        opacity: 0;
        transform-box: fill-box;
        transform-origin: left center;
        animation: stop-in var(--cycle) linear infinite;
      }

      @keyframes stop-in {
        0%,
        63.3% {
          opacity: 0;
          transform: scaleX(0.9);
          animation-timing-function: var(--ease-out);
        }
        69.3%,
        88.3% {
          opacity: 1;
          transform: scaleX(1);
        }
        95%,
        100% {
          opacity: 0;
          transform: scaleX(1);
        }
      }

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

      .slot {
        fill: none;
        stroke: var(--slate);
        stroke-width: 1;
        stroke-dasharray: 3 3;
        opacity: 0.22;
      }

      .slot-full {
        fill: none;
        stroke: var(--clay);
        stroke-width: 1.5;
        opacity: 0;
        animation: slot-full var(--cycle) linear infinite;
      }

      @keyframes slot-full {
        0%,
        61.3% {
          opacity: 0;
          animation-timing-function: var(--ease-out);
        }
        66.6%,
        88.3% {
          opacity: 1;
        }
        95%,
        100% {
          opacity: 0;
        }
      }

      .chip {
        fill: var(--clay);
        opacity: 0;
        transform-box: fill-box;
        transform-origin: center;
      }

      .chip-a {
        animation: chip-a var(--cycle) linear infinite;
      }

      .chip-b {
        animation: chip-b var(--cycle) linear infinite;
      }

      .chip-c {
        animation: chip-c var(--cycle) linear infinite;
      }

      @keyframes chip-a {
        0%,
        15.3% {
          opacity: 0;
          transform: translateX(-14px) scale(0.96);
          animation-timing-function: var(--ease-out);
        }
        22.7%,
        88.3% {
          opacity: 1;
          transform: translateX(0) scale(1);
        }
        95%,
        100% {
          opacity: 0;
          transform: translateX(0) scale(0.97);
        }
      }

      @keyframes chip-b {
        0%,
        33.9% {
          opacity: 0;
          transform: translateX(-14px) scale(0.96);
          animation-timing-function: var(--ease-out);
        }
        41.3%,
        88.3% {
          opacity: 1;
          transform: translateX(0) scale(1);
        }
        95%,
        100% {
          opacity: 0;
          transform: translateX(0) scale(0.97);
        }
      }

      @keyframes chip-c {
        0%,
        52.7% {
          opacity: 0;
          transform: translateX(-14px) scale(0.96);
          animation-timing-function: var(--ease-out);
        }
        60%,
        88.3% {
          opacity: 1;
          transform: translateX(0) scale(1);
        }
        95%,
        100% {
          opacity: 0;
          transform: translateX(0) scale(0.97);
        }
      }

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

      .meter-tick {
        fill: var(--ivory);
      }

      .meter-fill {
        fill: var(--clay);
        transform-box: fill-box;
        transform-origin: left center;
        animation: meter-fill var(--cycle) linear infinite;
      }

      @keyframes meter-fill {
        0%,
        10% {
          transform: scaleX(0);
          opacity: 1;
          animation-timing-function: steps(5, jump-start);
        }
        56.6%,
        88.3% {
          transform: scaleX(0.5);
          opacity: 1;
        }
        95% {
          transform: scaleX(0.5);
          opacity: 0;
        }
        95.2%,
        100% {
          transform: scaleX(0);
          opacity: 0;
        }
      }

      @media (prefers-reduced-motion: reduce) {
        .probe-fade,
        .probe-step {
          animation: none;
          opacity: 0;
        }

        .stop-bar {
          animation: stop-still var(--cycle) linear infinite;
        }

        @keyframes stop-still {
          0%,
          63.3% {
            opacity: 0;
            transform: scaleX(1);
          }
          69.3%,
          88.3% {
            opacity: 1;
            transform: scaleX(1);
          }
          95%,
          100% {
            opacity: 0;
            transform: scaleX(1);
          }
        }

        .chip-a {
          animation: chip-still-a var(--cycle) linear infinite;
        }

        .chip-b {
          animation: chip-still-b var(--cycle) linear infinite;
        }

        .chip-c {
          animation: chip-still-c var(--cycle) linear infinite;
        }

        @keyframes chip-still-a {
          0%,
          15.3% {
            opacity: 0;
          }
          22.7%,
          88.3% {
            opacity: 1;
          }
          95%,
          100% {
            opacity: 0;
          }
        }

        @keyframes chip-still-b {
          0%,
          33.9% {
            opacity: 0;
          }
          41.3%,
          88.3% {
            opacity: 1;
          }
          95%,
          100% {
            opacity: 0;
          }
        }

        @keyframes chip-still-c {
          0%,
          52.7% {
            opacity: 0;
          }
          60%,
          88.3% {
            opacity: 1;
          }
          95%,
          100% {
            opacity: 0;
          }
        }

        /* Cost stated, not travelled: five of ten rows read. */
        .meter-fill {
          animation: none;
          transform: scaleX(0.5);
        }
      }
    </style>
  </head>
  <body>
    <svg
      viewBox="0 0 280 200"
      role="img"
      aria-label="A scan that stops after five rows because the three-row result set is already full"
    >
      <g>
        <rect class="row" x="64" y="24" width="112" height="10" rx="2" />
        <rect class="row" x="64" y="38" width="112" height="10" rx="2" />
        <rect class="row" x="64" y="52" width="112" height="10" rx="2" />
        <rect class="row" x="64" y="66" width="112" height="10" rx="2" />
        <rect class="row" x="64" y="80" width="112" height="10" rx="2" />
      </g>

      <g class="unread">
        <rect class="row" x="64" y="94" width="112" height="10" rx="2" />
        <rect class="row" x="64" y="108" width="112" height="10" rx="2" />
        <rect class="row" x="64" y="122" width="112" height="10" rx="2" />
        <rect class="row" x="64" y="136" width="112" height="10" rx="2" />
        <rect class="row" x="64" y="150" width="112" height="10" rx="2" />
      </g>

      <g>
        <rect class="read" x="64" y="24" width="112" height="10" rx="2" />
        <rect class="read" x="64" y="38" width="112" height="10" rx="2" />
        <rect class="read" x="64" y="52" width="112" height="10" rx="2" />
        <rect class="read" x="64" y="66" width="112" height="10" rx="2" />
        <rect class="read" x="64" y="80" width="112" height="10" rx="2" />
      </g>

      <rect class="hit hit-a" x="64" y="24" width="112" height="10" rx="2" />
      <rect class="hit hit-b" x="64" y="52" width="112" height="10" rx="2" />
      <rect class="hit hit-c" x="64" y="80" width="112" height="10" rx="2" />

      <rect class="stop-bar" x="58" y="91" width="124" height="2" rx="1" />

      <g class="probe">
        <g class="probe-fade">
          <g class="probe-step">
            <path d="M46 24 L58 29 L46 34 Z" />
          </g>
        </g>
      </g>

      <path class="feed" d="M180 85 L192 85" />
      <rect class="slot" x="194" y="58" width="40" height="54" rx="4" />
      <rect class="slot-full" x="194" y="58" width="40" height="54" rx="4" />
      <rect class="chip chip-a" x="200" y="66" width="28" height="10" rx="2" />
      <rect class="chip chip-b" x="200" y="84" width="28" height="10" rx="2" />
      <rect class="chip chip-c" x="200" y="102" width="28" height="10" rx="2" />

      <rect class="meter-track" x="64" y="176" width="112" height="5" rx="2.5" />
      <rect class="meter-fill" x="64" y="176" width="112" height="5" rx="2.5" />
      <g class="meter-tick">
        <rect x="75.2" y="176" width="1.6" height="5" />
        <rect x="86.4" y="176" width="1.6" height="5" />
        <rect x="97.6" y="176" width="1.6" height="5" />
        <rect x="108.8" y="176" width="1.6" height="5" />
        <rect x="120" y="176" width="1.6" height="5" />
        <rect x="131.2" y="176" width="1.6" height="5" />
        <rect x="142.4" y="176" width="1.6" height="5" />
        <rect x="153.6" y="176" width="1.6" height="5" />
        <rect x="164.8" y="176" width="1.6" height="5" />
      </g>
    </svg>
  </body>
</html>

The variant family

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