# What Breaks When a Database Diagram Reaches 1,000 Tables, and What We Redesigned

> Around 1,000 tables a database diagram stops being a drawing problem and becomes a scaling problem. In Schemity, drawing every table at once, comparing every line with every other, redrawing a 624-relation table on every move and painting a 134-megapixel export all broke. After the redesign a 1,035-table diagram opens in about 10 seconds and idles at 1.1 to 1.7 GB on a 16 GB M1 Pro. The next limit found is the line-jump pass, which grows much faster than the diagram.

Source: https://schemity.com/blog/database-diagram-1000-tables-memory-cpu/

Going from a few hundred tables to a thousand is where a database diagram stops being a drawing and becomes a performance problem. On a 1,035-table, 1,822-relation PostgreSQL diagram, four things broke in Schemity: drawing every table at once, comparing every line with every other, redrawing a 624-relation table on every move, and painting the PNG export as one 134-megapixel canvas. Each one needed a redesign, and each redesign has a cost, measured below.

The result opens in about 10 seconds and sits at 1.1 to 1.7 GB with no CPU use while idle, on a 16 GB laptop. That is the user-facing answer. The rest of this post is the engineering behind it: what failed, what replaced it, what the replacement gave up, the export that needed 10.4 GB, and where the design breaks next, because it does.

## The test diagram and how it was measured

The diagram is a generated ERP-style PostgreSQL schema, built through Schemity's MCP server for stress testing rather than taken from a customer: 33 business domains from identity and billing to payroll and logistics, laid out in one diagram. It is the same model the app's stress test loads, and its limits as a benchmark are spelled out near the end.

| What | Count |
| --- | --- |
| Tables | 1,035 |
| Columns | 9,407 |
| Relations (foreign keys) | 1,822 |
| Most relations on one table | 624, on `iam_users` |
| Check and unique constraints | 1,124 |
| Legends (domain boxes) | 33 |
| Diagram size | 15,760 by 14,178 px |
| Diagram file on disk | 13.2 MB of JSON |

The machine is an Apple M1 Pro with 8 cores and 16 GB of memory, on macOS 27, running Schemity 2.12.1. Memory is the physical footprint macOS reports, the number Activity Monitor shows, summed over the app's own process and the WebKit processes that draw its window. It was sampled at least once a second while each step ran, with a pause of about 20 seconds between steps so each one shows up on its own in the log.

## How much memory does an ERD for a large database need?

About 1.1 GB once it is open, rising to 1.6 to 1.7 GB after you have moved around it, with short peaks above that:

| Step | Time | Peak memory | Memory after | CPU |
| --- | --- | --- | --- | --- |
| App open, home screen | - | 183 MB | 183 MB | none |
| Open the 1,035-table diagram | about 10 s to usable | 1.9 GB | 1.1 GB | 12 s, about one core |
| Move across it with the minimap and search | smooth | 3.1 GB | about 1.6 GB | 44 s over the whole step |
| Drag the 624-relation table and drop it | works, with visible lag | 2.4 GB | 1.6 GB | 11 s over the whole step |
| Open Lint on the whole schema | instant | 1.9 GB | 1.7 GB | 11 s over the whole step |
| Idle with the diagram open | - | - | - | none |

![Line chart of Schemity 2.12.1 whole-app memory over one 545-second run on the 1,035-table diagram: about 0.2 GB on the home screen, a 1.9 GB peak while opening that settles at 1.1 GB, a 3.1 GB peak on the first trip across the diagram, 2.4 GB while dragging the 624-relation table, 1.9 GB opening Lint, and a spike to 10.4 GB during PNG export, with a dashed line at 2.9 GB for the same export in a test build of the next release](https://schemity.com/images/blog/database-diagram-1000-tables-memory-cpu/memory-timeline.svg)

Almost all of the memory sits in WebKit's content process, which holds the canvas and the diagram's data. The app's own process stayed between 33 and 109 MB throughout, and the GPU process rose to about 290 MB while the view was moving and fell back below 30 MB when it stopped. With the diagram open and nothing happening, the CPU counter did not move for a minute: no CPU while idle. Each row of that table is the product of one of the redesigns below.

## How do you open a 1,000-table diagram without drawing every table?

You draw only what is on screen. Every table in a Schemity diagram is cached as a bitmap, so panning moves pictures instead of redrawing text. Before 2.12.1, opening a diagram rasterized every table into that cache whether it was near the screen or not, twice: once when it mounted and again when the diagram font finished loading. On 1,035 tables that was 2,070 bitmap renders and 13.7 seconds of frozen canvas after the diagram had already appeared.

Now a table outside the viewport is not drawn at all. It keeps a note of what it still owes, the text layout the font change asks for and its bitmap, and pays it in the frame where it first scrolls into view. Opening costs only the tables you can see, which is how the same diagram became usable in about 10 seconds.

The cost is memory that grows as you explore. A table that has been drawn keeps its bitmap, so panning back over it is cheap, but the cache only grows. In this run, moving around the diagram took it from 1.1 GB to about 1.6 GB, and the 3.1 GB peak in the table came during that first trip across.

## Why does comparing every line with every other stop working?

Because the number of pairs grows with the square of the lines. Where two relation lines cross, Schemity draws a small hop so you can tell which line goes where, and finding the crossings means testing segments against each other. The 1,822 relations here are 21,254 straight segments, which is 226 million pairs, and testing them all took 1.4 seconds, after every single drop.

The replacement sorts the segments by their left edge and sweeps across them, so a segment is only tested against the segments whose horizontal range overlaps its own. It finds exactly the same crossings, in about 0.2 seconds. The pass was also moved out of the frame that shows the drop: the table lands and the snap guide clears at once, and the hops follow a moment later. Until they do, a line that just moved is drawn without hops rather than with hops at its old crossings.

That sweep has a known weakness, and it is the first limit in the scaling run further down: it only prunes by horizontal position. Segments stacked in the same vertical band are still tested against each other, and a bigger diagram stacks more of them.

## How do you drag a table with 624 relations?

By not drawing the real lines while it moves. A high-degree table is the worst case for an interactive diagram, and `iam_users`, with 624 relations, made every part of the drag path visible:

- Every move re-rendered, committed and redrew all 624 routed relations.
- Starting and ending a drag re-rendered every one of the 1,035 tables, twice, because of one diagram-wide dragging flag, at over a second of React work each time.
- Every table carried its selection ring, resize handles and other controls whether selected or not, about 5,000 components, and two of them loaded an icon before drawing nothing, so every drop set off about 2,000 icon loads.

Now the dragging state is shared by reference instead of passed to every table, the controls exist only on the selected table, and a table with 30 or more relations is dragged through a lightweight preview. Its real lines are hidden, and one shape draws a straight line from its centre to the centre of each related table, re-aimed directly on each move with no routing and no React render. Aiming all 624 lines costs under 0.1 ms per frame, and on drop the 624 real routes are recomputed in about 7 ms and drawn once.

This is a deliberate trade of looks for usability. For the length of the drag the lines run straight through whatever lies between, instead of routed around the other tables with clean right angles, so the diagram looks rougher than it really is. The properly routed lines come back the moment you drop. Even so, the drag is the one step that does not feel smooth: the table follows the pointer with a visible lag instead of stalling as it did before, but it is not the fluid drag a small table gets.

## Why did exporting the diagram to PNG need 10 GB?

Because the image was drawn the way a browser draws anything: into one canvas. Schemity's window is a web view, and a 1,035-table diagram exported at the largest scale the pixel budget allows, about 0.77 of its size on screen, is 12,208 by 10,994 pixels, 134 million pixels in one canvas. Before 2.12.1 the export always asked for its usual 2x scale, a 909-million-pixel canvas the browser refused, and silently wrote no file. 2.12.1 fits the scale to the pixel budget, and the export works, but the whole app went from 1.7 GB to a 10.4 GB peak, more than an 8 GB laptop has. An instrumented build showed where most of the jump came from: drawing the diagram into the canvas took 3.2 seconds and added about 3.3 GB, and WebKit's PNG encoder added about another 3.2 GB. JPEG, which goes through the same canvas, cost about the same.

We confirmed it was the canvas and not the diagram by exporting the same diagram as SVG, which writes the drawing out as text and never creates a canvas. That export added about 240 MB. So the next release no longer asks the browser to paint the image. On desktop, PNG and JPEG export now take the SVG export and render it natively with resvg, an SVG renderer written in Rust, encoding the PNG row by row so the pixels are never copied whole:

| Export of the 1,035-table diagram | 2.12.1 | Next release (test build) |
| --- | --- | --- |
| PNG peak, whole app | 10.4 GB | 2.9 GB |
| JPEG peak, whole app | about the same as PNG | 2.8 GB |
| SVG, added while exporting | about 240 MB | unchanged |
| Image size | 12,208 by 10,994 px | the same |

The next-release figures come from a test build with the export code optimized as it will ship. The renderer alone, timed outside the app, peaks at about 925 MB and takes 8.7 seconds for this diagram, against about 7 seconds for the whole old export. One detail cost a factor of three: with every installed font available for fallback, a copy of the diagram font installed on the test machine competed with the bundled one and was mapped from disk again for each of the 27,811 text elements, which took the render from 8.5 to 29 seconds until the bundled copy was made the only one. The price is 2.3 MB more app binary, for the renderer and that bundled font. Until the release reaches you, export a diagram this size as SVG: it is the cheap path today.

## How long does it take an AI agent to route 1,822 relations?

It used to be never. Schemity's MCP server lets an AI agent sort a large schema into domains with one call, `group_entities`, which places the legends and then routes every relation around the tables. On 1,035 tables and 1,822 relations that call never finished. Each re-routing pass rebuilt its record of where every other route already ran, once per relation, and re-totalled the whole layout twice for every attempt.

The router now takes one route out of a running count and puts it back, and prices only the line that changed. Its A* search keeps its best costs in flat arrays reused across searches instead of hash maps. Routes came out byte-identical on 162- and 400-table diagrams, and 400 tables went from 26.8 seconds to 3.6. Past 500 relations the re-routing stops after 2,000 searches in total, a count rather than a clock, so the same input still gives the same routes. When that change landed, 1,035 tables in 33 domains went from over 3 minutes to about 85 seconds end to end. A later change compiled the routing engine for speed instead of size, which cut a full 1,822-relation routing pass from 33.2 to 22.0 seconds for 0.4 MB of app binary; the end-to-end call was not re-timed after it.

## Does opening on the Context Map use less memory?

In the next release, yes: a diagram can open on its Context Map instead of the full canvas. A context view is a saved view of the diagram that shows only the tables you picked and the relations between them, and the Context Map shows every context view as one box with its table count, with arrows between boxes that come from the foreign keys. The canvas behind the main diagram is now built only the first time something other than the map is shown, so a diagram set to open on the map draws none of its 1,035 tables on the way in.

To measure it, this diagram's 33 domains became 33 context views, one per legend, which grew the file from 13.2 to 15.3 MB. The diagram was then set to open on the map, with Open on in its connection settings, and opened in a local release build of the next version, sampled the same way:

| Step | Time | Peak memory | Memory after |
| --- | --- | --- | --- |
| Open the diagram on its [Context Map](https://schemity.com/doc/context-map/) | about 4 s | 0.74 GB | 0.46 GB |
| Enter the Sales context view, 36 tables | instant | 0.69 GB | 0.47 GB |
| Switch to the main diagram, all 1,035 tables | about 3 s | 1.5 GB | 1.2 GB |

Against opening straight onto the main diagram in 2.12.1 (1.9 GB peak, 1.1 GB after, about 10 s), opening on the map in the next version's build settles at about 40% of the memory in less than half the time. Someone who works in one domain settles under half a gigabyte, which matters more on an 8 GB laptop than any single peak. The full diagram is still one step away, and with the data already loaded it takes about 3 s.

## Where does the architecture break next?

To find out without a bigger real schema, the engine was run on copies of this diagram: the same 1,035 tables tiled 2, 4 and 8 times across a larger canvas, each copy renamed so nothing collides, keeping the real routed lines and one 624-relation hub per copy. These are engine timings on the same M1 Pro, run in Node outside the app, so they exclude drawing:

| Tables | Relations | Line jumps, after every drop | Lint, whole schema | Recompute the hub's 624 relations on drop | Drag preview, per frame |
| --- | --- | --- | --- | --- | --- |
| 1,035 | 1,822 | 0.19 s | 0.04 s | 6.9 ms | 0.06 ms |
| 2,070 | 3,644 | 0.48 s | 0.07 s | 5.4 ms | 0.04 ms |
| 4,140 | 7,288 | 2.1 s | 0.13 s | 5.6 ms | 0.07 ms |
| 8,280 | 14,576 | 5.5 s | 0.28 s | 8.2 ms | 0.04 ms |

Three of the four scale well. Lint grows in step with the schema. The drag of a 624-relation table costs the same at 8,280 tables as at 1,035, because the drag only touches that table's own relations: its cost follows the table's degree, not the diagram's size, which is the property the preview was built for.

The line-jump pass does not. Eight times the relations cost 29 times the time, and it grows faster than the diagram from the first doubling: 2.6 times the time for twice the relations, then 4.3 times for the next doubling. The cause is the horizontal sweep from earlier, which still tests every pair of segments that share a vertical band, and a larger diagram stacks more of them. How much depends on layout: the copies here are tiled in a grid, so they share columns, and a wider or taller arrangement would give a different curve. The pass runs on the main thread after any change to the relations, a drop, an edit or an undo, and once synchronously while the diagram opens, so at 8,280 tables it would hold the window for about 5.5 seconds each time in Node. WebKit was not measured at that size. A grid that also prunes by vertical position would remove that weakness.

## What this benchmark does not show

Publishing the numbers is only useful with their limits next to them:

- **One synthetic schema.** The model is generated, and its topology is cleaner than a real legacy database: every table sits in exactly one domain, and Schemity's own router laid out every line. A real schema can have tables that relate to everything, inferred relations with no foreign key behind them, and a layout nobody planned.
- **Modest density.** 1,822 relations over 1,035 tables is about 1.8 per table. The single 624-relation hub is the deliberate worst case, but a schema that is dense everywhere would stress the line-jump pass sooner.
- **One machine.** Every number comes from a 16 GB M1 Pro. There are no measurements here for an 8 GB laptop, Windows or Linux, where the web view is a different engine.
- **No full-app curve past 1,035 tables.** The scaling table is engine-only, and it repeats one topology rather than growing a new one. Memory, drawing and export were not measured on the larger copies.
- **Test builds for what is unreleased.** The native export and Open on figures come from builds of the next release, not from a shipped version.

## Is it worth keeping a 1,000-table schema in one diagram?

Yes, in one file, and no, not on one screen. The point of loading the whole schema is that nothing is missing when you go looking: every table and every foreign key between domains is in the diagram, and on a real database Schemity reads them from the live schema, so you can understand your schema without first deciding which half to ignore. Reading it is a different job, done one domain at a time.

Schemity is database design software that reads your live database, shows the impact of every schema change before it runs, and keeps the diagram as a file in Git. A payroll engineer can open the payroll context view without the other 1,000 tables around it. [Context views](https://schemity.com/doc/context-views/) and [legends](https://schemity.com/doc/legends-and-annotations/) are how this diagram's 33 domains stay readable, and [Lint](https://schemity.com/doc/schema-lint/) checks all 1,035 tables at once whichever view you are in. The diagram file itself is plain JSON, 13.2 MB for this schema, so it [lives in Git](https://schemity.com/doc/version-control-git/) like the code it describes.

If the large schema is one you inherited, [grouping a legacy database by domain](https://schemity.com/blog/reverse-engineer-legacy-database-group-by-domain/) covers how to get from a wall of tables to named domains, with an AI agent doing the first pass. And for what a diagram of a live database should and should not let an agent touch, see [what a database MCP server should let an agent do](https://schemity.com/blog/database-mcp-server-sql-access-or-schema-only/).

## Frequently asked questions

### How much RAM does a 1,000-table database diagram use?

In this test, a 1,035-table PostgreSQL diagram with 9,407 columns and 1,822 relations used about 1.1 GB right after opening and 1.6 to 1.7 GB after moving around it, on an Apple M1 Pro with 16 GB. Peaks were higher for a few seconds: 1.9 GB while opening and 3.1 GB while visiting parts of the diagram for the first time. Opened on its Context Map instead, the same diagram settled at 0.46 GB in a test build of the next release.

### Why does exporting a huge diagram to PNG use so much memory?

A browser-based renderer draws the whole diagram into one canvas the size of the image, here 12,208 by 10,994 pixels, and then encodes it. In WebKit that took the app from 1.7 GB to a 10.4 GB peak for this diagram, while the SVG export of the same diagram added about 240 MB. Rendering the SVG natively instead brings the PNG export peak down to 2.9 GB in a test build of the next release.

### How many tables can Schemity handle?

A 1,035-table diagram is measured end to end in the app. Past that, only the engine was measured, on copies of the same diagram up to 8,280 tables and 14,576 relations: lint and the drag of a 624-relation table stayed fast, but the line-jump pass that runs after every drop grew from 0.19 to 5.5 seconds. Of the engine paths measured, that one grew fastest; memory past 1,035 tables was not measured.
