mirror of
https://github.com/penpot/penpot.git
synced 2026-09-12 06:58:57 +00:00
5.6 KiB
5.6 KiB
Backend Storage
Abstraction
app.storagestores binary objects.- Each object has a
storage_objectdatabase row. - The row stores the UUID, size, backend, timestamps, and Transit metadata.
- The backend stores the binary content.
- Supported backends are
:fsand:s3. - FS uses one root directory and a UUID-derived path.
- S3 uses one configured bucket and an optional prefix.
- A Penpot bucket is metadata. It is not an S3 bucket or a filesystem directory.
- FS and S3 use the same UUID-derived object path. The bucket does not change the path.
PENPOT_OBJECTS_STORAGE_*configures the current object backend.- Deprecated asset-storage config keys remain supported for migration.
- Database rows keep the backend name. Keep the legacy
:assets-fsand:assets-s3aliases.
Object Lifecycle
put-object!creates the database row before it writes backend content.- Backend content is written only when the row is new.
- A failed backend write can leave an unreferenced database row.
- Callers often set
:touched-atso garbage collection can remove such rows. get-objectexcludes rows withdeleted_at.- Existing object values can remain readable until physical deletion.
:expired-atblocks reads after the expiration time.del-object!setsdeleted_at. It does not remove backend content.storage-gc-deletedremoves the database row and backend content after the deletion delay.storage-gc-touchedfinds references before it setsdeleted_at.objects-gcremoves deleted domain rows and touches their storage object IDs.- Use
::db/reuse-conn truewithsto/resolveinside a database transaction.
Deduplication
- Deduplication requires
::sto/deduplicate?, a content hash, and bucket metadata. - The lookup matches hash, bucket, backend, and
deleted_at IS NULL. - The lookup does not include file ID, profile ID, team ID, or organization ID.
- Objects can therefore share content across users and files within one bucket.
- Deleted objects are not reused.
tempfileobjects never use deduplication, even when the caller requests it.- Use
sto/wrap-with-hashwhen the caller already calculated the content hash.
Bucket Rules
| Bucket | Content and references | Dedup | Direct /assets/by-id access |
Cleanup |
|---|---|---|---|---|
file-media-object |
Original file images and generated media thumbnails. References: file_media_object.media_id and thumbnail_id. |
Yes | Public | Reference scan. |
team-font-variant |
Font variants in team_font_variant. References: woff1_file_id, woff2_file_id, otf_file_id, and ttf_file_id. |
Yes | Public | Reference scan. |
file-object-thumbnail |
Frame and component thumbnails in file_tagged_object_thumbnail.media_id. |
Yes | Public | Reference scan. |
file-thumbnail |
File grid thumbnails in file_thumbnail.media_id. |
Yes | Authentication required | Reference scan. |
profile |
User and team profile photos. References: profile.photo_id and team.photo_id. |
Yes | Authentication required | Reference scan. |
organization |
Organization logos uploaded by the Nitrate management API. | Yes | Public | No reference scan. A touched object is deleted. |
tempfile |
Export files, chunked-upload chunks, and temporary font downloads. | No | Authentication required | No reference scan. A touched object uses a two-hour deletion delay. |
file-data |
Encoded file data when file-data-backend is storage. Reference metadata has storage-ref-id, file-id, and the file_data row ID. |
Yes | Authentication required | Reference scan. |
file-data-fragment |
Compatibility value for file-data fragments. The current backend has no dedicated producer for this bucket. | No current write semantics | Public | No touched-object collector case. |
file-change |
Compatibility value for file changes. Current snapshots store data in file_data, not this bucket. |
No current write semantics | Authentication required | No touched-object collector case. |
- The valid bucket set lives in
app.storage/valid-buckets. file-media-objectis the default bucket for old rows without bucket metadata.- Do not assign a new bucket without adding its access and cleanup behavior.
- The touched-object collector raises an internal error for an unknown bucket.
- It supports
file-media-object,team-font-variant,file-object-thumbnail,file-thumbnail,profile,file-data,tempfile, andorganization. - It does not support
file-data-fragmentorfile-change.
Access Rules
app.http.assetsdecides direct object authentication from the bucket.- Public buckets are
file-media-object,file-object-thumbnail,team-font-variant,file-data-fragment, andorganization. - Other valid buckets require a session or access-token profile ID.
- File-media routes also require file read permission.
- Non-public direct responses set
content-disposition: attachment. - FS responses use
x-accel-redirectfor the configured asset path. - S3 responses use a presigned URL and an HTTP redirect.
File Data
file-data-backendacceptslegacy-db,db, orstorage.legacy-dbstores main data infile.dataand snapshots infile_change.data.dbstores encoded data infile_data.data.storagestores encoded data in storage subsystem withfile-databucket and keepsdatanil infile_datatable.- The
file_data.metadata.storage-ref-idvalue points to the storage object. fdata/upsert!touches a storage object from incoming metadata before it stores the new row.- File snapshots use
file_datafor snapshot data andfile_changefor snapshot metadata.