Workspace IndexAlgorithms › Day 11

Topological Sort and DAG Scheduling TODO

Algorithms · Day 11 / 100 · A. Advanced Algorithms & Data Structures (Day 1-19)

Concept

Topological sort orders the vertices of a directed acyclic graph (DAG) so that, for every edge u→v, u comes before v. Kahn's algorithm computes this in O(V+E) by queuing up vertices with in-degree 0 and, each time one is dequeued, decrementing the in-degree of its neighbors; if the queue empties while vertices remain, a cycle exists. From a scheduling angle, each step of the topological sort — the set of vertices whose in-degree hits 0 at the same time — can be grouped into one parallel layer, and the number of layers equals the length of the DAG's longest path. So even with infinite processors, the lower bound on execution time is the longest path, i.e. the critical path — this is the fundamental limit on parallel scheduling. In parallel transaction execution, the DAG's edges are defined by state-access conflicts (write-write or read-write on the same slot), and if the access lists are declared up front, this graph can be built statically before execution even starts.

Every design that tries to run transactions within a block in parallel eventually runs into the limit set by the conflict graph's longest path, so this calculation is what lets you estimate the maximum gain parallelization can actually deliver ahead of time.

Code & Formula

# Day 11: 위상정렬·DAG 스케줄링 — Kahn 알고리즘으로 순서·병렬 레이어·최장 경로를 구한다
# 진입차수 0인 정점을 레이어 단위로 소진시키면 위상순서와 병렬 실행 스케줄이 동시에 나온다.

from collections import defaultdict

def topo_layers(nodes, edges):
    graph = defaultdict(list)
    indeg = {n: 0 for n in nodes}
    for u, v in edges:
        graph[u].append(v)
        indeg[v] += 1

    layer = [n for n in nodes if indeg[n] == 0]
    layers, order, remaining = [], [], dict(indeg)
    while layer:
        layers.append(sorted(layer))
        order.extend(layer)
        next_layer = []
        for u in layer:
            for v in graph[u]:
                remaining[v] -= 1
                if remaining[v] == 0:
                    next_layer.append(v)
        layer = next_layer
    if len(order) != len(nodes):
        raise ValueError("사이클이 존재해 위상정렬 불가")
    return order, layers

# 트랜잭션 5개(A~E)가 스토리지 슬롯 접근으로 서로 의존(충돌)하는 상황을 DAG로 모델링
nodes = ["A", "B", "C", "D", "E"]
edges = [("A", "C"), ("B", "C"), ("C", "D"), ("C", "E")]   # A,B 끝나야 C 실행, C 끝나야 D,E 실행

order, layers = topo_layers(nodes, edges)
print("위상정렬 순서 =", order)
print("병렬 실행 레이어 =", layers)
print(f"레이어 수(=최장 경로 길이) = {len(layers)} -> {len(nodes)}개 트랜잭션을 {len(layers)}단계에 실행 가능")

Exercise

Write a program that takes an arbitrary list of transactions along with the storage keys each one reads and writes, builds the conflict DAG, and uses Kahn's algorithm to output the list of parallel layers and the longest-path length.

Practical Connection

When Verex's CLOB matching results get settled as multiple transactions, ones that only touch different markets or different position tokens are independent in the conflict graph — meaning they can be grouped into a parallel layer, with room to lower batch-processing cost.

If you study this on a given day, add a note link and a ✅ to this line in the source curriculum (docs/knowledge/dev-100-curriculum.md) and this spot will lead straight to the note body. You can also write directly on this page — but regenerating overwrites it, so it's safer to keep anything you want to save as markdown under docs/algorithms/.


한국어

위상정렬·DAG 스케줄링 TODO

Algorithms · Day 11 / 100 · A. 고급 알고리즘·자료구조 (Day 1–19)

개념

위상정렬은 방향 비순환 그래프(DAG)의 정점을 모든 간선 u→v에 대해 u가 v보다 앞서도록 나열하는 것이다. Kahn 알고리즘은 진입차수 0인 정점을 큐에 넣고 꺼낼 때마다 인접 정점의 진입차수를 줄이는 방식으로 O(V+E)에 이를 계산하며, 큐가 비었는데 남은 정점이 있으면 사이클이 존재한다는 뜻이다. 스케줄링 관점에서는 위상정렬의 각 단계, 즉 동시에 진입차수가 0이 되는 정점 집합을 하나의 병렬 레이어로 묶을 수 있고, 이때 레이어 수는 DAG의 최장 경로 길이와 같다. 따라서 프로세서가 무한히 많아도 실행 시간의 하한은 최장 경로(critical path)이며, 이것이 병렬 스케줄의 근본 한계다. 트랜잭션 병렬 실행에서 DAG의 간선은 상태 접근 충돌(같은 슬롯에 대한 write-write 또는 read-write)로 정의되고, 접근 목록이 사전에 선언되면 이 그래프를 실행 전에 정적으로 구성할 수 있다.

블록 내 트랜잭션을 병렬로 돌리려는 모든 설계는 결국 '충돌 그래프의 최장 경로'라는 한계에 부딪히므로, 병렬화로 얻을 수 있는 최대 이득을 미리 추정하려면 이 계산이 필요하다.

코드 · 수식

# Day 11: 위상정렬·DAG 스케줄링 — Kahn 알고리즘으로 순서·병렬 레이어·최장 경로를 구한다
# 진입차수 0인 정점을 레이어 단위로 소진시키면 위상순서와 병렬 실행 스케줄이 동시에 나온다.

from collections import defaultdict

def topo_layers(nodes, edges):
    graph = defaultdict(list)
    indeg = {n: 0 for n in nodes}
    for u, v in edges:
        graph[u].append(v)
        indeg[v] += 1

    layer = [n for n in nodes if indeg[n] == 0]
    layers, order, remaining = [], [], dict(indeg)
    while layer:
        layers.append(sorted(layer))
        order.extend(layer)
        next_layer = []
        for u in layer:
            for v in graph[u]:
                remaining[v] -= 1
                if remaining[v] == 0:
                    next_layer.append(v)
        layer = next_layer
    if len(order) != len(nodes):
        raise ValueError("사이클이 존재해 위상정렬 불가")
    return order, layers

# 트랜잭션 5개(A~E)가 스토리지 슬롯 접근으로 서로 의존(충돌)하는 상황을 DAG로 모델링
nodes = ["A", "B", "C", "D", "E"]
edges = [("A", "C"), ("B", "C"), ("C", "D"), ("C", "E")]   # A,B 끝나야 C 실행, C 끝나야 D,E 실행

order, layers = topo_layers(nodes, edges)
print("위상정렬 순서 =", order)
print("병렬 실행 레이어 =", layers)
print(f"레이어 수(=최장 경로 길이) = {len(layers)} -> {len(nodes)}개 트랜잭션을 {len(layers)}단계에 실행 가능")

연습

임의의 트랜잭션 목록과 각 트랜잭션이 읽고 쓰는 스토리지 키 집합을 입력으로 받아 충돌 DAG를 만들고, Kahn 알고리즘으로 병렬 레이어 목록과 최장 경로 길이를 출력하는 프로그램을 작성해 보라.

실무 · Verex 연결

Verex의 CLOB 매칭 결과를 여러 트랜잭션으로 정산할 때, 서로 다른 마켓·서로 다른 포지션 토큰만 건드리는 트랜잭션들은 충돌 그래프에서 독립적이므로 병렬 레이어로 묶여 배치 처리 비용을 낮출 여지가 있다.

공부한 날 원본 커리큘럼(docs/knowledge/dev-100-curriculum.md)의 이 줄에 노트 링크와 ✅ 를 붙이면, 이 자리는 노트 본문으로 바로 이어집니다. 노트 없이 이 페이지에 바로 적어도 됩니다 — 다만 다시 생성하면 덮어쓰이므로, 남길 글은 docs/algorithms/ 의 마크다운으로 쓰는 편이 안전합니다.

← 10. 세그먼트 트리 심화12. 최대 유량·최소 컷과 매칭 →