diff --git a/presentation/.presently.json b/presentation/.presently.json new file mode 100644 index 0000000..8c36f36 --- /dev/null +++ b/presentation/.presently.json @@ -0,0 +1,6 @@ +{ + "current_index": 13, + "elapsed": 0, + "running": false, + "started": false +} \ No newline at end of file diff --git a/presentation/audio/010-welcome.webm b/presentation/audio/010-welcome.webm new file mode 100644 index 0000000..e33d3f2 Binary files /dev/null and b/presentation/audio/010-welcome.webm differ diff --git a/presentation/audio/015-falcon-cluster.webm b/presentation/audio/015-falcon-cluster.webm new file mode 100644 index 0000000..502ddf4 Binary files /dev/null and b/presentation/audio/015-falcon-cluster.webm differ diff --git a/presentation/audio/020-problem/010-placement.webm b/presentation/audio/020-problem/010-placement.webm new file mode 100644 index 0000000..80af0b6 Binary files /dev/null and b/presentation/audio/020-problem/010-placement.webm differ diff --git a/presentation/audio/020-problem/020-existing-strategies.webm b/presentation/audio/020-problem/020-existing-strategies.webm new file mode 100644 index 0000000..5b63eee Binary files /dev/null and b/presentation/audio/020-problem/020-existing-strategies.webm differ diff --git a/presentation/audio/020-problem/022-endpoint-load-balancer.webm b/presentation/audio/020-problem/022-endpoint-load-balancer.webm new file mode 100644 index 0000000..743b004 Binary files /dev/null and b/presentation/audio/020-problem/022-endpoint-load-balancer.webm differ diff --git a/presentation/audio/020-problem/025-over-admission.webm b/presentation/audio/020-problem/025-over-admission.webm new file mode 100644 index 0000000..db51f72 Binary files /dev/null and b/presentation/audio/020-problem/025-over-admission.webm differ diff --git a/presentation/audio/020-problem/027-tail-latency.webm b/presentation/audio/020-problem/027-tail-latency.webm new file mode 100644 index 0000000..76d7151 Binary files /dev/null and b/presentation/audio/020-problem/027-tail-latency.webm differ diff --git a/presentation/audio/020-problem/030-desirable-state.webm b/presentation/audio/020-problem/030-desirable-state.webm new file mode 100644 index 0000000..4166456 Binary files /dev/null and b/presentation/audio/020-problem/030-desirable-state.webm differ diff --git a/presentation/audio/030-fantail/010-architecture.webm b/presentation/audio/030-fantail/010-architecture.webm new file mode 100644 index 0000000..e792201 Binary files /dev/null and b/presentation/audio/030-fantail/010-architecture.webm differ diff --git a/presentation/audio/030-fantail/020-permits.webm b/presentation/audio/030-fantail/020-permits.webm new file mode 100644 index 0000000..e94e6bc Binary files /dev/null and b/presentation/audio/030-fantail/020-permits.webm differ diff --git a/presentation/audio/030-fantail/030-queues.webm b/presentation/audio/030-fantail/030-queues.webm new file mode 100644 index 0000000..a974132 Binary files /dev/null and b/presentation/audio/030-fantail/030-queues.webm differ diff --git a/presentation/audio/040-results/010-clean-capacity.webm b/presentation/audio/040-results/010-clean-capacity.webm new file mode 100644 index 0000000..eccda1a Binary files /dev/null and b/presentation/audio/040-results/010-clean-capacity.webm differ diff --git a/presentation/audio/040-results/020-overload.webm b/presentation/audio/040-results/020-overload.webm new file mode 100644 index 0000000..8ab914d Binary files /dev/null and b/presentation/audio/040-results/020-overload.webm differ diff --git a/presentation/audio/050-conclusion/010-summary.webm b/presentation/audio/050-conclusion/010-summary.webm new file mode 100644 index 0000000..bf6d693 Binary files /dev/null and b/presentation/audio/050-conclusion/010-summary.webm differ diff --git a/presentation/bake.rb b/presentation/bake.rb new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/presentation/bake.rb @@ -0,0 +1 @@ + diff --git a/presentation/gems.locked b/presentation/gems.locked new file mode 100644 index 0000000..cec8ff9 --- /dev/null +++ b/presentation/gems.locked @@ -0,0 +1,180 @@ +GEM + remote: https://rubygems.org/ + specs: + agent-context (0.3.0) + bake (>= 0.23) + async (2.45.1) + console (~> 1.29) + fiber-annotation + io-event (~> 1.21) + async-actor (0.2.0) + async (~> 2.33) + async-container (0.38.0) + async (~> 2.44) + async-http (0.103.0) + async (>= 2.35.1) + async-pool (~> 0.12) + io-endpoint (~> 0.18) + io-stream (~> 0.14) + protocol-http (~> 0.66) + protocol-http1 (~> 0.41) + protocol-http2 (~> 0.28) + protocol-url (~> 0.2) + async-http-cache (0.4.6) + async-http (~> 0.56) + async-htty (0.5.0) + async (~> 2.39) + async-http (~> 0.88) + protocol-http (~> 0.62) + protocol-htty (~> 0.4) + async-pool (0.12.0) + async (>= 2.0) + async-service (0.25.0) + async + async-container (~> 0.34) + string-format (~> 0.2) + async-utilization (0.5.0) + console (~> 1.0) + async-webdriver (0.12.2) + async-actor (~> 0.1) + async-http (~> 0.61) + async-pool (~> 0.4) + async-websocket (~> 0.25) + base64 (~> 0.2) + async-websocket (0.30.1) + async-http (~> 0.76) + protocol-http (~> 0.34) + protocol-rack (~> 0.7) + protocol-websocket (~> 0.17) + bake (0.25.0) + bigdecimal + samovar (~> 2.1) + base64 (0.3.0) + bigdecimal (4.1.2) + console (1.37.0) + fiber-annotation + fiber-local (~> 1.1) + json + falcon (0.57.0) + async + async-container (~> 0.20) + async-http (~> 0.97) + async-http-cache (~> 0.4) + async-service (~> 0.19) + async-utilization (~> 0.3) + bundler + localhost (~> 1.1) + openssl (>= 3.0) + protocol-http (~> 0.31) + protocol-rack (~> 0.7) + samovar (~> 2.3) + fiber-annotation (0.2.0) + fiber-local (1.1.0) + fiber-storage + fiber-storage (1.0.1) + io-endpoint (0.18.0) + openssl (>= 3.3.0) + io-event (1.21.1) + io-stream (0.14.0) + openssl (>= 3.3) + io-watch (0.6.3) + json (3.0.1) + live (0.19.0) + async-websocket (~> 0.27) + protocol-websocket (~> 0.19) + xrb (~> 0.10) + lively (0.23.0) + agent-context + async-htty + async-service (~> 0.23) + falcon (~> 0.47) + io-watch + live (~> 0.19) + protocol-url (~> 0.19) + xrb + localhost (1.8.0) + bake + markly (0.17.0) + openssl (4.0.2) + presently (0.16.0) + async-webdriver (~> 0.12) + lively (~> 0.18) + markly (~> 0.16) + protocol-hpack (1.5.1) + protocol-http (0.71.0) + protocol-http1 (0.41.0) + protocol-http (~> 0.68) + protocol-http2 (0.28.0) + protocol-hpack (~> 1.4) + protocol-http (~> 0.62) + protocol-htty (0.5.0) + protocol-http2 + protocol-rack (0.22.1) + io-stream (>= 0.10) + protocol-http (~> 0.58) + rack (>= 1.0) + protocol-url (0.19.0) + protocol-websocket (0.21.1) + protocol-http (~> 0.2) + rack (3.2.7) + samovar (2.5.1) + console (~> 1.0) + string-format (0.2.0) + xrb (0.12.0) + +PLATFORMS + arm64-darwin-25 + ruby + +DEPENDENCIES + io-watch + presently + +CHECKSUMS + agent-context (0.3.0) sha256=1bc73ffb4f73756417e349654b09953b42e8b26a4d091b4f5a13bd6c41d326fe + async (2.45.1) sha256=95849f7432eca535146c69f3078dab3c7228d0075ed71f48733f3e585dd7c267 + async-actor (0.2.0) sha256=4d4c6397843a2e237386bd9536b9f0a437217daf83978e88e98f48a6deb9b12b + async-container (0.38.0) sha256=71479d8551870d684e602f463d7b8368909c835a64e38b555e1821512ffffc88 + async-http (0.103.0) sha256=137e3d5d419f7d72d5fa33cf04f30232e79c57b690025a650b8f9e48ece8bc41 + async-http-cache (0.4.6) sha256=2038d1f093182f16b50b4db271c25085e3938da10bfcfc2904cadb0530fddfd6 + async-htty (0.5.0) sha256=c78172412ea592ef70c0c2a16a7a5055ae1ccd134db9b0e2df9dd93c73715e7a + async-pool (0.12.0) sha256=5580493dc024bd704dab2ed0bf1307100722e49cbb825c7965f8501194f58506 + async-service (0.25.0) sha256=71d9d75c2898dddf6549150cecd57156453e5b61da69fa87f10a0c97becfb6d1 + async-utilization (0.5.0) sha256=96c347634e09edcbcb7f70cf8c5a1ae058c64d88186341e3f924a44440f95981 + async-webdriver (0.12.2) sha256=49a82e6f60fd229547f5f298d58a43b1439925e37a981ed05743f7a4931d65c1 + async-websocket (0.30.1) sha256=54bb8a8f184e4aa64434c7a78ecc55850a67a3d1dcd02e3ae787376e2c673936 + bake (0.25.0) sha256=a47bdc6a26addc048827debc36fe27bb4d5d71ac2958ad910b5386e9baf49869 + base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b + bigdecimal (4.1.2) sha256=53d217666027eab4280346fba98e7d5b66baaae1b9c3c1c0ffe89d48188a3fbd + bundler (4.0.18) sha256=02d9a17429de1847b4e0c9f27a9ee4b20c0a74c0a641b4e77195d6019e3618ac + console (1.37.0) sha256=8093b286c2595a063849c098594fee5155ecc7967d36f3aa8cbe7569c8f3efd7 + falcon (0.57.0) sha256=c9b060bc23a3b90a80376078aae037d8f48e0b2f12f5f30f360d4b29d7baf0f7 + fiber-annotation (0.2.0) sha256=7abfadf1d119f508867d4103bf231c0354d019cc39a5738945dec2edadaf6c03 + fiber-local (1.1.0) sha256=c885f94f210fb9b05737de65d511136ea602e00c5105953748aa0f8793489f06 + fiber-storage (1.0.1) sha256=f48e5b6d8b0be96dac486332b55cee82240057065dc761c1ea692b2e719240e1 + io-endpoint (0.18.0) sha256=720d186bcd584e8ba490bfc91dab332cc3c5ffee848b5b03894555cb275151aa + io-event (1.21.1) sha256=b3088f89ad67edf9be9fc2f48a5fae0790e92862487551eeb337b49098eb99e6 + io-stream (0.14.0) sha256=07d9b13ea2066ccbbc761fff8d69ad5a31e47561d31e02cc271f3ed9c1a8f1cd + io-watch (0.6.3) sha256=95f80faf4b1ee00d8e184340830265ddae237b4f3a42b21f1e90593108932363 + json (3.0.1) sha256=c80f74a3570c3a310cd41c526904e7ef8363abdece4432f31fe97d812bc4a39e + live (0.19.0) + lively (0.23.0) + localhost (1.8.0) sha256=df7ea825b4f64949c588c17efac86bc47ddc4460d723778abe933b71759b2701 + markly (0.17.0) sha256=fab61b604f7c4c8f771c3b7f2f16b971034bb72ddd27009b90c3fae1990b17e6 + openssl (4.0.2) sha256=1037ad2868ae58df9ad917891c0c0f9815a1172f6846d4bcdd508e4c2ee747c2 + presently (0.16.0) sha256=2f10f0d1d2e3094b782a22e9abef2c6c6d9e3b10f21ab013eef3f5106344d8c4 + protocol-hpack (1.5.1) sha256=6feca238b8078da1cd295677d6f306c6001af92d75fe0643d33e6956cbc3ad91 + protocol-http (0.71.0) sha256=38577bf3df0bf0c3f7ee7e21b43fcb4bd7624e36f697dbe9c3eea344f14bd9ba + protocol-http1 (0.41.0) sha256=9863874a1228948a896c3e7ea16d13a680c8f3ed50706007c78b7033018b0ea3 + protocol-http2 (0.28.0) sha256=b7c6195dcfa80ec2484670e9fd8fc21024cc654a391aa0792433aff1188c1691 + protocol-htty (0.5.0) sha256=52ccd2d890d17d01346b96a99b942b48b8df82a1b9a861da87ba9d7eb8005e46 + protocol-rack (0.22.1) sha256=1185d245927ef9849a603700d6991ca353bc89724fbf98efa4a4333ed62a9fc3 + protocol-url (0.19.0) sha256=3f6b9f6aa742a259a0b3610fbbd9685dcd9506adf6734f428ff96259faabfb21 + protocol-websocket (0.21.1) sha256=34325e4325697f0956877e67784bcc838cfd51ebbf4f8e9e5201be292041ee61 + rack (3.2.7) sha256=93e13e1c24f93556671d85d2d79fa228c3485815c50d7e2f265b5330c6528fb7 + samovar (2.5.1) sha256=8a9fc41eb8868084f0321eb41678c485cfbc7d282fb306c0be67c3284b1d2394 + string-format (0.2.0) sha256=bc981c14116b061f12134549f32fa2d61a17b5a35dd6fd36596c21722a789af6 + xrb (0.12.0) sha256=93b31201effbb03bf69136f7d32fd7eb1278b4fa225853f6539e169ca3b2fb65 + +BUNDLED WITH + 4.0.18 diff --git a/presentation/gems.rb b/presentation/gems.rb new file mode 100644 index 0000000..d59904a --- /dev/null +++ b/presentation/gems.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gem "presently", "~> 0.16" +gem "io-watch" diff --git a/presentation/public/_static/custom.css b/presentation/public/_static/custom.css new file mode 100644 index 0000000..d9359bd --- /dev/null +++ b/presentation/public/_static/custom.css @@ -0,0 +1,615 @@ +:root { + --fantail-blue: #42a5f5; + --fantail-cyan: #55d6be; + --fantail-orange: #ffad4d; + --fantail-red: #ff6b6b; + --fantail-muted: color-mix(in srgb, var(--slide-text) 62%, transparent); +} + +.fantail-slide { + inset: 0; + display: flex; + flex-direction: column; + padding: 3.5rem 5rem; +} + +.fantail-heading { + max-width: 74rem; +} + +.fantail-heading h1 { + margin: 0; + font-size: 3.25rem; + line-height: 1.05; +} + +.fantail-heading > p:not(.fantail-kicker) { + margin: 0.7rem 0 0; + font-size: 1.15rem; + opacity: 0.65; +} + +.fantail-kicker { + margin: 0 0 0.5rem; + color: var(--fantail-cyan); + font-family: var(--font-mono); + font-size: 1rem; + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.strategy-grid, +.queue-grid, +.summary-grid { + display: grid; + gap: 1.5rem; + margin: auto 0; +} + +.strategy-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.strategy-grid.single-strategy { + grid-template-columns: minmax(0, 1fr); + max-width: 52rem; + width: 100%; + margin-inline: auto; +} + +.strategy-card, +.queue-card, +.summary-grid > div { + padding: 2rem; + background: color-mix(in srgb, var(--surface) 88%, var(--fantail-blue)); + border: 1px solid color-mix(in srgb, var(--slide-text) 15%, transparent); + border-radius: 1.25rem; +} + +.strategy-card h2, +.queue-card h2 { + margin: 0 0 1.3rem; + font-size: 1.65rem; +} + +.strategy-card p, +.queue-card p { + margin: 1.3rem 0 0; + font-size: 1.15rem; + line-height: 1.45; + opacity: 0.75; +} + +.mini-flow { + display: flex; + align-items: center; + justify-content: center; + gap: 0.75rem; + min-height: 5rem; +} + +.mini-flow span { + padding: 0.75rem 1rem; + background: color-mix(in srgb, var(--fantail-blue) 12%, var(--surface)); + border: 1px solid var(--fantail-blue); + border-radius: 0.65rem; + font-family: var(--font-mono); + font-size: 0.9rem; +} + +.mini-flow b, +.architecture-arrow { + color: var(--fantail-cyan); +} + +.admission-flow { + display: grid; + grid-template-columns: 1fr auto 2.2fr; + align-items: center; + gap: 1.5rem; + margin: auto 0 1.5rem; +} + +.listener-queue, +.worker-admission { + padding: 1.25rem; + background: color-mix(in srgb, var(--surface) 88%, var(--fantail-blue)); + border: 1px solid color-mix(in srgb, var(--slide-text) 18%, transparent); + border-radius: 1rem; +} + +.listener-queue { + display: grid; + gap: 1rem; + text-align: center; +} + +.request-stack { + display: flex; + justify-content: center; + gap: 0.45rem; +} + +.request { + padding: 0.45rem 0.65rem; + border-radius: 0.45rem; + font-family: var(--font-mono); + font-size: 0.72rem; + font-weight: 700; +} + +.request.cpu {background: color-mix(in srgb, var(--fantail-orange) 35%, var(--surface));} +.request.io {background: color-mix(in srgb, var(--fantail-cyan) 30%, var(--surface));} +.request.short {background: color-mix(in srgb, var(--fantail-blue) 30%, var(--surface));} + +.admission-arrow { + color: var(--fantail-cyan); + font-size: 2.5rem; +} + +.worker-admission-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.8rem; +} + +.worker-admission { + display: grid; + gap: 0.65rem; + min-height: 8.5rem; + text-align: center; +} + +.worker-admission.busy { + border-color: var(--fantail-red); + box-shadow: inset 0 -0.3rem 0 color-mix(in srgb, var(--fantail-red) 45%, transparent); +} + +.active-work, +.waiting-work, +.capacity-note { + padding: 0.55rem; + border-radius: 0.45rem; + font-size: 0.78rem; +} + +.active-work {background: color-mix(in srgb, var(--fantail-orange) 28%, var(--surface));} +.io-work {background: color-mix(in srgb, var(--fantail-cyan) 25%, var(--surface));} +.available-work {background: color-mix(in srgb, var(--fantail-blue) 22%, var(--surface));} +.waiting-work {background: color-mix(in srgb, var(--fantail-red) 25%, var(--surface)); color: var(--fantail-red);} +.capacity-note {opacity: 0.62;} + +.admission-caption { + margin: 0 0 1.1rem; + text-align: center; + opacity: 0.72; +} + +.signal-timing { + display: grid; + grid-template-columns: 1fr auto 1fr; + align-items: center; + gap: 0.8rem; +} + +.signal-card { + display: grid; + grid-template-columns: auto 1fr; + align-items: baseline; + gap: 0.8rem; + padding: 0.75rem 1rem; + border: 1px solid; + border-radius: 0.65rem; + font-size: 0.82rem; +} + +.signal-card span {opacity: 0.68;} +.leading-signal {border-color: var(--fantail-cyan);} +.leading-signal strong {color: var(--fantail-cyan);} +.trailing-signal {border-color: var(--fantail-orange);} +.trailing-signal strong {color: var(--fantail-orange);} + +.signal-timing > b { + font-family: var(--font-mono); + font-size: 0.7rem; + opacity: 0.45; + text-transform: uppercase; +} + +.tail-distribution { + display: grid; + grid-template-columns: 4fr 1fr; + gap: 1rem; + margin: auto 0 1.5rem; +} + +.percentile-band { + display: grid; + grid-template-columns: auto 1fr; + align-items: center; + gap: 1.2rem; + min-height: 9rem; + padding: 1.4rem; + border: 1px solid color-mix(in srgb, var(--slide-text) 18%, transparent); + border-radius: 1rem; +} + +.fast-band {background: color-mix(in srgb, var(--fantail-cyan) 11%, var(--surface));} +.tail-band {background: color-mix(in srgb, var(--fantail-red) 14%, var(--surface)); border-color: var(--fantail-red);} + +.percentile-label { + display: grid; + gap: 0.2rem; + min-width: 7rem; +} + +.percentile-label strong { + font-size: 2rem; +} + +.tail-band .percentile-label strong {color: var(--fantail-red);} + +.percentile-label span { + font-size: 0.78rem; + opacity: 0.65; +} + +.request-dots { + display: flex; + align-items: center; + gap: 0.45rem; +} + +.request-dots span { + display: block; + width: 1.4rem; + height: 1.4rem; + background: var(--fantail-cyan); + border-radius: 50%; +} + +.tail-band .request-dots span {background: var(--fantail-red);} + +.percentile-band p { + grid-column: 1 / -1; + margin: 0; + font-size: 0.92rem; + opacity: 0.7; +} + +.causal-chain { + display: flex; + align-items: center; + justify-content: center; + gap: 0.7rem; + font-size: 0.88rem; +} + +.causal-chain span { + padding: 0.65rem 0.8rem; + background: color-mix(in srgb, var(--fantail-blue) 10%, var(--surface)); + border: 1px solid color-mix(in srgb, var(--slide-text) 15%, transparent); + border-radius: 0.55rem; +} + +.causal-chain b {color: var(--fantail-cyan);} +.causal-chain .tail-outcome {border-color: var(--fantail-red); color: var(--fantail-red);} + +.architecture-slide { + justify-content: space-between; +} + +.architecture { + display: grid; + grid-template-columns: 1fr auto 1fr auto 1.25fr auto 1.4fr; + align-items: center; + gap: 1rem; + margin: auto 0; +} + +.architecture-node { + display: grid; + place-items: center; + min-height: 6rem; + padding: 1.25rem; + border: 1px solid color-mix(in srgb, var(--slide-text) 20%, transparent); + border-radius: 1rem; + text-align: center; +} + +.architecture-arrow { + font-size: 2.5rem; +} + +.fantail-node { + background: color-mix(in srgb, var(--fantail-cyan) 18%, var(--surface)); + border: 2px solid var(--fantail-cyan); +} + +.fantail-node strong { + font-size: 1.6rem; +} + +.fantail-node small, +.worker-node span { + margin-top: 0.35rem; + opacity: 0.7; +} + +.worker-stack { + display: grid; + gap: 0.6rem; +} + +.worker-node { + grid-template-columns: 1fr auto; + min-height: 0; + padding: 0.85rem 1rem; + background: color-mix(in srgb, var(--fantail-blue) 10%, var(--surface)); + border-color: var(--fantail-blue); +} + +.worker-node span { + margin: 0; + padding: 0.25rem 0.5rem; + color: var(--fantail-cyan); + font-family: var(--font-mono); + font-size: 0.75rem; +} + +.architecture-caption, +.queue-caption, +.results-callout { + margin: 0; + text-align: center; + opacity: 0.7; +} + +.cluster-slide { + justify-content: space-between; +} + +.supervisor-boundary { + position: relative; + margin: auto 0 0; + padding: 2.5rem 2rem 2rem; + border: 2px solid color-mix(in srgb, var(--slide-text) 28%, transparent); + border-radius: 1.25rem; +} + +.supervisor-label { + position: absolute; + top: 0; + left: 2rem; + padding: 0.45rem 0.8rem; + background: var(--surface); + transform: translateY(-50%); + font-family: var(--font-mono); + font-size: 0.85rem; + font-weight: 700; + letter-spacing: 0.05em; + text-transform: uppercase; +} + +.cluster-topology { + display: grid; + grid-template-columns: 1fr auto 1.65fr; + align-items: center; + gap: 2rem; +} + +.cluster-routes { + display: grid; + justify-items: center; + gap: 0.25rem; + color: var(--fantail-cyan); +} + +.cluster-routes span { + font-family: var(--font-mono); + font-size: 0.75rem; +} + +.cluster-routes b { + font-size: 2.5rem; +} + +.lifecycle { + display: grid; + grid-template-columns: 1fr auto 1fr auto 1fr auto 1fr; + align-items: center; + margin: auto 0; +} + +.lifecycle-step { + display: grid; + justify-items: center; + gap: 0.55rem; + padding: 1.7rem 1rem; + border: 2px solid var(--fantail-blue); + border-radius: 1rem; + text-align: center; +} + +.lifecycle-step strong { + display: grid; + place-items: center; + width: 2.5rem; + height: 2.5rem; + background: var(--fantail-blue); + border-radius: 50%; + color: white; +} + +.lifecycle-step small { + color: var(--fantail-cyan); + font-family: var(--font-mono); +} + +.lifecycle-step.release { + border-color: var(--fantail-cyan); +} + +.lifecycle-step.stream { + border-color: var(--fantail-orange); +} + +.lifecycle-line { + width: 2rem; + height: 2px; + background: color-mix(in srgb, var(--slide-text) 35%, transparent); +} + +.queue-grid { + grid-template-columns: repeat(3, minmax(0, 1fr)); + margin-bottom: 1.5rem; +} + +.queue-card { + display: flex; + flex-direction: column; + min-height: 13rem; +} + +.queue-card code { + margin-top: auto; + padding: 0.7rem; + border-radius: 0.5rem; + font-size: 0.82rem; + white-space: normal; +} + +.cpu-queue {border-color: var(--fantail-blue);} +.io-queue {border-color: var(--fantail-cyan);} +.shed-queue {border-color: var(--fantail-orange);} + +.results-slide { + gap: 2rem; +} + +.results-table { + width: 100%; + margin: auto 0; + border-collapse: separate; + border-spacing: 0; + font-size: 1rem; +} + +.results-table th, +.results-table td { + padding: 0.85rem 1rem; + border-bottom: 1px solid color-mix(in srgb, var(--slide-text) 14%, transparent); + text-align: right; +} + +.results-table th:first-child, +.results-table td:first-child, +.results-table th:nth-child(2), +.results-table td:nth-child(2) { + text-align: left; +} + +.results-table th { + font-size: 0.82rem; + letter-spacing: 0.04em; + opacity: 0.6; + text-transform: uppercase; +} + +.results-table .fantail-result td { + background: color-mix(in srgb, var(--fantail-cyan) 12%, transparent); + color: color-mix(in srgb, var(--fantail-cyan) 78%, var(--slide-text)); +} + +.results-table .fantail-result td:first-child { + border-left: 3px solid var(--fantail-cyan); +} + +.overload-table { + font-size: 0.92rem; +} + +.results-callout { + padding: 0.85rem 1rem; + background: color-mix(in srgb, var(--fantail-cyan) 12%, transparent); + border-radius: 0.65rem; + color: var(--fantail-cyan); + font-weight: 600; +} + +.summary-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.summary-grid > div { + display: grid; + gap: 0.55rem; +} + +.summary-grid strong { + color: var(--fantail-cyan); + font-size: 1.3rem; +} + +.summary-grid span { + font-size: 1.05rem; + line-height: 1.4; + opacity: 0.72; +} + +@media (max-width: 900px) { + .fantail-slide { + padding: 2rem; + } + + .fantail-heading h1 { + font-size: 2.3rem; + } + + .strategy-grid, + .queue-grid, + .summary-grid { + grid-template-columns: 1fr; + } + + .admission-flow, + .tail-distribution { + grid-template-columns: 1fr; + } + + .admission-arrow { + transform: rotate(90deg); + text-align: center; + } + + .worker-admission-grid { + grid-template-columns: 1fr; + } + + .causal-chain { + flex-wrap: wrap; + } + + .signal-timing { + grid-template-columns: 1fr; + } + + .signal-timing > b { + text-align: center; + } + + .architecture { + transform: scale(0.72); + transform-origin: center; + } + + .results-table { + font-size: 0.72rem; + } + + .results-table th, + .results-table td { + padding: 0.5rem; + } +} diff --git a/presentation/readme.md b/presentation/readme.md new file mode 100644 index 0000000..4693794 --- /dev/null +++ b/presentation/readme.md @@ -0,0 +1,23 @@ +# Introducing Fantail + +A short presentation introducing Fantail: worker-aware HTTP load balancing with a global admission queue. + +The deck explains the scheduling problem Fantail addresses, its permit and queue model, and how it compares with a shared-listener baseline under a representative CPU-heavy workload. + +## Setup + +```bash +$ bundle install +``` + +## Running + +```bash +$ bundle exec presently +``` + +Then open in your browser. + +Use the **arrow keys** (or swipe) to navigate between slides. + +For presenter notes, visit . diff --git a/presentation/slides/010-welcome.md b/presentation/slides/010-welcome.md new file mode 100644 index 0000000..a3cb7ee --- /dev/null +++ b/presentation/slides/010-welcome.md @@ -0,0 +1,18 @@ +--- +template: title +duration: 20 +marker: Welcome +transition: fade +--- + +# Title + +Fantail + +## Subtitle + +Worker-aware HTTP load balancing + +--- + +Fantail is an HTTP load balancer built around a simple idea: route work to a worker when that worker is ready to process it. It builds on Falcon's existing long-task admission model, extends it to HTTP/2, and supports multiple queueing strategies for different workloads. diff --git a/presentation/slides/015-falcon-cluster.md b/presentation/slides/015-falcon-cluster.md new file mode 100644 index 0000000..3a05241 --- /dev/null +++ b/presentation/slides/015-falcon-cluster.md @@ -0,0 +1,36 @@ +--- +template: diagram +duration: 18 +marker: Falcon Cluster +transition: fade +--- + +
+
+

Deployment model

+

Fantail operates in front of a Falcon cluster

+

Each worker is an independently routable process with its own HTTP endpoint.

+
+
+
Supervisor
+
+
+ Fantail + load balancer +
+
+ HTTP/2 + +
+
+
Falcon worker 1 :3001
+
Falcon worker 2 :3002
+
Falcon worker N :30xx
+
+
+
+
+ +--- + +Fantail operates in front of a Falcon cluster. Each worker has a distinct HTTP endpoint rather than sharing a single listener. The supervisor manages those processes, while Fantail uses their endpoints to decide which worker should receive each request. diff --git a/presentation/slides/020-problem/010-placement.md b/presentation/slides/020-problem/010-placement.md new file mode 100644 index 0000000..c658ab0 --- /dev/null +++ b/presentation/slides/020-problem/010-placement.md @@ -0,0 +1,12 @@ +--- +template: statement +duration: 21 +marker: The Problem +transition: fade +--- + +Distributing requests is not the same as scheduling work. + +--- + +Load balancers distribute incoming requests across servers, but generally do not know whether a particular worker can begin a specific request immediately. This matters for CPU-heavy workloads: assigning overlapping CPU-bound requests to the same worker creates local queueing and increases tail latency. diff --git a/presentation/slides/020-problem/020-existing-strategies.md b/presentation/slides/020-problem/020-existing-strategies.md new file mode 100644 index 0000000..a721a2d --- /dev/null +++ b/presentation/slides/020-problem/020-existing-strategies.md @@ -0,0 +1,24 @@ +--- +template: diagram +duration: 22 +marker: Shared Listener +transition: fade +--- + +
+
+

Existing strategies

+

Where does excess work wait?

+
+
+
+

Shared listener

+
ClientSocket queueWorkers
+

Simple, but accepting a socket is only a proxy for request-processing capacity.

+
+
+
+ +--- + +Servers commonly use a shared listener backed by an operating-system socket accept queue. This distributes connections, but accepting a connection is only a proxy for request-processing capacity. A worker can accept a connection before it knows whether it can immediately process the request inside it, creating local queueing or resource contention. diff --git a/presentation/slides/020-problem/022-endpoint-load-balancer.md b/presentation/slides/020-problem/022-endpoint-load-balancer.md new file mode 100644 index 0000000..a68fe9f --- /dev/null +++ b/presentation/slides/020-problem/022-endpoint-load-balancer.md @@ -0,0 +1,24 @@ +--- +template: diagram +duration: 23 +marker: Endpoint Load Balancer +transition: fade +--- + +
+
+

Existing strategies

+

Where does excess work wait?

+
+
+
+

Endpoint load balancer

+
ClientConnection poolWorker
+

A request can be committed to one worker while another worker becomes available.

+
+
+
+ +--- + +General-purpose load balancers commonly use round-robin, least-request, or similar policies. These approximate capacity when selecting an endpoint, while utilization reported with a response is necessarily a trailing signal. Once a request is committed to an endpoint's connection pool, it often cannot be reassigned merely because another worker becomes available. diff --git a/presentation/slides/020-problem/025-over-admission.md b/presentation/slides/020-problem/025-over-admission.md new file mode 100644 index 0000000..675e17f --- /dev/null +++ b/presentation/slides/020-problem/025-over-admission.md @@ -0,0 +1,58 @@ +--- +template: diagram +duration: 30 +marker: Over-admission +transition: fade +--- + +
+
+

Shared listener

+

Accepted does not always mean capacity.

+

The listener can see sockets—not the kind of work inside them or whether a worker can begin it now.

+
+
+
+ Shared listener +
+ CPU + I/O + short +
+
+
+
+
+ Worker A + Long CPU task + accepted work waits +
+
+ Worker B + I/O wait + could overlap work +
+
+ Worker C + Available + capacity may reappear +
+
+
+

Once work enters a worker, it is no longer globally assignable—even if another worker becomes available first.

+
+
+ Leading · worker permit + Can this worker begin the request now? +
+ versus +
+ Trailing · response utilization + How busy was the worker after dispatch? +
+
+
+ +--- + +With mixed workloads, especially on event-driven servers, a CPU-bound request can monopolize a worker and unfairly delay I/O-bound work assigned behind it. Once assigned, that work is no longer globally available to another suitable worker. Leading utilization signals like worker permits and request classification let the load balancer make this admission decision before dispatch, rather than infer capacity later from response utilization. diff --git a/presentation/slides/020-problem/027-tail-latency.md b/presentation/slides/020-problem/027-tail-latency.md new file mode 100644 index 0000000..0f17526 --- /dev/null +++ b/presentation/slides/020-problem/027-tail-latency.md @@ -0,0 +1,37 @@ +--- +template: diagram +duration: 27 +marker: Tail Latency +transition: fade +--- + +
+
+

Mixed workloads

+

Poor placement appears in the tail.

+

The system can have spare capacity while individual requests wait inside the wrong worker.

+
+
+
+
P50capacity matched
+
+ +
+

Assigned to a worker that could begin immediately.

+
+
+
P90+locally queued
+
+ +
+

Committed to a worker that could not begin it.

+
+
+
+ Spare capacity elsewhereRequest already committedLocal waitingHigh P90 / P95 / P99 +
+
+ +--- + +Over-admission does not necessarily make the median look unhealthy. Most requests may reach workers that can begin them immediately, while a minority are committed to busy workers and wait despite spare capacity elsewhere. That imbalance appears first in P90, P95, and P99 latency. Response utilization arrives after placement and cannot correct those requests. diff --git a/presentation/slides/020-problem/030-desirable-state.md b/presentation/slides/020-problem/030-desirable-state.md new file mode 100644 index 0000000..07d78ec --- /dev/null +++ b/presentation/slides/020-problem/030-desirable-state.md @@ -0,0 +1,12 @@ +--- +template: statement +duration: 25 +marker: Desirable State +transition: fade +--- + +Keep each request globally assignable until a compatible worker is ready to begin it. + +--- + +This is the premise Fantail is built around: admission before assignment. Pending requests wait once, in a global queue. Workers advertise permits as a leading capacity signal, and the oldest compatible request is assigned only when a permit can be reserved. Work therefore does not disappear into a worker-local queue or connection pool before it can run. diff --git a/presentation/slides/030-fantail/010-architecture.md b/presentation/slides/030-fantail/010-architecture.md new file mode 100644 index 0000000..62bdc29 --- /dev/null +++ b/presentation/slides/030-fantail/010-architecture.md @@ -0,0 +1,34 @@ +--- +template: diagram +duration: 16 +marker: Fantail +transition: fade +--- + +
+
+

Fantail

+

Admission before assignment

+
+
+
Clients
+
+
Edge proxy
+
+
+ Fantail + global admission queue +
+
+
+
Worker 1 permit
+
Worker 2 permit
+
Worker N permit
+
+
+

HTTP/2 upstreams · dynamic endpoint registration · explicit worker capacity

+
+ +--- + +Fantail sits next to the application workers. Workers register dynamically and advertise processing permits. Fantail keeps pending requests globally assignable and dispatches one only when an eligible worker permit can be reserved. diff --git a/presentation/slides/030-fantail/020-permits.md b/presentation/slides/030-fantail/020-permits.md new file mode 100644 index 0000000..96a6124 --- /dev/null +++ b/presentation/slides/030-fantail/020-permits.md @@ -0,0 +1,26 @@ +--- +template: diagram +duration: 12 +marker: Permits +transition: fade +--- + +
+
+

Capacity model

+

A processing permit means a worker is ready.

+
+
+
1Reserve worker permit
+
+
2Process request
+
+
3Response headersrelease worker permit
+
+
4Stream bodyretain stream slot
+
+
+ +--- + +Once response processing completes, the worker can accept new work. The streaming response retains a separate permit until its body closes, bounding the number of concurrent streams. diff --git a/presentation/slides/030-fantail/030-queues.md b/presentation/slides/030-fantail/030-queues.md new file mode 100644 index 0000000..e29723e --- /dev/null +++ b/presentation/slides/030-fantail/030-queues.md @@ -0,0 +1,35 @@ +--- +template: diagram +duration: 22 +marker: Queue Policy +transition: fade +--- + +
+
+

Application policy

+

Different workloads need different admission policies.

+
+
+
+

CPU-heavy

+

Spread across idle workers

+ balance :spread +
+
+

I/O-heavy

+

Prefer compatible affinity

+ balance :pack, affinity: :grpc +
+
+

Overload

+

Bound queue depth and time

+ wait_limit 0.25 +
+
+

Queues express preference and admission policy. The central scheduler remains work-conserving.

+
+ +--- + +Fantail can classify requests into queues. A queue can express backend eligibility, soft affinity, admission limits, and its shedding response. Preferences never reserve capacity independently: if the preferred worker is unavailable, another eligible worker can run the request. diff --git a/presentation/slides/040-results/010-clean-capacity.md b/presentation/slides/040-results/010-clean-capacity.md new file mode 100644 index 0000000..2a61026 --- /dev/null +++ b/presentation/slides/040-results/010-clean-capacity.md @@ -0,0 +1,27 @@ +--- +template: diagram +duration: 19 +marker: Results +transition: fade +--- + +
+
+

Representative benchmark

+

Comparable throughput before saturation

+

60 workers · CPU-heavy requests · five-minute fixed-rate runs

+
+ + + + + + + + +
OfferedStrategySuccessful RPS200 mean200 p99Rejected
100 RPSShared listener99.98465 ms712 ms0
100 RPSFantail100.00477 ms722 ms0
110 RPSShared listener110.00634 ms1,479 ms0
110 RPSFantail109.89525 ms797 ms0.10%
+
+ +--- + +The goal was not to win a microbenchmark. It was to preserve the useful capacity of the existing strategy while gaining explicit scheduling control. At 100 and 110 offered requests per second, Fantail delivered equivalent successful throughput as a shared listener design. diff --git a/presentation/slides/040-results/020-overload.md b/presentation/slides/040-results/020-overload.md new file mode 100644 index 0000000..89bf203 --- /dev/null +++ b/presentation/slides/040-results/020-overload.md @@ -0,0 +1,27 @@ +--- +template: diagram +duration: 23 +marker: Overload +transition: fade +--- + +
+
+

At saturation

+

Similar throughput. Very different failure behavior.

+
+ + + + + + + + +
OfferedStrategySuccessful RPS200 mean200 p99Overload outcome
120 RPSShared listener115.613,924 ms4,486 ms1,302 load-generator drops
120 RPSFantail115.28664 ms900 ms1,415 HTTP 429
130 RPSShared listener113.914,164 ms4,912 ms4,763 load-generator drops + 24 timeouts
130 RPSFantail115.78710 ms976 ms4,267 HTTP 429
+

Fantail rejected excess work in about 252 ms—close to its configured 250 ms queue limit.

+
+ +--- + +At saturation, successful throughput was comparable. The shared-listener path accumulated roughly four seconds of latency and eventually produced load-generator drops and timeouts. Fantail bounded queue wait and returned explicit HTTP 429 responses, keeping successful p99 latency below one second. diff --git a/presentation/slides/050-conclusion/010-summary.md b/presentation/slides/050-conclusion/010-summary.md new file mode 100644 index 0000000..34fddee --- /dev/null +++ b/presentation/slides/050-conclusion/010-summary.md @@ -0,0 +1,23 @@ +--- +template: diagram +duration: 26 +marker: Summary +transition: fade +--- + +
+
+

Fantail provides

+

An application-aware scheduling layer for HTTP.

+
+
+
Global admissionKeep work assignable until capacity exists.
+
Explicit permitsRoute using a leading indicator of worker capacity.
+
Queue policyClassify, balance, bound, and shed requests.
+
HTTP semanticsReturn clear overload responses instead of hidden backlog.
+
+
+ +--- + +Fantail turns worker readiness into an explicit scheduling primitive. The design preserves baseline capacity and lets applications define how excess load is queued or rejected. In summary, it gives us the control required to handle mixed workloads efficiently, while providing leading scale up and scale down signals, avoiding overload and enabling cost effective deployment.