From 6c32eddd6916bdd56b5db860bd2db3821e8880b6 Mon Sep 17 00:00:00 2001 From: Daniel Jones Date: Wed, 11 Nov 2026 12:33:25 +0000 Subject: [PATCH 1/2] Skip rewriting indexes that haven't changed on save Statamic puts every index of a collection back into the Stache each time an entry is saved. Most come back exactly as they were read, but each one was exported to PHP and hashed before finding its file already existed. On a 9,000-entry collection that was about 45 ms at the end of every save request. Compare the value with what this request read first, and skip it when nothing changed and no other process has replaced the file since. --- CHANGELOG.md | 6 ++++++ src/FlatpackStore.php | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a33174..0c0681e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Changed + +- **Saving an entry does less work afterwards.** Statamic puts every index of a collection back into the Stache on each save, and most of them come back exactly as they were read. Flatpack used to export each one to PHP again before noticing the file already existed. It now compares first and skips the ones that haven't changed. On the demo's 9,000-entry site, the request that saves a book went from about 165 ms to about 130 ms. + ## 1.2.0 (2026-11-10) ### Changed diff --git a/src/FlatpackStore.php b/src/FlatpackStore.php index 4264009..d8eeae0 100644 --- a/src/FlatpackStore.php +++ b/src/FlatpackStore.php @@ -227,6 +227,8 @@ public function persist(): void flock($lock, LOCK_EX); try { // re-read: other processes may have persisted since this request started + $read = $this->loaded; + $readFrom = $this->pointer ?? []; $this->pointer = null; $this->loaded = []; $pointer = $this->pointer(); @@ -238,6 +240,13 @@ public function persist(): void $pointer = $this->writeLogical($pointer, $logical, $rows ?: null, 'shards'); } foreach ($this->kvDirty as $logical => $value) { + // Statamic puts every index of a collection back on each save, and most come back + // exactly as they were read. Comparing is far cheaper than exporting them again + // to find the file already exists. + if ($value !== null && isset($read[$logical], $pointer[$logical]) + && $pointer[$logical] === ($readFrom[$logical] ?? null) && $read[$logical] === $value) { + continue; + } $pointer = $this->writeLogical($pointer, $logical, $value, 'kv'); } $this->persistFallback(); From 4f463f1d799726027e78fdcf7f01f69cae726edf Mon Sep 17 00:00:00 2001 From: Daniel Jones Date: Wed, 11 Nov 2026 12:57:58 +0000 Subject: [PATCH 2/2] Take the save timings out of the README The demo times the first save after a build, which carries one-off costs, so the Save column and chart overstated the difference. A typical save comes out about level; say that in a sentence instead, along with create and delete being slower. --- README.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index dea5bba..2f1dfca 100644 --- a/README.md +++ b/README.md @@ -29,21 +29,21 @@ Measured with the [Dog-ear Books demo](https://github.com/1994limited/flatpack-s ![Typical page load: Statamic against Flatpack, for each shape and size](https://raw.githubusercontent.com/1994limited/statamic-marketplace/main/flatpack/charts/performance-page.png) -![Saving an entry: Statamic against Flatpack, for each shape and size](https://raw.githubusercontent.com/1994limited/statamic-marketplace/main/flatpack/charts/performance-save.png) - -| Content | Entries | Build | Build memory | Typical page | Save | -|---|---|---:|---:|---:|---:| -| Fixed fields, small | 375 | 952 ms → **610 ms** | 81 MB → **57 MB** | 73 ms → **61 ms** | 101 ms → **81 ms** | -| Fixed fields, medium | 2,250 | 5.0 s → **3.1 s** | 143 MB → **91 MB** | 121 ms → **100 ms** | 240 ms → **171 ms** | -| Fixed fields, large | 9,000 | 22.8 s → **12.5 s** | 375 MB → **213 MB** | 306 ms → **262 ms** | 1.6 s → **578 ms** | -| Page builder, small | 375 | 1.5 s → **700 ms** | 133 MB → **59 MB** | 82 ms → **68 ms** | 112 ms → **82 ms** | -| Page builder, medium | 2,250 | 8.3 s → **3.7 s** | 351 MB → **97 MB** | 149 ms → **131 ms** | 400 ms → **184 ms** | -| Page builder, large | 9,000 | 37.0 s → **15.0 s** | 1.1 GB → **241 MB** | 355 ms → **307 ms** | 1.8 s → **604 ms** | -| Nested page builder, small | 375 | 2.4 s → **800 ms** | 165 MB → **59 MB** | 103 ms → **87 ms** | 123 ms → **87 ms** | -| Nested page builder, medium | 2,250 | 14.1 s → **4.0 s** | 641 MB → **97 MB** | 200 ms → **172 ms** | 582 ms → **195 ms** | -| Nested page builder, large | 9,000 | 58.7 s → **14.7 s** | 2.2 GB → **241 MB** | 554 ms → **473 ms** | 2.3 s → **494 ms** | - -Statamic → **with Flatpack**. "Typical page" is the median of six kinds of page (home, a book list, a book, a journal post, an author, a subject). Saves are single measurements, so read them as a direction. +| Content | Entries | Build | Build memory | Typical page | +|---|---|---:|---:|---:| +| Fixed fields, small | 375 | 952 ms → **610 ms** | 81 MB → **57 MB** | 73 ms → **61 ms** | +| Fixed fields, medium | 2,250 | 5.0 s → **3.1 s** | 143 MB → **91 MB** | 121 ms → **100 ms** | +| Fixed fields, large | 9,000 | 22.8 s → **12.5 s** | 375 MB → **213 MB** | 306 ms → **262 ms** | +| Page builder, small | 375 | 1.5 s → **700 ms** | 133 MB → **59 MB** | 82 ms → **68 ms** | +| Page builder, medium | 2,250 | 8.3 s → **3.7 s** | 351 MB → **97 MB** | 149 ms → **131 ms** | +| Page builder, large | 9,000 | 37.0 s → **15.0 s** | 1.1 GB → **241 MB** | 355 ms → **307 ms** | +| Nested page builder, small | 375 | 2.4 s → **800 ms** | 165 MB → **59 MB** | 103 ms → **87 ms** | +| Nested page builder, medium | 2,250 | 14.1 s → **4.0 s** | 641 MB → **97 MB** | 200 ms → **172 ms** | +| Nested page builder, large | 9,000 | 58.7 s → **14.7 s** | 2.2 GB → **241 MB** | 554 ms → **473 ms** | + +Statamic → **with Flatpack**. "Typical page" is the median of six kinds of page (home, a book list, a book, a journal post, an author, a subject). + +Saving isn't in the table because it comes out about level. On the 9,000-entry nested site, the request that saves a book takes about 140 ms with Statamic and about 130 ms with Flatpack, in roughly half the memory. Creating or deleting an entry is slower with Flatpack, by about a quarter. What it shows: the bigger the site and the deeper its page builders, the more Flatpack saves at build time and in memory (4× faster and 9× less memory for a 9,000-entry site with nested page builders). Page loads improve by 12 to 17%: most of a page's time is spent rendering templates, which Flatpack doesn't change. Your numbers will differ, so run the demo on your own machine: `php artisan demo:compare --all`.