Big PlantUML diagrams — subgraph, package, !include — staying fast past 1000 nodes

puml.online

A big diagram looks clear to the reader, but the renderer pays the cost — 100 nodes is the watershed. This post distills how to keep big PlantUML diagrams readable and smooth to render.

The 100-node watershed

Empirically:

Node count State
<30 Smooth (<200ms)
30–80 Occasional crowding (500ms–1s)
80–150 Borderline lag (1s–3s)
>150 Graphviz stalls or crashes

Core principle: start layering before 80 nodes (by responsibility), and must split once you cross 150.

First move: subgraph and package

package = named rectangle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@startuml
package "Frontend" {
[WebApp]
[AdminPanel]
[DocsSite]
}

package "Backend" {
[AuthService]
[UserService]
[OrderService]
}

WebApp --> AuthService
WebApp --> OrderService
AdminPanel --> UserService
@enduml

A package is a namespace with a border — visually clustering nodes.

subgraph = flexible grouping

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@startuml
skinparam rectangle {
BackgroundColor<<infra>> #ECECFF
BackgroundColor<<biz>> #FFECEC
}

rectangle "Load Balancer" <<infra>> as lb
rectangle "App Node 1" <<biz>> as n1
rectangle "App Node 2" <<biz>> as n2

subgraph cluster_db {
label "Database Cluster"
rectangle "Primary" <<infra>> as p
rectangle "Replica 1" <<infra>> as r1
}

lb --> n1
lb --> n2
n1 --> p
n2 --> p
n1 .> r1 : fallback
@enduml

subgraph … end is Graphviz’s “cluster” — PlantUML supports it fully; you can colour via <<stereotype>>.

Second move: !include to split into multiple files

How to split

Split by module — but module boundaries must be clean:

1
2
3
4
5
6
7
8
9
/docs/diagrams/
├── system.puml <- top-level
├── service/
│ ├── auth.puml <- 20-30 nodes
│ ├── order.puml
│ └── billing.puml
└── shared/
├── nodes.puml <- common node macros
└── styles.puml <- theme

Pattern A: !include child diagrams

shared/styles.puml:

1
2
3
4
!pragma layout: "elk"
skinparam nodesep 40
skinparam ranksep 50
skinparam defaultFontName "Noto Sans CJK SC"

shared/nodes.puml (standard nodes via macros):

1
2
3
!define SERVICE(name, label) rectangle "==label" <<service>> as name
!define STORE(name, label) database "label" as name
!define QUEUE(name, label) queue "label" as name

system.puml:

1
2
3
4
5
6
7
8
9
@startuml system
!include shared/styles.puml
!include shared/nodes.puml
!include service/auth.puml
!include service/order.puml

auth_service --> order_service
auth_service --> postgres
@enduml

service/auth.puml:

1
2
3
4
5
SERVICE(auth_service, "AuthService")
SERVICE(sso_provider, "SSO")
STORE(postgres, "UserDB")
auth_service --> postgres
auth_service --> sso_provider

Note: !include and render location

By default PlantUML searches relative to the current file plus the stdio include path. In the VS Code plugin, configure plantuml.includepaths; on the CLI use -I:

1
plantuml -I docs/diagrams/shared -tsvg system.puml

Third move: !pragma layout: “elk”

Graphviz’s dot algorithm is “hub-and-spoke” — when nodes pile up, they fight for the centre. ELK (Eclipse Layout Kernel) is a different algorithm, better for hierarchical architectures:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@startuml
!pragma layout: "elk"

package "Frontend" {
[Web] [Mobile]
}
package "Gateway" {
[API GW] [Auth]
}
package "Backend" {
[Order] [Payment] [User]
}

Web --> API GW
Mobile --> API GW
API GW --> Auth
API GW --> Order
API GW --> Payment
Order --> User
@enduml

Requires PlantUML ≥ 1.2024.x and an elk.js install:

1
wget https://www.eclipse.org/elk/downloads/elk-0.7.2.js -O ~/plantuml/elk.js

Empirically: a 150-node microservice architecture takes ~8s with dot, ~2s with elk — and elk produces clearer layering (defaults to data-flow direction).

Fourth move: tidy connections (!useVerticalIf alternative)

A tangled web of edges is another cause of slow render:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
' Anti-pattern: 13 nodes randomly linked
A -> B
A -> C
A -> D
B -> E
C -> E
C -> F
D -> F
E -> G
F -> G
G -> H
H -> I
I -> J
J -> K
' Graphviz can't find a good layout, tries again and again

Fix: put nodes inside package / subgraph so Graphviz can see the spatial structure:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package "Inputs" {
[A] [B] [C] [D]
}
package "Processing" {
[E] [F]
[G] [H]
}
package "Outputs" {
[I] [J] [K]
}

A --> E
A --> F
B --> E
B --> G
C --> F
C --> H
D --> G
D --> H
E --> I
F --> I
F --> J
G --> J
H --> K

Graphviz sees the clear package hierarchy and renders by cutting the layout at package boundaries — render time drops from ~12s to ~1.5s.

Fifth move: !theme

1
!theme cyborg

Render the whole big diagram in a unified theme — visual hierarchy comes from colour, not from cranking nodesep to 100+.

Sixth move: paginated rendering

Only useful in VuePress / Hexo / docs sites. After generating one diagram, split it with a cut/:

1
2
3
<!-- A large diagram is auto-split by PlantUML into multiple pages,
with a nav auto-updating -->
<div class="plantuml-multipage">...</div>

This is what plantuml-multipage does. After installing, {% plantuml %} auto-embeds a “Page 1/10” nav.

Performance optimization checklist

  • Group nodes by business responsibility into package
  • Cross-package edges are explicit (avoid fully bidirectional meshes)
  • At >100 nodes, !pragma layout: "elk"
  • Reuse via !include instead of copy-paste
  • CJK via unified skinparam defaultFontName
  • Big diagrams on local server mode (avoid plantuml.com cross-border)
  • Use plantuml-multipage for “huge diagram” docs pages
  • When a complex sequence gets unwieldy as a component diagram, switch to sequence-diagram + state-machine diagrams as the natural decomposition

A real case: “our e-commerce mid-platform”

Old version: 300 nodes in one component diagram + 600 edges, 30+ seconds render — architecture peers gave up.

New version:

  1. Split into core.puml / fulfillment.puml / marketing.puml / data.puml — 4 top-level diagrams
  2. Each diagram !includes its own sub-modules (30-50 nodes)
  3. Each diagram opens with !pragma layout: "elk" + standard styles.puml
  4. Docs site embeds via plantuml-multipage

Reading order:

1
2
3
4
View core (top-level)
├── render fulfillment sub-module (independent diagram)
├── render marketing sub-module (independent diagram)
└── render data sub-module (independent diagram)

Each diagram renders independently in 1-3 seconds; total reading time <8 seconds, an order of magnitude better than the previous single 30s.

Recap

  • Big diagrams’ biggest bottleneck is Graphviz’s layout algorithm.
  • package + subgraph + !include + !pragma layout elk is the four-piece kit.
  • Splitting is more effective than optimizing: think about splitting past 80 nodes.

Next

  • Title: Big PlantUML diagrams — subgraph, package, !include — staying fast past 1000 nodes
  • Author: puml.online
  • Created at : 2026-07-30 10:13:00
  • Updated at : 2026-08-14 21:34:29
  • Link: https://puml.online/blog/plantuml-large-diagram-en/
  • License: This work is licensed under CC BY-NC-SA 4.0.