Przechowywanie plików
Każdy backend danych aplikacji otrzymuje obok swoich tabel prywatny magazyn obiektów. Podczas gdy backend danych przechowuje wiersze szeregów czasowych, przechowywanie plików mieści wszystko, co nie mieści się w wierszu: klatki z kamery, raporty PDF, obrazy firmware, klipy audio, eksporty.
Oba mechanizmy zaprojektowano do wspólnego używania. Zapisanie pliku zwraca ci trwały adres URL, a zamierzony wzorzec to wpisanie tego adresu URL do kolumny tabeli za jednym zamachem — widget na panelu wyrenderuje wtedy obraz bez żadnej dodatkowej pracy:
Python
from datetime import datetime, timezone
info = await ironflock.files.put("part-1.jpg", jpeg_bytes, content_type="image/jpeg")
await ironflock.publish_to_table("inspections", {
"tsp": datetime.now(timezone.utc).isoformat(),
"part_id": "1",
"photo_url": info.url,
})Pełne API klienta — odczyt, wyświetlanie listy, wykorzystanie, linki do udostępniania, duże obiekty, kody błędów — jest opisane w dokumentacji SDK. Ta strona omawia stronę platformy: jak magazyn jest deklarowany, zarządzany, udostępniany i obsługiwany.
Jak to działa
- Opcjonalnie zadeklaruj sekcję
files:w.ironflock/data-template.yml. - Gdy użytkownik zainstaluje twoją aplikację w projekcie, IronFlock tworzy dla niej prywatny obszar przechowywania — dokładnie tak, jak tworzy bazę danych projektu.
- Twój kod brzegowy zapisuje i odczytuje obiekty przez API
filesz SDK. - Odinstalowanie aplikacji całkowicie usuwa jej magazyn: każdy obiekt i każde poświadczenie, tak samo jak usuwany jest schemat bazy danych.
Podobnie jak baza danych, magazyn jest per projekt. Ta sama aplikacja zainstalowana w dwóch projektach otrzymuje dwa całkowicie odrębne obszary przechowywania, a ty jako deweloper aplikacji nie masz dostępu do żadnego z nich — dane należą do użytkownika uruchamiającego twoją aplikację.
Zerowa konfiguracja jest poprawną konfiguracją: aplikacja bez sekcji files: i tak otrzymuje jedną przestrzeń nazw o nazwie default, więc files.put(...) działa od razu w każdej aplikacji. Ta niejawna default istnieje tylko wtedy, gdy szablon danych nie deklaruje własnych przestrzeni nazw: zadeklarowanie którejkolwiek ją zastępuje, więc wymień też przestrzeń o nazwie default (jak poniżej), jeśli wywołania bez przestrzeni nazw mają nadal działać — w przeciwnym razie kończą się błędem NO_SUCH_NAMESPACE.
Deklarowanie magazynu w szablonie danych
Sekcja files: znajduje się obok data: w data-template.yml:
files:
description: Camera frames and generated inspection reports.
# Budżet magazynu, jaki aplikacja SUGERUJE dla siebie, w bajtach (tutaj 5 GiB).
# Użytkownik projektu może go nadpisać; egzekwowane jest jego ustawienie.
quotaBytes: 5368709120
namespaces:
# Zachowaj default: deklarowanie przestrzeni nazw zastępuje niejawną.
- name: default
description: Everything without rules of its own.
- name: frames
description: Raw camera frames, one JPEG per inspected part.
contentTypes: ["image/jpeg"]
maxObjectBytes: 20971520
retention: { deleteAfter: 30 days }
- name: reports
description: Generated PDF inspection reports.
contentTypes: ["application/pdf"]
private: truePrzestrzenie nazw
Przestrzeń nazw to prefiks klucza, z którym powiązane są zasady. Nie jest to osobny bucket magazynu — każda przestrzeń nazw aplikacji znajduje się w jej jednym obszarze przechowywania, a nazwa przestrzeni nazw staje się po prostu pierwszym segmentem ścieżki przechowywania każdego obiektu.
To rozróżnienie mówi ci, kiedy zadeklarować przestrzeń nazw:
- Porządkujesz pliki? Używaj ścieżek kluczy —
2026/03/part-1.jpg— wewnątrz przestrzeni nazwdefault. Klucze w stylu folderów to normalny przypadek, aiter/iteratez SDK przechodzi przez folder (prefiks zakończony na/), łącznie z podfolderami. - Inne reguły dla zbioru obiektów? Zadeklaruj przestrzeń nazw. Reguły to jedyne, co dodaje przestrzeń nazw.
Reguły, jakie może nieść przestrzeń nazw:
| Pole | Znaczenie |
|---|---|
name | Małe litery, cyfry i myślniki, zaczynające się od litery. sys, system, ironflock oraz ironflock-* są zarezerwowane |
description | Wyświetlany użytkownikom i czytelny dla agentów AI |
private | Wyklucza przestrzeń nazw z dostępu między aplikacjami. Domyślnie false (udostępniona) — dokładnie tak jak tabele |
contentTypes | Dozwolone typy MIME, dozwolone globy (image/*). Domyślnie: dowolne |
maxObjectBytes | Największy pojedynczy obiekt, do 5 GiB. Domyślnie 100 MiB |
retention.deleteAfter | Obiekty starsze niż podana wartość są automatycznie usuwane (30 days, 2 weeks, 1 year, …) |
share | Czy obiekty można otwierać przez ich trwały adres URL: authenticated, link lub none. Domyślnie authenticated — zobacz Udostępnianie przez adres URL |
Budżet magazynu
quotaBytes deklaruje się raz dla całej aplikacji, a nie per przestrzeń nazw. Przestrzeń nazw to tylko prefiks klucza, więc budżet per prefiks nie miałby czego egzekwować — przydział dotyczy jednego obszaru przechowywania aplikacji, a sam magazyn obiektów egzekwuje go na każdej ścieżce zapisu, w tym przy bezpośrednich przesłaniach.
Istnieją dwie liczby, a różnica między nimi jest zamierzona:
- Sugerowany przydział — to, o co prosi twój szablon. Stosowany przy pierwszej instalacji aplikacji.
- Egzekwowany przydział — to, co ustawił użytkownik projektu. Gdy użytkownik zmieni budżet w ustawieniach przechowywania aplikacji, wygrywa jego wartość, a ponowne wdrożenie aplikacji jej nie resetuje.
Jeśli twój szablon nic nie mówi, obowiązuje wartość domyślna platformy (1 GiB dla backendów deweloperskich, 10 GiB dla produkcyjnych).
Retencja
Obiekty, które przekroczyły wiek deleteAfter swojej przestrzeni nazw, są usuwane automatycznie — plikowy odpowiednik tabelarycznej zasady dropAfter. Retencja działa wewnątrz magazynu obiektów tam, gdzie magazyn ją obsługuje, oraz jako codzienne zadanie platformy tam, gdzie jej nie obsługuje (urządzenia on-premises), więc zadeklarowana retencja zachowuje się wszędzie tak samo.
Udostępnianie przez adres URL
share określa, czy obiekty przestrzeni nazw można otwierać przez ich trwały adres URL:
share: authenticated(wartość domyślna) — adres URL może otworzyć każdy zalogowany użytkownik z dostępem READ do tego backendu danych. Polegają na tym panele,<img src>i pobieranie per plik w widoku danych: zachowaj wartość domyślną dla wszystkiego, co oglądają użytkownicy.share: link— ma dodatkowo umożliwiać wygasające linki, zlinkMaxTtljako ich maksymalnym czasem życia. Nie jest to jeszcze zaimplementowane: obecnielinkdziała dokładnie tak jakauthenticated, alinkMaxTtlnie ma żadnego efektu.share: none— adres URL odpowiada kodem 404, nawet użytkownikom z dostępem READ, także na urządzeniach on-premises i przez ich tunel chmurowy. Stosuj tę wartość do obiektów, które odczytuje wyłącznie kod twojej aplikacji, za pomocąget— pośrednich wyników przetwarzania, plików modeli lub kalibracji — tak aby żaden adres URL zapisany w tabeli lub skopiowany gdzie indziej ich nie otwierał. Widgety nie mogą wyświetlać tych obiektów, a widok danych pokazuje je na liście, ale nie potrafi ich pobrać.
share decyduje wyłącznie o trwałym adresie URL; nie ogranicza tego, kto może odczytywać obiekty. Odczyt za pomocą get, wygasające linki share_url, adresy URL do przesyłania, asystent AI i bezpośrednie poświadczenia S3 działają tak samo w każdej przestrzeni nazw, a o tym, co mogą odczytywać inne aplikacje, decyduje wyłącznie private (zobacz Udostępnianie plików między aplikacjami). Żadna wartość nie czyni adresu URL publicznym, a każda wartość inna niż te trzy nie przechodzi walidacji szablonu danych, więc z nią aplikacji nie da się ani zainstalować, ani zaktualizować.
SDK nie widzą tego ustawienia, ponieważ katalog go nie podaje: w przestrzeni nazw z share: none zarówno url, jak i pole url obiektu nadal zwracają adres URL, więc nie zapisuj go w tabeli.
Reguła należy do przestrzeni nazw i jest stosowana przy każdym żądaniu adresu URL; nie jest przechowywana razem z obiektami. Gdy projekt aktualizuje aplikację do wydania, które zmienia share, nowa wartość dotyczy więc także obiektów już zapisanych: adresy URL w kolumnach twoich tabel pozostają takie same i zaczynają albo przestają działać. Żaden obiekt nie jest przenoszony ani usuwany.
Trwałe adresy URL i panele
Każdy przechowywany obiekt ma stabilny adres URL w postaci https://files.ironflock.com/f/<backend>/<namespace>/<key>. Trzy właściwości sprawiają, że to właśnie jego warto wpisać do kolumny tabeli:
- Nigdy nie wygasa. Adres URL jest czystym adresem; pozostaje ważny przez cały czas życia obiektu.
- Nie jest linkiem publicznym. Każde żądanie przechodzi przez proxy uwierzytelniające, które sprawdza, czy żądający jest zalogowany i posiada dostęp READ do tego backendu danych; proxy buforuje każdą decyzję przez maksymalnie 5 minut. Cofnięcie dostępu użytkownikowi w ciągu tych 5 minut odbiera mu możliwość pobrania każdego pliku.
- Renderuje się w
<img>. Przeglądarka automatycznie wysyła swój plik cookie sesji, więc widget na panelu może użyć tego adresu URL w<img src>,<video src>lub w linku do pobrania, bez udziału JavaScript.
Przestrzeń nazw zadeklarowana z share: none jest z tego wyłączona: tam adres URL odpowiada kodem 404.
Aby przekazać plik komuś spoza projektu, share_url z SDK tworzy zamiast tego wygasający link na okaziciela — zobacz udostępnianie obiektów, aby dowiedzieć się, którego kiedy użyć. Adres URL do przesyłania (upload_url / uploadUrl) również jest poświadczeniem na okaziciela, i to takim, które pozwala zapisywać: typy zawartości i limit rozmiaru przestrzeni nazw oraz przydział są sprawdzane przy jego tworzeniu, a nie przy użyciu, więc każdy, kto go posiada, może zapisać pod jego kluczem dowolną zawartość aż do wygaśnięcia.
Duże pliki
Transfery do 6 MiB przechodzą jako pojedyncze wywołanie przez system komunikatów. Wszystko większe jest przesyłane bezpośrednio między urządzeniem a magazynem obiektów przez HTTPS — SDK przełącza się automatycznie, a pomocniki plikowe SDK dla Pythona i Go strumieniują z dysku i na dysk, więc wielogigabajtowy plik nigdy nie musi mieścić się w pamięci. Górny limit pojedynczego przesłania to 5 GiB.
Ścieżka bezpośrednia wymaga wdrożenia, które potrafi wstępnie podpisywać (presign) adresy URL magazynu, oraz urządzenia z dostępem do hosta magazynu obiektów (s3.ironflock.com), a nie tylko do routera komunikatów. Tam, gdzie brakuje któregoś z nich, SDK odczytują duży obiekt w zakresach bajtów przez router (FleetFiles v0.1.33 i nowsze). Zapisy nie mają takiej ścieżki zastępczej: tam, gdzie wdrożenie nie potrafi podpisywać adresów, większego obiektu w ogóle nie da się zapisać (TOO_LARGE), a jeśli fabryczne proxy przepuszcza wyłącznie ruch do routera, duże przesłania kończą się jawnym kodem PRESIGN_UNREACHABLE, a nie ogólnym błędem. Zegar urządzenia rozbieżny o więcej niż 15 minut kończy się kodem CLOCK_SKEW, który jest wezwaniem do sprawdzenia NTP, a nie poświadczeń.
Udostępnianie plików między aplikacjami
Dostęp do plików między aplikacjami korzysta z tej samej zgody co dostęp do tabel między aplikacjami. Jest jeden przełącznik: gdy użytkownik projektu przyzna aplikacji B dostęp do danych aplikacji A (zgoda data_access w ustawieniach aplikacji), to przyznanie obejmuje tabele A oraz nieprywatne przestrzenie nazw plików A. Cofnięcie go cofa oba.
To, co twoja aplikacja kontroluje jako dostawca, to flaga private per przestrzeń nazw:
private: false(wartość domyślna) — aplikacje posiadające przyznany dostęp do danych mogą ją odczytywać (nigdy do niej zapisywać).private: true— przestrzeń nazw jest niewidoczna dla innych aplikacji, kropka, nawet przy przyznanym dostępie.
Odzwierciedla to dokładnie korzystanie z danych innych aplikacji: przestrzenie nazw i tabele mają tę samą wartość domyślną. Bez zgody użytkownika projektu nic nie jest udostępniane — private: jedynie zawęża to, co widzi już uprawniony czytelnik, i nie jest samą zgodą.
Widok użytkownika
Użytkownicy projektu widzą i zarządzają magazynem twojej aplikacji w dwóch miejscach:
- Widok danych pokazuje wpis Pliki obok tabel i widoków każdej aplikacji — przeszukiwalną listę każdego przechowywanego obiektu wraz z rozmiarem, typem i datą modyfikacji oraz pobieraniem per plik.
- Ustawienia przechowywania aplikacji pokazują wykorzystanie (bajty i liczbę obiektów), egzekwowany budżet obok sugestii twojej aplikacji, element sterujący do zmiany budżetu oraz akcję Usuń wszystkie pliki — plikowy bliźniak opróżnienia wszystkich tabel. Usunięcie potwierdza się, wpisując nazwę aplikacji, i nie można go cofnąć.
- Asystent AI może wyświetlać, przeszukiwać i odczytywać te pliki w imieniu użytkownika. Obowiązuje ta sama kontrola
DATABACKEND/READ, więc nigdy nie pokaże pliku, którego użytkownik nie mógłby otworzyć samodzielnie. Przeszukuje ścieżki plików, a nie ich zawartość, i odczytuje pliki tekstowe, obrazy oraz pliki PDF — archiwów i innych formatów binarnych odczytać nie potrafi.
Podobnie jak w przypadku tabel, są to dane użytkownika: może je przeglądać, ograniczać i usuwać bez twojego udziału.
Bezpośredni dostęp S3
Dla wszystkiego poza SDK — analityka z DuckDB, nocnej kopii zapasowej rclone, potoku BI — użytkownik projektu może wydać poświadczenia S3 tylko do odczytu per aplikacja z ustawień przechowywania aplikacji.
Każde poświadczenie jest ograniczone do obszaru przechowywania tej jednej aplikacji: poświadczenie wydane dla aplikacji A nie działa na pliki aplikacji B — strukturalnie, ponieważ zasady przechowywania B po prostu nigdy go nie wskazują. Projekt może posiadać kilka poświadczeń per aplikacja (jedno na konsumenta: zadanie CI, skrypt kopii zapasowej, laptop), a cofnięcie jednego pozostawia pozostałe działające. Na tym też polega rotacja: jeśli klucz wycieknie, wydaj drugie poświadczenie, przenieś na nie konsumenta, cofnij pierwsze — żaden inny konsument nie zostaje naruszony.
Sekret jest wyświetlany jednokrotnie, przy tworzeniu. Wydanie wymaga dostępu do odczytu backendu danych tej aplikacji — tego samego uprawnienia, które w ogóle pozwala odczytywać te pliki, więc poświadczenie nigdy nie poszerza niczyjego zasięgu.
# rclone
rclone config create myapp s3 provider=Other \
endpoint=https://s3.ironflock.com \
access_key_id=ifs-key-3317-x7k2m secret_access_key=<shown once>
rclone ls myapp:if-1042-3317# DuckDB
CREATE SECRET (TYPE S3, KEY_ID 'ifs-key-3317-x7k2m', SECRET '<shown once>',
ENDPOINT 's3.ironflock.com');
SELECT * FROM read_parquet('s3://if-1042-3317/exports/*.parquet');Ustawienia przechowywania pokazują punkt końcowy oraz dokładną nazwę bucketa, na który skierować klienta. Zwróć uwagę, że wylistowanie bucketów najwyższego poziomu (aws s3 ls bez argumentu) celowo nic nie zwraca — poświadczenie nie posiada żadnych bucketów; ma przyznany dostęp do jednego. Adresuj bucket bezpośrednio, jak powyżej.
Urządzenia on-premises
Przechowywanie plików działa identycznie na urządzeniu on-premises, z czterema różnicami wynikającymi z konstrukcji urządzenia:
- Pliki są serwowane w tym samym origin pod własnym adresem urządzenia (
/files/...) — bez dodatkowej nazwy DNS, bez dodatkowego certyfikatu, i działa to w instalacjach na zwykłym HTTP. Trwałe adresy URL oraz<img src>zachowują się dokładnie tak jak w chmurze. - Backend przechowywania urządzenia strumieniuje pobrania plików przez platformę, zamiast przekierowywać do osobnego hosta magazynu, więc usługa przechowywania nigdy nie jest wystawiana jako drugi origin.
- Domyślnie brak transferów bezpośrednich. Urządzenie nie wydaje wstępnie podpisanych adresów URL magazynu, chyba że zostanie tak skonfigurowane, więc SDK odczytują obiekty większe niż limit inline (6 MiB) w zakresach bajtów przez platformę i nie mogą zapisywać obiektów większych niż ten limit — zapis kończy się błędem
TOO_LARGE. - Bezpośrednie poświadczenia S3 nie są dostępne na urządzeniach — wbudowany magazyn obiektów nie potrafi wyrazić przyznań dostępu per poświadczenie. Sekcja ta po prostu nie pojawia się tam w ustawieniach przechowywania. Wszystko pozostałe, w tym zadeklarowana retencja, działa tak samo.
Urządzenie odcięte od sieci (air-gapped) obsługuje cały ruch plików lokalnie: logo aplikacji, obrazy paneli oraz pobrania plików nie wymagają łączności z internetem.
Limity w skrócie
| Limit | Wartość | Skąd pochodzi |
|---|---|---|
| Transfer inline (pojedyncze wywołanie) | 6 MiB | Zgłaszany w czasie działania; może zostać podniesiony po stronie serwera |
| Pojedynczy obiekt | 100 MiB domyślnie, 5 GiB maks. | maxObjectBytes per przestrzeń nazw |
| Pojedyncze przesłanie | 5 GiB | Górny limit pojedynczego PUT w magazynie obiektów; przesyłanie wieloczęściowe nie jest jeszcze dostępne. Tam, gdzie wdrożenie nie potrafi podpisywać adresów (domyślnie urządzenie on-premises), limit inline |
| Budżet magazynu | domyślnie 1 GiB dev / 10 GiB prod | Sugerowany przez szablon, decydowany przez użytkownika |
| Długość i znaki klucza | UTF-8, ścieżki rozdzielane /; najwyżej 900 bajtów, 20 segmentów i 255 bajtów na segment | Bez / na początku ani na końcu, bez pustych segmentów ani segmentów . czy .., bez znaków sterujących i formatujących, bez znaków takich jak \, :, * czy ? oraz bez zarezerwowanego prefiksu _sys/ — odrzucane z INVALID_KEY lub, przez FleetFiles v0.2, z INTERNAL (zobacz błędy przechowywania plików) |