From 5c09c82c6b8074cb838ca49a4d6e92c7e1355834 Mon Sep 17 00:00:00 2001 From: Gaurav Sen <59478396+coding-parrot@users.noreply.github.com> Date: Fri, 27 Feb 2026 18:30:40 +0530 Subject: [PATCH] Add neural network line-fit weight update animation --- db_connection_pool_diagram.py | 184 ++++++++++++++++++++++++++++++++ hierarchical_wal_replication.py | 111 +++++++++++++++++++ neural_network_line_fit.py | 101 ++++++++++++++++++ primary_to_read_replicas.py | 140 ++++++++++++++++++++++++ request_collapsing_cache.py | 154 ++++++++++++++++++++++++++ 5 files changed, 690 insertions(+) create mode 100644 db_connection_pool_diagram.py create mode 100644 hierarchical_wal_replication.py create mode 100644 neural_network_line_fit.py create mode 100644 primary_to_read_replicas.py create mode 100644 request_collapsing_cache.py diff --git a/db_connection_pool_diagram.py b/db_connection_pool_diagram.py new file mode 100644 index 0000000..c26e07f --- /dev/null +++ b/db_connection_pool_diagram.py @@ -0,0 +1,184 @@ +from manim import * + +# Keep a 16:9 render with a taller frame so vertical layouts stay visible. +config.pixel_width = 1920 +config.pixel_height = 1080 +config.frame_height = 10.5 +config.frame_width = config.frame_height * 16 / 9 + + +class DBConnectionPoolDiagram(Scene): + def construct(self): + # ---------- Helpers ---------- + def make_node( + label: str, + width=3.0, + height=1.0, + fill_color=None, + text_color=WHITE, + font_size=30, + ): + box = RoundedRectangle( + width=width, + height=height, + corner_radius=0.2, + stroke_width=2, + ) + if fill_color: + box.set_fill(fill_color, opacity=1) + box.set_stroke(fill_color) + else: + box.set_fill(opacity=0) + box.set_stroke(WHITE) + + text = Text(label, font_size=font_size, color=text_color) + text.move_to(box.get_center()) + return VGroup(box, text) + + def make_connection(slot_label: str): + slot = RoundedRectangle( + width=2.6, + height=0.8, + corner_radius=0.15, + stroke_width=2, + ) + slot.set_fill("#ffd166", opacity=1) + slot.set_stroke("#ffd166") + txt = Text(slot_label, font_size=24, color=BLACK) + txt.move_to(slot.get_center()) + return VGroup(slot, txt) + + def make_request(color=YELLOW): + return Dot(radius=0.16, color=color) + + # ---------- Main blocks ---------- + incoming = make_node( + "Clients", + width=3.1, + height=1.2, + fill_color="#5c7cfa", + text_color=WHITE, + font_size=30, + ) + incoming.move_to(LEFT * 5.2) + + pool_shell = RoundedRectangle( + width=4.8, + height=5.6, + corner_radius=0.2, + stroke_width=2, + ) + pool_shell.set_fill(opacity=0) + pool_shell.set_stroke(WHITE) + + pool_title = Text("DB Connection Pool", font_size=30, color=WHITE) + pool_title.move_to(pool_shell.get_top() + DOWN * 0.45) + + connections = VGroup(*[make_connection(f"Conn {i}") for i in range(1, 5)]) + connections.arrange(DOWN, buff=0.30) + connections.move_to(pool_shell.get_center() + DOWN * 0.45) + + pool = VGroup(pool_shell, pool_title, connections) + pool.move_to(ORIGIN) + + database = make_node( + "Database", + width=2.8, + height=1.0, + fill_color="#06d6a0", + text_color=BLACK, + font_size=30, + ) + database.move_to(RIGHT * 5.2) + + # ---------- Static arrows ---------- + request_arrow = Arrow( + start=np.array([incoming.get_right()[0], pool_shell.get_center()[1], 0]), + end=np.array([pool_shell.get_left()[0], pool_shell.get_center()[1], 0]), + buff=0.2, + stroke_width=3, + max_tip_length_to_length_ratio=0.12, + ) + + db_arrows = VGroup() + for conn in connections: + db_arrows.add( + Arrow( + start=conn.get_right(), + end=database.get_left(), + buff=0.2, + stroke_width=2.5, + max_tip_length_to_length_ratio=0.12, + ) + ) + + # ---------- Draw ---------- + self.play(FadeIn(incoming), FadeIn(pool_shell), FadeIn(pool_title), FadeIn(database)) + self.play(LaggedStart(*[FadeIn(c) for c in connections], lag_ratio=0.08)) + self.play(Create(request_arrow)) + self.play(LaggedStart(*[Create(a) for a in db_arrows], lag_ratio=0.06)) + + # ---------- Request lifecycle animation ---------- + status = Text("Acquire connection", font_size=32, color=YELLOW) + status.to_edge(DOWN) + self.play(FadeIn(status), run_time=0.3) + + total_requests = 8 + for i in range(total_requests): + conn_index = i % len(connections) + chosen = connections[conn_index] + + req = make_request(color=YELLOW) + req.move_to(incoming.get_right() + RIGHT * 0.15) + + original_color = chosen[0].get_fill_color() + busy_color = "#ff6b6b" + + # Acquire in 2 hops: follow incoming arrow into pool, then route to selected connection + self.play( + MoveAlongPath( + req, + Line(req.get_center(), request_arrow.get_end() + RIGHT * 0.06), + rate_func=linear, + ), + run_time=0.3, + ) + + self.play( + MoveAlongPath( + req, + Line(req.get_center(), chosen.get_left() + RIGHT * 0.15), + rate_func=linear, + ), + chosen[0].animate.set_fill(busy_color, opacity=1), + run_time=0.3, + ) + + # Use connection (connection -> DB) + self.play( + MoveAlongPath( + req, + Line(chosen.get_right() + RIGHT * 0.05, database.get_left() + LEFT * 0.05), + rate_func=linear, + ), + run_time=0.45, + ) + + # Release: return to pool and mark as free + self.play( + MoveAlongPath( + req, + Line(database.get_left() + LEFT * 0.05, chosen.get_center()), + rate_func=linear, + ), + chosen[0].animate.set_fill(original_color, opacity=1), + run_time=0.45, + ) + + self.play(FadeOut(req), run_time=0.15) + + if i == total_requests // 2 - 1: + self.play(Transform(status, Text("Release after use", font_size=32, color=GREEN).to_edge(DOWN))) + + self.play(FadeOut(status), run_time=0.25) + self.wait(0.5) diff --git a/hierarchical_wal_replication.py b/hierarchical_wal_replication.py new file mode 100644 index 0000000..c08f35a --- /dev/null +++ b/hierarchical_wal_replication.py @@ -0,0 +1,111 @@ +from manim import * + +config.pixel_width = 1920 +config.pixel_height = 1080 +config.frame_height = 10.5 +config.frame_width = config.frame_height * 16 / 9 + + +class HierarchicalWALReplication(Scene): + def construct(self): + def make_node(label, w=2.8, h=1.0, fill="#171717", text_color=WHITE, fs=28): + box = RoundedRectangle(width=w, height=h, corner_radius=0.24, stroke_width=2) + box.set_fill(fill, opacity=1) + box.set_stroke(WHITE, width=2) + txt = Text(label, font_size=fs, color=text_color) + txt.move_to(box.get_center()) + return VGroup(box, txt) + + def wal_arrow(start, end): + return Arrow( + start=start, + end=end, + buff=0.14, + stroke_width=2.8, + max_tip_length_to_length_ratio=0.16, + color=WHITE, + ) + + title = Text("PostgreSQL Cascading Replication", font_size=42, color=WHITE).to_edge(UP) + + primary = make_node("PRIMARY", w=3.2, h=1.1, fill="#0398fc", text_color=WHITE, fs=30) + primary.move_to(UP * 3.0) + + rep_l2_left = make_node("READ\nREPLICA", w=3.0, h=1.2, fill="#ffb30f", text_color=BLACK, fs=27) + rep_l2_right = make_node("READ\nREPLICA", w=3.0, h=1.2, fill="#ffb30f", text_color=BLACK, fs=27) + rep_l2_left.move_to(LEFT * 3.0 + UP * 0.9) + rep_l2_right.move_to(RIGHT * 3.0 + UP * 0.9) + level2 = VGroup(rep_l2_left, rep_l2_right) + + read_left_1 = make_node("READ\nREPLICA", w=2.7, h=1.0, fill="#171717", fs=26) + read_left_2 = make_node("READ\nREPLICA", w=2.7, h=1.0, fill="#171717", fs=26) + read_right_1 = make_node("READ\nREPLICA", w=2.7, h=1.0, fill="#171717", fs=26) + read_right_2 = make_node("READ\nREPLICA", w=2.7, h=1.0, fill="#171717", fs=26) + + read_left_1.move_to(LEFT * 4.5 + DOWN * 1.6) + read_left_2.move_to(LEFT * 1.5 + DOWN * 1.6) + read_right_1.move_to(RIGHT * 1.5 + DOWN * 1.6) + read_right_2.move_to(RIGHT * 4.5 + DOWN * 1.6) + reads = VGroup(read_left_1, read_left_2, read_right_1, read_right_2) + + ellipsis_top = Text("...", font_size=42, color=WHITE).move_to(ORIGIN + UP * 1.0) + ellipsis_bottom_left = Text("...", font_size=42, color=WHITE).move_to(LEFT * 3.0 + DOWN * 1.6) + ellipsis_bottom_right = Text("...", font_size=42, color=WHITE).move_to(RIGHT * 3.0 + DOWN * 1.6) + + a_p_l = wal_arrow(primary.get_bottom() + LEFT * 0.65, rep_l2_left.get_top() + RIGHT * 0.35) + a_p_r = wal_arrow(primary.get_bottom() + RIGHT * 0.65, rep_l2_right.get_top() + LEFT * 0.35) + a_l_1 = wal_arrow(rep_l2_left.get_bottom() + LEFT * 0.45, read_left_1.get_top() + RIGHT * 0.25) + a_l_2 = wal_arrow(rep_l2_left.get_bottom() + RIGHT * 0.45, read_left_2.get_top() + LEFT * 0.25) + a_r_1 = wal_arrow(rep_l2_right.get_bottom() + LEFT * 0.45, read_right_1.get_top() + RIGHT * 0.25) + a_r_2 = wal_arrow(rep_l2_right.get_bottom() + RIGHT * 0.45, read_right_2.get_top() + LEFT * 0.25) + + top_arrows = VGroup(a_p_l, a_p_r) + bottom_arrows = VGroup(a_l_1, a_l_2, a_r_1, a_r_2) + + self.play(FadeIn(title), run_time=0.8) + self.play(FadeIn(primary), run_time=0.6) + self.play(FadeIn(level2), FadeIn(ellipsis_top), run_time=0.8) + self.play(FadeIn(reads), FadeIn(ellipsis_bottom_left), FadeIn(ellipsis_bottom_right), run_time=0.9) + + self.play(LaggedStart(*[Create(a) for a in top_arrows], lag_ratio=0.15), run_time=0.8) + self.play(LaggedStart(*[Create(a) for a in bottom_arrows], lag_ratio=0.1), run_time=0.9) + + wal_events_top = VGroup(*[Dot(radius=0.09, color=YELLOW) for _ in top_arrows]) + for dot, arrow in zip(wal_events_top, top_arrows): + dot.move_to(arrow.get_start()) + self.play(FadeIn(wal_events_top), run_time=0.2) + + for _ in range(3): + self.play( + *[MoveAlongPath(dot, arrow, rate_func=linear) for dot, arrow in zip(wal_events_top, top_arrows)], + run_time=0.8, + ) + for dot, arrow in zip(wal_events_top, top_arrows): + dot.move_to(arrow.get_start()) + + branch_dots = VGroup(*[Dot(radius=0.08, color="#7df9ff") for _ in bottom_arrows]) + for dot, arrow in zip(branch_dots, bottom_arrows): + dot.move_to(arrow.get_start()) + self.play(FadeIn(branch_dots), run_time=0.15) + self.play( + *[MoveAlongPath(dot, arrow, rate_func=linear) for dot, arrow in zip(branch_dots, bottom_arrows)], + run_time=0.9, + ) + self.play(FadeOut(branch_dots), run_time=0.15) + + self.play(FadeOut(wal_events_top), run_time=0.2) + + self.play( + primary[0].animate.set_stroke(YELLOW, width=4), + rep_l2_left[0].animate.set_stroke(YELLOW, width=4), + rep_l2_right[0].animate.set_stroke(YELLOW, width=4), + run_time=0.3, + ) + self.play( + reads[0][0].animate.set_stroke(GREEN, width=4), + reads[1][0].animate.set_stroke(GREEN, width=4), + reads[2][0].animate.set_stroke(GREEN, width=4), + reads[3][0].animate.set_stroke(GREEN, width=4), + run_time=0.4, + ) + self.wait(0.8) diff --git a/neural_network_line_fit.py b/neural_network_line_fit.py new file mode 100644 index 0000000..9179e5d --- /dev/null +++ b/neural_network_line_fit.py @@ -0,0 +1,101 @@ +from manim import * + +config.pixel_width = 1920 +config.pixel_height = 1080 +config.frame_height = 10.5 +config.frame_width = config.frame_height * 16 / 9 + + +class NeuralNetworkLearnsLine(Scene): + def construct(self): + def node(label, fill, text_color=WHITE, w=1.9, h=0.95, fs=30): + box = RoundedRectangle(width=w, height=h, corner_radius=0.2, stroke_width=2) + box.set_fill(fill, opacity=1) + box.set_stroke(WHITE, width=2) + txt = Text(label, font_size=fs, color=text_color) + txt.move_to(box.get_center()) + return VGroup(box, txt) + + title = Text("Neural Network Learning y = m x + C", font_size=40, color=WHITE).to_edge(UP) + + x_node = node("x", fill="#5c7cfa", w=1.4, fs=36) + mult_node = node("× m", fill="#ffb30f", text_color=BLACK, w=2.0, fs=32) + add_node = node("+ C", fill="#ffb30f", text_color=BLACK, w=2.0, fs=32) + yhat_node = node("ŷ", fill="#06d6a0", text_color=BLACK, w=1.6, fs=36) + y_node = node("y", fill="#ef476f", w=1.4, fs=36) + loss_node = node("Loss", fill="#7b2cbf", w=2.2, fs=32) + + network = VGroup(x_node, mult_node, add_node, yhat_node) + network.arrange(RIGHT, buff=1.0).move_to(UP * 0.7) + y_node.next_to(yhat_node, DOWN, buff=1.5) + loss_node.next_to(yhat_node, RIGHT, buff=2.2).shift(DOWN * 1.5) + + a1 = Arrow(x_node.get_right(), mult_node.get_left(), buff=0.12, stroke_width=2.8) + a2 = Arrow(mult_node.get_right(), add_node.get_left(), buff=0.12, stroke_width=2.8) + a3 = Arrow(add_node.get_right(), yhat_node.get_left(), buff=0.12, stroke_width=2.8) + compare_arrow = Arrow(yhat_node.get_bottom(), loss_node.get_left() + LEFT * 0.1, buff=0.15, stroke_width=2.6) + gt_arrow = Arrow(y_node.get_right(), loss_node.get_left() + DOWN * 0.15, buff=0.15, stroke_width=2.6) + back_m = Arrow(loss_node.get_top(), mult_node.get_bottom(), buff=0.15, stroke_width=2.4, color=ORANGE) + back_c = Arrow(loss_node.get_top(), add_node.get_bottom(), buff=0.15, stroke_width=2.4, color=ORANGE) + + eq = MathTex(r"\hat{y} = m x + C", font_size=44).next_to(network, DOWN, buff=1.1).shift(LEFT * 2.0) + m_val = DecimalNumber(0.40, num_decimal_places=2, font_size=38, color=YELLOW) + c_val = DecimalNumber(1.80, num_decimal_places=2, font_size=38, color=YELLOW) + loss_val = DecimalNumber(8.60, num_decimal_places=2, font_size=38, color=RED) + + m_label = Text("m:", font_size=30, color=WHITE) + c_label = Text("C:", font_size=30, color=WHITE) + l_label = Text("loss:", font_size=30, color=WHITE) + + stats = VGroup( + VGroup(m_label, m_val).arrange(RIGHT, buff=0.2), + VGroup(c_label, c_val).arrange(RIGHT, buff=0.2), + VGroup(l_label, loss_val).arrange(RIGHT, buff=0.2), + ).arrange(DOWN, aligned_edge=LEFT, buff=0.25) + stats.next_to(loss_node, RIGHT, buff=1.0) + + self.play(FadeIn(title), run_time=0.8) + self.play(FadeIn(network), run_time=1.0) + self.play(Create(a1), Create(a2), Create(a3), run_time=0.9) + self.play(FadeIn(y_node), run_time=0.4) + self.play(FadeIn(loss_node), Create(compare_arrow), Create(gt_arrow), run_time=0.9) + self.play(Write(eq), FadeIn(stats), run_time=0.9) + + forward_dot = Dot(radius=0.09, color=YELLOW) + for _ in range(2): + forward_dot.move_to(a1.get_start()) + self.play(FadeIn(forward_dot), run_time=0.15) + self.play(MoveAlongPath(forward_dot, a1, rate_func=linear), run_time=0.35) + self.play(MoveAlongPath(forward_dot, a2, rate_func=linear), run_time=0.35) + self.play(MoveAlongPath(forward_dot, a3, rate_func=linear), run_time=0.35) + self.play(MoveAlongPath(forward_dot, compare_arrow, rate_func=linear), run_time=0.45) + self.play(FadeOut(forward_dot), run_time=0.15) + + epochs = [ + (0.75, 1.45, 5.20), + (1.10, 1.05, 2.90), + (1.35, 0.70, 1.40), + (1.50, 0.50, 0.35), + ] + + for m_new, c_new, l_new in epochs: + self.play(Create(back_m), Create(back_c), run_time=0.25) + self.play( + m_val.animate.set_value(m_new), + c_val.animate.set_value(c_new), + loss_val.animate.set_value(l_new), + mult_node[0].animate.set_stroke(ORANGE, width=5), + add_node[0].animate.set_stroke(ORANGE, width=5), + run_time=0.8, + ) + self.play( + mult_node[0].animate.set_stroke(WHITE, width=2), + add_node[0].animate.set_stroke(WHITE, width=2), + FadeOut(back_m), + FadeOut(back_c), + run_time=0.35, + ) + + final_box = SurroundingRectangle(eq, color=GREEN, buff=0.2, stroke_width=3) + self.play(Create(final_box), loss_node[0].animate.set_stroke(GREEN, width=4), run_time=0.6) + self.wait(0.8) diff --git a/primary_to_read_replicas.py b/primary_to_read_replicas.py new file mode 100644 index 0000000..04d6c61 --- /dev/null +++ b/primary_to_read_replicas.py @@ -0,0 +1,140 @@ +from manim import * + +# Increase both render resolution and camera frame size so lower replicas remain visible. +config.pixel_width = 1920 +config.pixel_height = 1080 +config.frame_height = 10.5 +config.frame_width = config.frame_height * 16 / 9 + + +class PrimaryToReadReplicas(Scene): + def construct(self): + # ---------- Helpers ---------- + def make_node( + label: str, + w=2.6, + h=0.9, # reduced height so the extra replica fits in-frame + font_size=26, # reduced font size to match the smaller box + fill_color=None, + text_color=WHITE, + ): + box = RoundedRectangle( + width=w, + height=h, + corner_radius=0.18, + stroke_width=2, + ) + + if fill_color: + box.set_fill(fill_color, opacity=1) + box.set_stroke(fill_color) + else: + box.set_fill(opacity=0) + box.set_stroke(WHITE) + + txt = Text(label, font_size=font_size, color=text_color) + txt.move_to(box.get_center()) + return VGroup(box, txt) + + def make_request_dot(color=YELLOW, r=0.07): + return Dot(radius=r, color=color) + + # ---------- Labels ---------- + labels = ["Read 1", "Read 2", "Read 3", "...", "Read 50"] + + # ---------- Nodes ---------- + primary = make_node("Primary DB", fill_color="#0398fc", text_color=WHITE) + + reads = VGroup( + *[make_node(label, fill_color="#ffb30f", text_color=BLACK) for label in labels] + ) + + # Layout: primary left, reads stacked right (use smaller vertical spacing so Read 51 fits) + primary.move_to(LEFT * 4) + reads.arrange(DOWN, buff=0.35).move_to(RIGHT * 3.6) + + # ---------- Edges for initial reads ---------- + arrows = VGroup() + for r in reads: + a = Arrow( + start=primary.get_right(), + end=r.get_left(), + buff=0.22, + stroke_width=3, + max_tip_length_to_length_ratio=0.12, + ) + arrows.add(a) + + # ---------- Draw ---------- + self.play(FadeIn(primary)) + self.play(LaggedStart(*[FadeIn(r) for r in reads], lag_ratio=0.08)) + self.play(LaggedStart(*[Create(a) for a in arrows], lag_ratio=0.08)) + self.wait(0.2) + + # ============================================================ + # Add Read 51 at the end (without camera zooming) + # ============================================================ + + read51 = make_node("Read 51", fill_color="#ffb30f", text_color=BLACK) + read51.next_to(reads[-1], DOWN, buff=0.35) + + arrow51 = Arrow( + start=primary.get_right(), + end=read51.get_left(), + buff=0.22, + stroke_width=3, + max_tip_length_to_length_ratio=0.12, + ) + + self.play(FadeIn(read51), run_time=0.35) + self.play(Create(arrow51), run_time=0.45) + + # ---------- Animate moving "requests" (after Read 51 is added) ---------- + all_arrows = VGroup(*arrows, arrow51) + request_dots = VGroup(*[make_request_dot() for _ in range(len(all_arrows))]) + for dot, arrow in zip(request_dots, all_arrows): + dot.move_to(arrow.get_start()) + self.play(FadeIn(request_dots), run_time=0.3) + + waves = 3 + for _ in range(waves): + self.play( + *[ + MoveAlongPath(dot, arrow, rate_func=linear) + for dot, arrow in zip(request_dots, all_arrows) + ], + run_time=1.1, + ) + for dot, arrow in zip(request_dots, all_arrows): + dot.move_to(arrow.get_start()) + self.wait(0.1) + + self.play(FadeOut(request_dots), run_time=0.3) + + # ---------- Bombard the Primary DB with write requests from the top ---------- + # Simulates incoming app-server writes (app server itself intentionally not shown). + x_offsets = [-0.9, -0.3, 0.3, 0.9] + incoming_dots = VGroup(*[make_request_dot(color=RED_E, r=0.08) for _ in x_offsets]) + + spawn_y = config.frame_height / 2 - 0.3 + for dot, x in zip(incoming_dots, x_offsets): + dot.move_to(np.array([primary.get_center()[0] + x, spawn_y, 0])) + + self.play(FadeIn(incoming_dots), run_time=0.2) + + for _ in range(5): + impacts = [] + for dot, x in zip(incoming_dots, x_offsets): + target = primary.get_top() + np.array([x * 0.22, -0.04, 0]) + path = Line(dot.get_center(), target) + impacts.append(MoveAlongPath(dot, path, rate_func=linear)) + + self.play(*impacts, run_time=0.32) + self.play(primary[0].animate.set_stroke(RED, width=6), run_time=0.08) + self.play(primary[0].animate.set_stroke("#0398fc", width=2), run_time=0.08) + + for dot, x in zip(incoming_dots, x_offsets): + dot.move_to(np.array([primary.get_center()[0] + x, spawn_y, 0])) + + self.play(FadeOut(incoming_dots), run_time=0.2) + self.wait(0.5) diff --git a/request_collapsing_cache.py b/request_collapsing_cache.py new file mode 100644 index 0000000..5729a83 --- /dev/null +++ b/request_collapsing_cache.py @@ -0,0 +1,154 @@ +from manim import * + +config.pixel_width = 1920 +config.pixel_height = 1080 +config.frame_height = 10.5 +config.frame_width = config.frame_height * 16 / 9 + + +class RequestCollapsingInCache(Scene): + def construct(self): + def make_box(label, w=2.8, h=1.0, fill="#2b2d42", text_color=WHITE, fs=30): + rect = RoundedRectangle(width=w, height=h, corner_radius=0.16, stroke_width=2) + rect.set_fill(fill, opacity=1) + rect.set_stroke(fill) + txt = Text(label, font_size=fs, color=text_color) + txt.move_to(rect.get_center()) + return VGroup(rect, txt) + + def req_dot(color=YELLOW, r=0.12): + return Dot(radius=r, color=color) + + # ---------- Nodes ---------- + clients = VGroup(*[make_box(f"Client {i}", w=2.3, h=0.8, fill="#5c7cfa", fs=24) for i in range(1, 4)]) + clients.arrange(DOWN, buff=0.35).move_to(LEFT * 5.2) + + cache = make_box("Cache", w=3.2, h=1.2, fill="#f4a261", text_color=BLACK) + cache.move_to(ORIGIN + UP * 0.8) + + db = make_box("Database", w=3.2, h=1.2, fill="#06d6a0", text_color=BLACK) + db.move_to(RIGHT * 5.1 + UP * 0.8) + + title = Text("Request Collapsing", font_size=42, color=WHITE).to_edge(UP) + + # ---------- Compact table (in-cache key/value entry) ---------- + table_box = RoundedRectangle(width=5.8, height=1.9, corner_radius=0.12, stroke_width=2) + table_box.set_stroke(GRAY_B) + table_box.set_fill("#1c1f2b", opacity=0.45) + table_box.next_to(cache, DOWN, buff=0.55) + + header_line = Line(table_box.get_left() + RIGHT * 0.25 + UP * 0.25, table_box.get_right() + LEFT * 0.25 + UP * 0.25) + header_line.set_stroke(GRAY_B, width=2) + divider = Line(table_box.get_center() + UP * 0.7, table_box.get_center() + DOWN * 0.7) + divider.set_stroke(GRAY_B, width=2) + + key_head = Text("KEY", font_size=24, color=GRAY_A) + val_head = Text("VALUE", font_size=24, color=GRAY_A) + key_head.move_to(np.array([table_box.get_left()[0] + 1.2, table_box.get_top()[1] - 0.33, 0])) + val_head.move_to(np.array([table_box.get_right()[0] - 1.2, table_box.get_top()[1] - 0.33, 0])) + + key_cell = Text("user:42", font_size=34, color=WHITE) + val_cell = Text("Promise", font_size=34, color=YELLOW) + key_cell.move_to(np.array([key_head.get_center()[0], table_box.get_bottom()[1] + 0.55, 0])) + val_cell.move_to(np.array([val_head.get_center()[0], table_box.get_bottom()[1] + 0.55, 0])) + + # ---------- Arrows ---------- + arrows_to_cache = VGroup(*[ + Arrow(c.get_right(), cache.get_left(), buff=0.16, stroke_width=2.6) + for c in clients + ]) + + y_mid = cache.get_center()[1] + cache_to_db = Arrow( + start=np.array([cache.get_right()[0], y_mid, 0]), + end=np.array([db.get_left()[0], y_mid, 0]), + buff=0.2, + stroke_width=3, + color=ORANGE, + ) + db_to_cache = Arrow( + start=np.array([db.get_left()[0], y_mid, 0]), + end=np.array([cache.get_right()[0], y_mid, 0]), + buff=0.2, + stroke_width=3, + color=GREEN, + ) + + # ---------- Draw ---------- + self.play(FadeIn(title), run_time=0.8) + self.play(LaggedStart(*[FadeIn(c) for c in clients], lag_ratio=0.12), FadeIn(cache), FadeIn(db), run_time=1.4) + self.play(LaggedStart(*[Create(a) for a in arrows_to_cache], lag_ratio=0.08), run_time=1.2) + self.play(FadeIn(table_box), Create(header_line), Create(divider), FadeIn(key_head), FadeIn(val_head), run_time=0.8) + + # ---------- First request creates promise + one DB call ---------- + req1 = req_dot(color=YELLOW) + req1.move_to(arrows_to_cache[0].get_start()) + self.play(FadeIn(req1), run_time=0.3) + self.play(MoveAlongPath(req1, arrows_to_cache[0], rate_func=linear), run_time=0.9) + + self.play( + FadeIn(key_cell), + FadeIn(val_cell), + table_box.animate.set_stroke(YELLOW, width=3), + run_time=0.7, + ) + + self.play(Create(cache_to_db), run_time=0.5) + db_req = req_dot(color=ORANGE) + db_req.move_to(cache_to_db.get_start()) + self.play(FadeIn(db_req), run_time=0.2) + self.play(MoveAlongPath(db_req, cache_to_db, rate_func=linear), run_time=1) + self.play(FadeOut(db_req), FadeOut(cache_to_db), run_time=0.4) + + # ---------- Collapsed requests: same promise returned ---------- + req2, req3 = req_dot(color=YELLOW), req_dot(color=YELLOW) + req2.move_to(arrows_to_cache[1].get_start()) + req3.move_to(arrows_to_cache[2].get_start()) + self.play(FadeIn(req2), FadeIn(req3), run_time=0.4) + self.play( + MoveAlongPath(req2, arrows_to_cache[1], rate_func=linear), + MoveAlongPath(req3, arrows_to_cache[2], rate_func=linear), + run_time=0.9, + ) + self.play(FadeOut(arrows_to_cache), run_time=0.4) + + # ---------- DB resolves once ---------- + self.play(Create(db_to_cache), run_time=0.5) + db_res = req_dot(color=GREEN) + db_res.move_to(db_to_cache.get_start()) + self.play(FadeIn(db_res), run_time=0.2) + self.play(MoveAlongPath(db_res, db_to_cache, rate_func=linear), run_time=1) + self.play(FadeOut(db_res), run_time=0.2) + + done_cell = Text("Data Ready", font_size=32, color=GREEN) + done_cell.move_to(val_cell.get_center()) + self.play(Transform(val_cell, done_cell), table_box.animate.set_stroke(GREEN, width=3), run_time=0.7) + + # ---------- Propagate completion to all clients ---------- + fanout_arrows = VGroup(*[ + Arrow(cache.get_left(), c.get_right(), buff=0.16, stroke_width=2.2, color=GREEN) + for c in clients + ]) + self.play(LaggedStart(*[Create(a) for a in fanout_arrows], lag_ratio=0.08), run_time=0.9) + + self.play( + MoveAlongPath(req1, fanout_arrows[0], rate_func=linear), + MoveAlongPath(req2, fanout_arrows[1], rate_func=linear), + MoveAlongPath(req3, fanout_arrows[2], rate_func=linear), + run_time=1.1, + ) + + self.play( + clients[0][0].animate.set_stroke(GREEN, width=4), + clients[1][0].animate.set_stroke(GREEN, width=4), + clients[2][0].animate.set_stroke(GREEN, width=4), + run_time=0.4, + ) + self.play( + clients[0][0].animate.set_stroke("#5c7cfa", width=2), + clients[1][0].animate.set_stroke("#5c7cfa", width=2), + clients[2][0].animate.set_stroke("#5c7cfa", width=2), + run_time=0.4, + ) + + self.wait(1.2)