-
Notifications
You must be signed in to change notification settings - Fork 945
feat: add article for when to use Bloom Filter #7928
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
aa0c8c1
feat(blog): add bloom filters in Postgres blog with CodeHike demos
ankur-arch fd84e22
refactor(blog): drop AgentPrompt usages, add labeled-step demo runner
ankur-arch 0138da8
fix(blog): runner panel layout and full-output terminal
ankur-arch e085069
docs(blog): add btree primer + bloom-filter-to-bloom-index bridge
ankur-arch 109a797
docs(blog): use B-tree spelling in prose, add to cspell dictionary
ankur-arch fd85ae5
fix(blog): runner terminal highlight extends across scroll, add copy …
ankur-arch 2c28d38
Merge branch 'main' into bloom-filter-blog-ankur
ankur-arch 9bca8e1
docs(blog): lead with bloom filters, rename ambiguous section
ankur-arch 25db701
feat(blog): wire bloom post into postgres-features series, add B-tree…
ankur-arch 03cc01c
feat(blog): register postgres-features series, add author landing pages
ankur-arch ad16f87
fix(blog): BTreeDemo crashed on next; clamp annotation line numbers
ankur-arch fa6b58c
Update apps/blog/content/blog/postgres-bloom-index-the-overlooked-pos…
ankur-arch File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
72 changes: 72 additions & 0 deletions
72
apps/blog/content/blog/postgres-bloom-index-the-overlooked-postgres-feature/BTreeDemo.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| import { highlight, type HighlightedCode } from "codehike/code"; | ||
| import { BTreeDemoClient, type BTreePhase } from "./BTreeDemoClient"; | ||
|
|
||
| const SOURCE = `function lookup(tree, key) { | ||
| let node = tree.root; | ||
| while (!node.isLeaf) { | ||
| const child = node.childFor(key); | ||
| node = child; | ||
| } | ||
| return node.findEntry(key)?.rowPointer; | ||
| } | ||
|
|
||
| lookup(idx, "t42"); // -> row 142`; | ||
|
|
||
| const PHASES: BTreePhase[] = [ | ||
| { | ||
| label: "Start at the root", | ||
| caption: | ||
| "A B-tree is a sorted tree of (key, pointer) entries. Lookups always start at the root.", | ||
| lines: { from: 1, to: 2 }, | ||
| activeRoot: false, | ||
| activeLeaf: null, | ||
| matchedKey: null, | ||
| query: null, | ||
| }, | ||
| { | ||
| label: 'lookup("t42")', | ||
| caption: "We want to find the row for tenant t42. Compare against the root keys.", | ||
| lines: { from: 10, to: 10 }, | ||
| activeRoot: true, | ||
| activeLeaf: null, | ||
| matchedKey: null, | ||
| query: "t42", | ||
| }, | ||
| { | ||
| label: "Pick the child", | ||
| caption: | ||
| "t42 is greater than or equal to t20 and less than t50, so descend into the middle child.", | ||
| lines: { from: 3, to: 5 }, | ||
| activeRoot: true, | ||
| activeLeaf: 1, | ||
| matchedKey: null, | ||
| query: "t42", | ||
| }, | ||
| { | ||
| label: "Search the leaf", | ||
| caption: "The middle leaf is sorted. Scan it for the exact key.", | ||
| lines: { from: 7, to: 7 }, | ||
| activeRoot: false, | ||
| activeLeaf: 1, | ||
| matchedKey: "t42", | ||
| query: "t42", | ||
| }, | ||
| { | ||
| label: "Return the row pointer", | ||
| caption: "The leaf entry holds a pointer to row 142 on disk. Follow it to read the row.", | ||
| lines: { from: 7, to: 7 }, | ||
| activeRoot: false, | ||
| activeLeaf: 1, | ||
| matchedKey: "t42", | ||
| query: "t42", | ||
| found: true, | ||
| }, | ||
| ]; | ||
|
|
||
| export async function BTreeDemo() { | ||
| const baseCode = (await highlight( | ||
| { value: SOURCE, lang: "typescript", meta: "" }, | ||
| "github-from-css", | ||
| )) as HighlightedCode; | ||
| return <BTreeDemoClient baseCode={baseCode} phases={PHASES} />; | ||
| } | ||
231 changes: 231 additions & 0 deletions
231
...log/content/blog/postgres-bloom-index-the-overlooked-postgres-feature/BTreeDemoClient.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,231 @@ | ||
| "use client"; | ||
|
|
||
| import { useEffect, useMemo, useRef, useState } from "react"; | ||
| import { InnerLine, Pre, type AnnotationHandler, type HighlightedCode } from "codehike/code"; | ||
| import { ChevronLeft, ChevronRight, Pause, Play } from "lucide-react"; | ||
|
|
||
| export type BTreePhase = { | ||
| label: string; | ||
| caption: string; | ||
| lines: { from: number; to: number }; | ||
| activeRoot: boolean; | ||
| activeLeaf: 0 | 1 | 2 | null; | ||
| matchedKey: string | null; | ||
| query: string | null; | ||
| found?: boolean; | ||
| }; | ||
|
|
||
| type Props = { | ||
| baseCode: HighlightedCode; | ||
| phases: BTreePhase[]; | ||
| }; | ||
|
|
||
| const STEP_HOLD_MS = 5400; | ||
|
|
||
| const ROOT_KEYS = ["t20", "t50"]; | ||
| const LEAVES: { keys: string[]; rows: number[] }[] = [ | ||
| { keys: ["t05", "t12", "t18"], rows: [105, 112, 118] }, | ||
| { keys: ["t30", "t40", "t42"], rows: [130, 140, 142] }, | ||
| { keys: ["t60", "t75", "t90"], rows: [160, 175, 190] }, | ||
| ]; | ||
|
|
||
| const markHandler: AnnotationHandler = { | ||
| name: "mark", | ||
| AnnotatedLine: ({ annotation, ...props }) => ( | ||
| <InnerLine merge={props} data-mark={annotation.query || "active"} /> | ||
| ), | ||
| }; | ||
|
|
||
| const handlers = [markHandler]; | ||
|
|
||
| function codeForPhase(base: HighlightedCode, phase: BTreePhase): HighlightedCode { | ||
| const totalLines = (base.code ?? "").split("\n").length; | ||
| const from = Math.max(1, Math.min(phase.lines.from, totalLines)); | ||
| const to = Math.max(from, Math.min(phase.lines.to, totalLines)); | ||
| const lineMarks = []; | ||
| for (let n = from; n <= to; n += 1) { | ||
| lineMarks.push({ name: "mark", query: "active", fromLineNumber: n, toLineNumber: n }); | ||
| } | ||
| return { | ||
| ...base, | ||
| annotations: [...base.annotations.filter((a) => a.name !== "mark"), ...lineMarks], | ||
| }; | ||
| } | ||
|
|
||
| export function BTreeDemoClient({ baseCode, phases }: Props) { | ||
| const [phaseIndex, setPhaseIndex] = useState(0); | ||
| const [playing, setPlaying] = useState(true); | ||
| const [inView, setInView] = useState(false); | ||
| const containerRef = useRef<HTMLDivElement>(null); | ||
|
|
||
| useEffect(() => { | ||
| const el = containerRef.current; | ||
| if (!el || typeof IntersectionObserver === "undefined") { | ||
| setInView(true); | ||
| return; | ||
| } | ||
| const obs = new IntersectionObserver(([entry]) => setInView(entry.isIntersecting), { | ||
| threshold: 0.25, | ||
| }); | ||
| obs.observe(el); | ||
| return () => obs.disconnect(); | ||
| }, []); | ||
|
|
||
| useEffect(() => { | ||
| if (!playing || !inView) return; | ||
| const id = setInterval(() => { | ||
| setPhaseIndex((i) => (i + 1) % phases.length); | ||
| }, STEP_HOLD_MS); | ||
| return () => clearInterval(id); | ||
| }, [playing, inView, phases.length]); | ||
|
|
||
| const phase = phases[phaseIndex]; | ||
| const code = useMemo(() => codeForPhase(baseCode, phase), [baseCode, phase]); | ||
|
|
||
| function goTo(index: number) { | ||
| setPlaying(false); | ||
| setPhaseIndex(((index % phases.length) + phases.length) % phases.length); | ||
| } | ||
|
|
||
| return ( | ||
| <div ref={containerRef} className="btree-demo not-prose"> | ||
| <div className="btree-demo-header"> | ||
| <span className="btree-demo-step" aria-hidden="true"> | ||
| {phaseIndex + 1} / {phases.length} | ||
| </span> | ||
| <span className="btree-demo-label">{phase.label}</span> | ||
| <div className="btree-demo-nav"> | ||
| <button | ||
| type="button" | ||
| className="btree-demo-toggle" | ||
| onClick={() => goTo(phaseIndex - 1)} | ||
| aria-label="Previous step" | ||
| > | ||
| <ChevronLeft size={14} /> | ||
| </button> | ||
| <button | ||
| type="button" | ||
| className="btree-demo-toggle" | ||
| onClick={() => setPlaying((p) => !p)} | ||
| aria-label={playing ? "Pause demo" : "Play demo"} | ||
| > | ||
| {playing ? <Pause size={14} /> : <Play size={14} />} | ||
| </button> | ||
| <button | ||
| type="button" | ||
| className="btree-demo-toggle" | ||
| onClick={() => goTo(phaseIndex + 1)} | ||
| aria-label="Next step" | ||
| > | ||
| <ChevronRight size={14} /> | ||
| </button> | ||
| </div> | ||
| </div> | ||
|
|
||
| <div className="btree-demo-steps" role="tablist" aria-label="B-tree walkthrough"> | ||
| {phases.map((p, i) => ( | ||
| <button | ||
| key={p.label} | ||
| type="button" | ||
| role="tab" | ||
| aria-selected={i === phaseIndex} | ||
| data-active={i === phaseIndex ? "true" : undefined} | ||
| className="btree-demo-step-pill" | ||
| onClick={() => goTo(i)} | ||
| > | ||
| <span className="btree-demo-step-pill-num">{i + 1}</span> | ||
| <span className="btree-demo-step-pill-label">{p.label}</span> | ||
| </button> | ||
| ))} | ||
| </div> | ||
|
|
||
| <div className="btree-demo-body"> | ||
| <div className="btree-demo-code"> | ||
| <Pre code={code} handlers={handlers} /> | ||
| </div> | ||
|
|
||
| <div className="btree-demo-tree-wrap"> | ||
| {phase.query ? ( | ||
| <div className="btree-demo-query"> | ||
| <span className="btree-demo-query-label">looking for</span> | ||
| <span className="btree-demo-query-value">"{phase.query}"</span> | ||
| </div> | ||
| ) : ( | ||
| <div className="btree-demo-query btree-demo-query-placeholder"> | ||
| <span>idle</span> | ||
| </div> | ||
| )} | ||
|
|
||
| <div className="btree-demo-tree"> | ||
| <div className="btree-demo-row btree-demo-row-root"> | ||
| <div className="btree-demo-node" data-active={phase.activeRoot ? "true" : undefined}> | ||
| <span className="btree-demo-node-label">root</span> | ||
| <div className="btree-demo-keys"> | ||
| {ROOT_KEYS.map((k) => ( | ||
| <span key={k} className="btree-demo-key"> | ||
| {k} | ||
| </span> | ||
| ))} | ||
| </div> | ||
| </div> | ||
| </div> | ||
|
|
||
| <div className="btree-demo-edges" aria-hidden="true"> | ||
| {LEAVES.map((_, i) => ( | ||
| <div | ||
| key={i} | ||
| className="btree-demo-edge" | ||
| data-active={phase.activeLeaf === i ? "true" : undefined} | ||
| /> | ||
| ))} | ||
| </div> | ||
|
|
||
| <div className="btree-demo-row btree-demo-row-leaves"> | ||
| {LEAVES.map((leaf, i) => ( | ||
| <div | ||
| key={i} | ||
| className="btree-demo-node btree-demo-leaf" | ||
| data-active={phase.activeLeaf === i ? "true" : undefined} | ||
| > | ||
| <span className="btree-demo-node-label">leaf {i}</span> | ||
| <div className="btree-demo-leaf-rows"> | ||
| {leaf.keys.map((k, j) => { | ||
| const matched = | ||
| phase.matchedKey === k && phase.activeLeaf === i ? "true" : undefined; | ||
| return ( | ||
| <div | ||
| key={k} | ||
| className="btree-demo-leaf-entry" | ||
| data-matched={matched} | ||
| > | ||
| <span className="btree-demo-leaf-key">{k}</span> | ||
| <span className="btree-demo-leaf-arrow" aria-hidden="true"> | ||
| → | ||
| </span> | ||
| <span className="btree-demo-leaf-row">row {leaf.rows[j]}</span> | ||
| </div> | ||
| ); | ||
| })} | ||
| </div> | ||
| </div> | ||
| ))} | ||
| </div> | ||
| </div> | ||
|
|
||
| <div className="btree-demo-caption">{phase.caption}</div> | ||
|
|
||
| {phase.found ? ( | ||
| <div className="btree-demo-result"> | ||
| <span className="btree-demo-result-icon" aria-hidden="true"> | ||
| ✓ | ||
| </span> | ||
| <span> | ||
| Found <strong>{phase.matchedKey}</strong>, returning the row pointer. | ||
| </span> | ||
| </div> | ||
| ) : null} | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.