penpot/backend/test/backend_tests/fdata_upsert_test.clj
Andrey Antukh 67643340a2 ✨ Partition file_change by file_id
Partition `file_change` by `HASH (file_id)` in 16 partitions, with the
same bounds as `file_data`, so a join between them is resolved partition
by partition. The primary key becomes `(file_id, id)`, which a
partitioned table needs, and every access filters by `file_id`.

The new table carries a `kind` column (1 snapshot, 2 revision) as a
reserve: the queries still tell them apart by the label, and `kind`
takes over when they are rewritten. The payload of a snapshot stays in
`file_data`; the `data` column is kept for reading old `legacy-db`
payloads.

The four snapshot commands require `file-id`, because it prunes the
partition. The objects GC locks and deletes by the pair, in batches. The
old table is kept as `file_change_legacy` as the way back.

The automatic migration refuses to run above 10 GiB or 2M rows and
points to the runbook; big instances partition online with that runbook,
which copies by id, follows the `updated_at` watermark in delta passes
and checks that no row of the source is missing from the target.

Closes #12416

AI-assisted-by: deepseek-v4.1-flash
2026-11-05 22:40:17 +01:00

56 lines
2.2 KiB
Clojure

;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS SUBSIDIARY SL
(ns backend-tests.fdata-upsert-test
"Covers the invariants of `fdata/upsert!` that the partition runbook relies
on."
(:require
[app.common.time :as ct]
[app.common.uuid :as uuid]
[app.db :as db]
[app.features.fdata :as fdata]
[app.features.file-changes :as fchanges]
[backend-tests.helpers :as th]
[clojure.test :as t]))
(t/use-fixtures :once th/state-init)
(t/use-fixtures :each th/database-reset)
(t/deftest legacy-db-snapshot-moves-the-updated-at-watermark
;; The runbook of the partition picks the rows changed since the bulk copy
;; with `updated_at`, so every mutation of `file_change` has to move it. The
;; `legacy-db` snapshot path writes the payload into the row, and it used to
;; leave the watermark behind.
(let [profile (th/create-profile* 1)
file (th/create-file* 1 {:profile-id (:id profile)
:project-id (:default-project-id profile)})
id (uuid/next)
past (ct/in-past "2h")
payload (byte-array [1 2 3])]
(th/db-insert! :file-change
{:id id
:file-id (:id file)
:revn 1
:label "v1"
:kind fchanges/snapshot-kind
:updated-at past})
(t/is (true? (fdata/upsert! th/*system*
{:id id
:file-id (:id file)
:type "snapshot"
:backend "legacy-db"
:data payload})))
(let [row (th/db-get :file-change {:id id} {::db/remove-deleted false})]
(t/testing "the payload lands in the legacy column"
(t/is (= (seq payload) (seq ^bytes (:data row)))))
(t/testing "the watermark moves to the moment of the mutation"
(t/is (pos? (compare (inst-ms (:updated-at row)) (inst-ms past))))))))