penpot/backend/resources/climit.edn
Álvaro Tejero Cantero 81a605f11d
✨ Report what the listing and the branch open cost, and bound the concurrency
Three of the preview's paths reported no duration, and two of them were
the slowest a user meets. A trial can be asked what a merge cost and
could not be asked what the panel cost.

The listing now passes through `audited` with two counters beside the
duration, being how many open branches it compared and how many of those
the summary cache answered, because a listing's cost is entirely
explained by that ratio. The vector result was the one risk and it holds:
a Clojure vector carries metadata, and nothing between the handler and
the audit step rebuilds the value.

A branch open now reports the derive's own time, its op-log depth, and
its batch count, from `branch-file-data` itself. Depth is what predicts
the cost, so the record carries it. An ordinary read never enters that
function, so it gains no line, no prop, and no timer.

A branch save gains the log line the other five print, and its timer
moved rather than gaining a caveat. It now opens before `get-file`, so
the number covers the read and, for a branch, the derive inside it, which
is what compare, merge, update and materialize have always covered. The
six figures are comparable for the first time.

The concurrency limits were decoration. Thirteen ids were declared across
the branch and pull-request commands and `climit.edn` configured none, so
`climit/wrap` logged a warning at every start and ran each handler
unwrapped, leaving the root chain's forty global permits as the only
bound on an operation that holds three realized documents. Every declared
id now has an entry, the two readers that realize whole documents gained
a declaration, and each number is justified in a comment against
`create-file-snapshot` as its anchor and marked provisional until a
peak-heap measurement replaces it.

The test helper drives the private save path directly, so it opens the
timer the RPC method would have opened.

Verified: 73 tests, 694 assertions, 0 failures across the three branch
suites and `rpc-file-test`.
2026-10-06 10:36:55 +02:00

177 lines
6.4 KiB
Clojure

;; Example climit.edn file
;; Required: permits
;; Optional: queue, ommited means Integer/MAX_VALUE
;; Optional: timeout, ommited means no timeout
;; Note: queue and timeout are excluding
{:update-file/global {:permits 20}
:update-file/by-profile
{:permits 1 :queue 5}
:process-font/global {:permits 4}
:process-font/by-profile {:permits 1}
:process-image/global {:permits 8}
:process-image/by-profile {:permits 1}
:auth/global {:permits 8}
:root/global
{:permits 40}
:root/by-profile
{:permits 10 :queue 30 :timeout 30000}
:file-thumbnail-ops/global
{:permits 20}
:file-thumbnail-ops/by-profile
{:permits 2}
:submit-audit-events/by-profile
{:permits 1 :queue 3}
:restore-file-snapshot/global
{:permits 3}
:restore-file-snapshot/by-profile
{:permits 1 :queue 2 :timeout 60000}
:create-file-snapshot/global
{:permits 3}
:create-file-snapshot/by-profile
{:permits 1 :queue 2 :timeout 60000}
:send-user-feedback/global
{:permits 4}
:send-user-feedback/by-profile
{:permits 1 :queue 3}
:import-binfile/global
{:permits 4}
:import-binfile/by-profile
{:permits 1 :queue 2}
:profile-plugin-ops/global
{:permits 4}
:profile-plugin-ops/by-profile
{:permits 1 :queue 2}
;; Every limit below is provisional. The container that runs the preview
;; has no heap cap: `docker/devenv/files/entrypoint.sh` and
;; `backend/scripts/_env` both replace JAVA_OPTS without `-Xmx`, and the
;; live JVM command line carries none, so the budget is the JVM default
;; and it is unmeasured. The permit counts are derived from the
;; neighbours above instead of from a memory figure, and they stay
;; provisional until a peak-heap measurement of a cold compare on the
;; design-system file replaces them.
;;
;; The anchor is the snapshot pair above: `:create-file-snapshot/global`
;; admits three whole-file operations at once, each realizing one
;; document and writing one snapshot, and `:restore-file-snapshot/global`
;; admits the same three for an operation that also writes a rollback
;; snapshot and the older payload back into the file. A branch operation
;; realizes more than one document, so it has to admit fewer at once to
;; keep the same number of realized documents in flight.
;;
;; What each operation holds, read from the code:
;; * `create-file-branch` realizes main once and writes one snapshot
;; (`backend/src/app/rpc/commands/files_branch.clj::create-file-branch`),
;; which is the anchor's shape and the anchor's cost.
;; * `merge-file-branch` and `update-branch-from-main` hold three
;; realized documents at once: main, the derived branch, and the base
;; snapshot (`files_branch.clj::merge-file-branch`,
;; `files_branch.clj::update-branch-from-main`). Both also write a
;; snapshot and a whole file payload, and the update computes a
;; second diff for the squash, so they earn the tightest count.
;; * `materialize-file-branch` realizes the branch and the libraries it
;; validates against, then writes the whole payload
;; (`files_branch.clj::materialize-file-branch`), so it sits between
;; the anchor and the merge pair.
;; * `create-pull-request` and `update-pull-request-snapshot` realize the
;; branch once and write one review snapshot
;; (`files_pull_request.clj::create-pull-request`,
;; `files_pull_request.clj::update-pull-request-snapshot`), which is
;; the anchor's shape again.
;;
;; The write-side operations carry a queue and a timeout so that a
;; refusal follows a bounded wait rather than arriving instantly. The
;; queue sits on the per-profile entry, following the anchor, and on the
;; global entry where the command declares no per-profile queue. The
;; per-profile entry of one permit keeps a single designer to one
;; operation at a time. The two read-side entries are generous by design
;; and carry no queue and no timeout, so a panel open waits for its turn
;; instead of being refused: six three-document comparisons hold
;; eighteen realized documents, fewer than the twenty one-document saves
;; `:update-file/global` already admits.
;; create-file-branch realizes main and writes one snapshot, the shape
;; of `:create-file-snapshot` above, so it takes the anchor's numbers
;; unchanged. Provisional.
:create-file-branch/global
{:permits 3}
:create-file-branch/by-profile
{:permits 1 :queue 2 :timeout 60000}
;; A merge holds three realized documents, a snapshot write, and a
;; whole-file write into main, so one instance-wide operation holds the
;; anchor's whole three-permit budget on its own. Provisional.
:merge-file-branch/global
{:permits 1}
:merge-file-branch/by-profile
{:permits 1 :queue 2 :timeout 60000}
;; An update from main holds the same three documents and pays for a
;; snapshot write and a squash diff on top, so it takes the merge's
;; count. Provisional.
:update-branch-from-main/global
{:permits 1}
:update-branch-from-main/by-profile
{:permits 1 :queue 2 :timeout 60000}
;; Materialize realizes one document plus its libraries and writes the
;; payload, which is cheaper than a merge and dearer than the anchor,
;; so it admits two where the merge admits one and the anchor admits
;; three. Provisional.
:materialize-file-branch/global
{:permits 2}
:materialize-file-branch/by-profile
{:permits 1 :queue 2 :timeout 60000}
;; A cold listing realizes main once and derives every uncached branch,
;; which makes it the slowest read of the preview. Six at once is above
;; what a panel open needs under normal use, and six three-document
;; operations hold eighteen realized documents, below the twenty
;; one-document saves `:update-file/global` above already admits.
;; Provisional.
:get-file-branches/global
{:permits 6}
;; The compare dialog's query holds main, the derived branch, and the
;; base at once, the same three documents the listing holds, so it takes
;; the same count. Provisional.
:get-branch-diff/global
{:permits 6}
;; Creating a pull request realizes the branch and pins one review
;; snapshot, the anchor's shape. Provisional.
:create-pull-request/global
{:permits 3}
:create-pull-request/by-profile
{:permits 1 :queue 2 :timeout 60000}
;; Publishing the branch state also realizes the branch and pins one
;; snapshot. The command declares a single instance-wide queue
;; (`files_pull_request.clj::update-pull-request-snapshot`), so the
;; bounded wait sits on the global entry here rather than on a
;; per-profile entry. Provisional.
:update-pull-request-snapshot/global
{:permits 3 :queue 4 :timeout 60000}}