Skip to content
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

feat(dashboard): add backpressure to the relation dependency graph #18280

Merged
merged 44 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
5017fdd
add ddl_graph page and DdlGraph component
kwannoel Aug 29, 2024
fcc6d11
get rid of plan node dependencies
kwannoel Sep 16, 2024
792529c
remove selected fragment id
kwannoel Sep 16, 2024
fb3ee36
cleanup more unused code
kwannoel Sep 16, 2024
92381f7
add sample DS, backpressure rates
kwannoel Sep 16, 2024
a033de7
add generateDdlEdges
kwannoel Sep 16, 2024
f17a4e3
complete component refactor of ddl graph
kwannoel Sep 16, 2024
b5a6704
build relation dep edges
kwannoel Sep 16, 2024
a62b4e6
exclude non-streaming relations from ddl graph
kwannoel Sep 17, 2024
019fc31
add method to map relation to fragment vertices
kwannoel Sep 17, 2024
249b3b4
add fragment id to vertex map
kwannoel Sep 17, 2024
f7dfe8d
use relation backpressure
kwannoel Sep 17, 2024
76dbe8c
format
kwannoel Sep 18, 2024
c336fc5
rename fragment to ddl
kwannoel Sep 18, 2024
18954fa
display schema name
kwannoel Sep 18, 2024
acc52f0
remove more useless things
kwannoel Sep 18, 2024
3a8993a
support prom
kwannoel Sep 18, 2024
0e05839
use circle instead
kwannoel Sep 18, 2024
9cb51c2
format
kwannoel Sep 19, 2024
4c22305
fix warn
kwannoel Sep 19, 2024
da3f361
simplify
kwannoel Sep 19, 2024
83dd9bb
extract out magic numbers
kwannoel Sep 19, 2024
22cf701
cleanup
kwannoel Sep 19, 2024
9d2c6ce
add button to reset backpressures
kwannoel Sep 19, 2024
c46b324
add button to reset embedded bp
kwannoel Sep 19, 2024
c2c54b5
cleanup code
kwannoel Sep 19, 2024
64bf784
clean
kwannoel Sep 19, 2024
3723a0e
format
kwannoel Sep 19, 2024
28c61aa
fix
kwannoel Sep 19, 2024
bcdba73
fix naming
kwannoel Sep 19, 2024
c4f735b
rename page title
kwannoel Sep 19, 2024
da1752c
use camelCase
kwannoel Sep 19, 2024
e3bfa6a
Update proto/meta.proto
kwannoel Sep 19, 2024
5e6115d
Update src/meta/src/dashboard/mod.rs
kwannoel Sep 19, 2024
0785a66
fix
kwannoel Sep 19, 2024
446b79e
fmt
kwannoel Sep 19, 2024
abb2a72
use relation deps api
kwannoel Sep 19, 2024
a6c04ec
rename to relation graph
kwannoel Sep 19, 2024
9f52231
format
kwannoel Sep 19, 2024
2f67710
add bp to dependency graph
kwannoel Sep 19, 2024
a8e06bd
remove obsolete
kwannoel Sep 19, 2024
ccfdf8a
add bp labels
kwannoel Sep 19, 2024
b16a249
typecheck
kwannoel Sep 19, 2024
3624e3a
reuse
kwannoel Sep 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion dashboard/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,20 @@ For example:
./risedev slt e2e_test/nexmark/create_sources.slt.part
./risedev psql -c 'CREATE TABLE dimension (v1 int);'
./risedev psql -c 'CREATE MATERIALIZED VIEW mv AS SELECT auction.* FROM dimension join auction on auction.id-auction.id = dimension.v1;'
./risedev psql -c 'INSERT INTO dimension select 0 from generate_series(1, 50);'
./risedev psql -c 'CREATE MATERIALIZED VIEW mv2 AS SELECT * FROM mv;'
./risedev psql -c 'CREATE MATERIALIZED VIEW mv3 AS SELECT count(*) FROM mv2;'

./risedev psql -c 'CREATE MATERIALIZED VIEW mv4 AS SELECT * FROM mv;'
./risedev psql -c 'CREATE MATERIALIZED VIEW mv5 AS SELECT count(*) FROM mv2;'
./risedev psql -c 'CREATE MATERIALIZED VIEW mv6 AS SELECT mv4.* FROM mv4 join mv2 using(id);'
./risedev psql -c 'CREATE MATERIALIZED VIEW mv7 AS SELECT max(id) FROM mv;'

./risedev psql -c 'CREATE MATERIALIZED VIEW mv8 AS SELECT mv.* FROM mv join mv6 using(id);'
./risedev psql -c 'CREATE SCHEMA s1;'
./risedev psql -c 'CREATE TABLE s1.t1 (v1 int);'
./risedev psql -c 'CREATE MATERIALIZED VIEW s1.mv1 AS SELECT s1.t1.* FROM s1.t1 join mv on s1.t1.v1 = mv.id;'

./risedev psql -c 'INSERT INTO dimension select 0 from generate_series(1, 20);'
kwannoel marked this conversation as resolved.
Show resolved Hide resolved
```

Install dependencies and start the development server.
Expand Down
45 changes: 2 additions & 43 deletions dashboard/components/FragmentGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
theme,
useDisclosure,
} from "@chakra-ui/react"
import { tinycolor } from "@ctrl/tinycolor"
import loadable from "@loadable/component"
import * as d3 from "d3"
import { cloneDeep } from "lodash"
Expand All @@ -26,6 +25,7 @@ import {
} from "../lib/layout"
import { PlanNodeDatum } from "../pages/fragment_graph"
import { StreamNode } from "../proto/gen/stream_plan"
import { backPressureColor, backPressureWidth } from "./utils/backPressure"

const ReactJson = loadable(() => import("react-json-view"))

Expand Down Expand Up @@ -396,7 +396,7 @@ export default function FragmentGraph({
if (backPressures) {
let value = backPressures.get(`${d.target}_${d.source}`)
if (value) {
return backPressureWidth(value)
return backPressureWidth(value, 30)
}
}

Expand Down Expand Up @@ -482,44 +482,3 @@ export default function FragmentGraph({
</Fragment>
)
}

/**
* The color for the edge with given back pressure value.
*
* @param value The back pressure rate, between 0 and 100.
*/
function backPressureColor(value: number) {
const colorRange = [
theme.colors.green["100"],
theme.colors.green["300"],
theme.colors.yellow["400"],
theme.colors.orange["500"],
theme.colors.red["700"],
].map((c) => tinycolor(c))

value = Math.max(value, 0)
value = Math.min(value, 100)

const step = colorRange.length - 1
const pos = (value / 100) * step
const floor = Math.floor(pos)
const ceil = Math.ceil(pos)

const color = tinycolor(colorRange[floor])
.mix(tinycolor(colorRange[ceil]), (pos - floor) * 100)
.toHexString()

return color
}

/**
* The width for the edge with given back pressure value.
*
* @param value The back pressure rate, between 0 and 100.
*/
function backPressureWidth(value: number) {
value = Math.max(value, 0)
value = Math.min(value, 100)

return 30 * (value / 100) + 2
}
2 changes: 1 addition & 1 deletion dashboard/components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ function Layout({ children }: { children: React.ReactNode }) {
</Section>
<Section>
<NavTitle>Streaming</NavTitle>
<NavButton href="/dependency_graph/">Dependency Graph</NavButton>
<NavButton href="/dependency_graph/">Relation Graph</NavButton>
kwannoel marked this conversation as resolved.
Show resolved Hide resolved
<NavButton href="/fragment_graph/">Fragment Graph</NavButton>
</Section>
<Section>
Expand Down
63 changes: 52 additions & 11 deletions dashboard/components/RelationDependencyGraph.tsx
kwannoel marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
relationTypeTitleCase,
} from "../lib/api/streaming"
import {
Edge,
Enter,
Position,
RelationPoint,
Expand All @@ -33,6 +34,7 @@ import {
generateRelationEdges,
} from "../lib/layout"
import { CatalogModal, useCatalogModal } from "./CatalogModal"
import { backPressureColor, backPressureWidth } from "./utils/backPressure"

function boundBox(
relationPosition: RelationPointPosition[],
Expand All @@ -59,10 +61,12 @@ export default function RelationDependencyGraph({
nodes,
selectedId,
setSelectedId,
backPressures,
}: {
nodes: RelationPoint[]
selectedId: string | undefined
setSelectedId: (id: string) => void
backPressures?: Map<string, number> // relationId-relationId->back_pressure_rate})
}) {
const [modalData, setModalId] = useCatalogModal(nodes.map((n) => n.relation))

Expand Down Expand Up @@ -114,22 +118,59 @@ export default function RelationDependencyGraph({

const isSelected = (id: string) => id === selectedId

const applyEdge = (sel: EdgeSelection) =>
const applyEdge = (sel: EdgeSelection) => {
const color = (d: Edge) => {
if (backPressures) {
let value = backPressures.get(`${d.target}_${d.source}`)
if (value) {
return backPressureColor(value)
}
}

return theme.colors.gray["300"]
}

const width = (d: Edge) => {
if (backPressures) {
let value = backPressures.get(`${d.target}_${d.source}`)
if (value) {
return backPressureWidth(value, 15)
}
}

return 2
}

sel
.attr("d", ({ points }) => line(points))
.attr("fill", "none")
.attr("stroke-width", 1)
.attr("stroke-width", (d) =>
isSelected(d.source) || isSelected(d.target) ? 4 : 2
)
.attr("stroke-width", width)
.attr("stroke", color)
.attr("opacity", (d) =>
isSelected(d.source) || isSelected(d.target) ? 1 : 0.5
)
.attr("stroke", (d) =>
isSelected(d.source) || isSelected(d.target)
? theme.colors.blue["500"]
: theme.colors.gray["300"]
)
kwannoel marked this conversation as resolved.
Show resolved Hide resolved

// Tooltip for back pressure rate
let title = sel.select<SVGTitleElement>("title")
if (title.empty()) {
title = sel.append<SVGTitleElement>("title")
}

const text = (d: Edge) => {
if (backPressures) {
let value = backPressures.get(`${d.target}_${d.source}`)
if (value) {
return `${value.toFixed(2)}%`
}
}

return ""
}

title.text(text)

return sel
}

const createEdge = (sel: Enter<EdgeSelection>) =>
sel.append("path").attr("class", "edge").call(applyEdge)
Expand Down Expand Up @@ -224,7 +265,7 @@ export default function RelationDependencyGraph({
nodeSelection.enter().call(createNode)
nodeSelection.call(applyNode)
nodeSelection.exit().remove()
}, [layoutMap, links, selectedId, setModalId, setSelectedId])
}, [layoutMap, links, selectedId, setModalId, setSelectedId, backPressures])

return (
<>
Expand Down
43 changes: 43 additions & 0 deletions dashboard/components/utils/backPressure.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { theme } from "@chakra-ui/react"
import { tinycolor } from "@ctrl/tinycolor"

/**
* The color for the edge with given back pressure value.
*
* @param value The back pressure rate, between 0 and 100.
*/
export function backPressureColor(value: number) {
const colorRange = [
theme.colors.green["100"],
theme.colors.green["300"],
theme.colors.yellow["400"],
theme.colors.orange["500"],
theme.colors.red["700"],
].map((c) => tinycolor(c))

value = Math.max(value, 0)
value = Math.min(value, 100)

const step = colorRange.length - 1
const pos = (value / 100) * step
const floor = Math.floor(pos)
const ceil = Math.ceil(pos)

const color = tinycolor(colorRange[floor])
.mix(tinycolor(colorRange[ceil]), (pos - floor) * 100)
.toHexString()

return color
}

/**
* The width for the edge with given back pressure value.
*
* @param value The back pressure rate, between 0 and 100.
*/
export function backPressureWidth(value: number, scale: number) {
value = Math.max(value, 0)
value = Math.min(value, 100)

return scale * (value / 100) + 2
}
5 changes: 4 additions & 1 deletion dashboard/lib/api/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ export default function useFetch<T>(
const [response, setResponse] = useState<T>()
const toast = useErrorToast()

// NOTE(eric): Don't put `fetchFn` in the dependency array. It might be a lambda function
useEffect(() => {
const fetchData = async () => {
if (when) {
Expand All @@ -53,6 +52,10 @@ export default function useFetch<T>(

const timer = setInterval(fetchData, intervalMs)
return () => clearInterval(timer)
// NOTE(eric): Don't put `fetchFn` in the dependency array. Otherwise, it can cause an infinite loop.
// This is because `fetchFn` can be recreated every render, then it will trigger a dependency change,
// which triggers a re-render, and so on.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [toast, intervalMs, when])

return { response }
Expand Down
9 changes: 9 additions & 0 deletions dashboard/lib/api/streaming.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
View,
} from "../../proto/gen/catalog"
import {
FragmentVertexToRelationMap,
ListObjectDependenciesResponse_ObjectDependencies as ObjectDependencies,
RelationIdInfos,
TableFragments,
Expand Down Expand Up @@ -130,6 +131,13 @@ export async function getRelationDependencies() {
return await getObjectDependencies()
}

export async function getFragmentVertexToRelationMap() {
let res = await api.get("/fragment_vertex_to_relation_id_map")
let fragmentVertexToRelationMap: FragmentVertexToRelationMap =
FragmentVertexToRelationMap.fromJSON(res)
return fragmentVertexToRelationMap
}

async function getTableCatalogsInner(
path: "tables" | "materialized_views" | "indexes" | "internal_tables"
) {
Expand Down Expand Up @@ -200,6 +208,7 @@ export async function getSchemas() {
return schemas
}

// Returns a map of object id to a list of object ids that it depends on
export async function getObjectDependencies() {
let objDependencies: ObjectDependencies[] = (
await api.get("/object_dependencies")
Expand Down
41 changes: 39 additions & 2 deletions dashboard/lib/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,8 +288,14 @@ export interface LayoutItemBase {

export type FragmentBox = LayoutItemBase & {
name: string
// Upstream Fragment Ids.
externalParentIds: string[]
fragment?: TableFragments_Fragment
fragment: TableFragments_Fragment
}

export type RelationBox = LayoutItemBase & {
relationName: string
schemaName: string
}

export type RelationPoint = LayoutItemBase & {
Expand All @@ -304,6 +310,7 @@ export interface Position {

export type FragmentBoxPosition = FragmentBox & Position
export type RelationPointPosition = RelationPoint & Position
export type RelationBoxPosition = RelationBox & Position

export interface Edge {
points: Array<Position>
Expand Down Expand Up @@ -489,7 +496,7 @@ export function generateFragmentEdges(
// Simply draw a horizontal line here.
// Typically, external parent is only applicable to `StreamScan` fragment,
// and there'll be only one external parent due to `UpstreamShard` distribution
// and plan node sharing. So there's no overlapping issue.
// and plan node sharing. So we won't see multiple horizontal lines overlap each other.
for (const externalParentId of fragment.externalParentIds) {
links.push({
points: [
Expand All @@ -509,3 +516,33 @@ export function generateFragmentEdges(
}
return links
}

export function generateRelationBackPressureEdges(
layoutMap: RelationBoxPosition[]
): Edge[] {
const links = []
const relationMap = new Map<string, RelationBoxPosition>()
for (const x of layoutMap) {
relationMap.set(x.id, x)
}
for (const relation of layoutMap) {
for (const parentId of relation.parentIds) {
const parentRelation = relationMap.get(parentId)!
links.push({
points: [
{
x: relation.x + relation.width / 2,
y: relation.y + relation.height / 2,
},
{
x: parentRelation.x + parentRelation.width / 2,
y: parentRelation.y + parentRelation.height / 2,
},
],
source: relation.id,
target: parentId,
})
}
}
return links
}
Loading
Loading