Obsah článku / Contents
Prečo je lepšie ukladať obrázky, videá a texty v MinIO ako v PostgreSQL

Prečo je lepšie ukladať obrázky, videá a texty v MinIO ako v PostgreSQL

"Kde mám ukladať súbory? V databáze alebo MinIO?"

Riešil som tento problém už stokrát. Tu je odpoveď.


Prológ: Problém

Moja cesta storage riešeniami bola... poučná.

Projekt 1 (2018): PostgreSQL BYTEA

Manager: "Potrebujeme storage pre screenshoty!"

Ja: "Jednoduché, dám to do DB..."

O mesiac neskôr:

DBA: "Prečo mi databáza narastá o 50GB denne?!"

Ja: "Screenshoty... Chyba."

Projekt 2 (2019): Filesystem

Manager: "Potrebujeme storage!"

Ja: "Filesystem? Jednoduché..."

O 2 mesiace neskôr:

DevOps: "Server má plný disk!"

Ja: "Backup?"

DevOps: "Backup súborov? Nie je automatický..."

Projekt 3 (2020): ownCloud

Manager: "Potrebujeme enterprise storage!"

Ja: "OK, ownCloud..."

3 týždne setup, komplexná konfigurácia. Výsledok: funguje, ale overkill pre automatické uploady.

Projekt 4 (2021): AWS S3 (SAAS)

Manager: "Potrebujeme cloud storage!"

Ja: "AWS S3?"

2 týždne čakania na AWS schválenie.

CFO: "Koľko to stojí?!"

Ja: "Ehm... €500/mesiac..."

Skvelé riešenie, ale náklady sa sčítavajú.

Projekt 5 (2024): MinIO (On-Premise)

Manager: "Potrebujeme storage! On-premise!"

Ja: "MinIO! 5 minút setup, S3-kompatibilný, nulové náklady"

Manager: "Perfektné!"

Dokonalá rovnováha: S3 API + on-premise kontrola!


Reálny scenár

Test automation generuje denne:

  • 100 testov/deň
  • 5 screenshotov/test = 500 obrázkov/deň (každý 200KB)
  • 20 videí/deň (každé 10MB)
  • 1000 log súborov/deň (každý 50KB)
Typ súboruDenneMesačneRočne
Obrázky100MB3GB36GB
Videá200MB6GB72GB
Logy50MB1.5GB18GB
Spolu350MB10GB120GB

Otázka: Kde to ukladať?


Kapitola 1: Tradičné riešenia (a prečo zlyhávajú)

Riešenie 1: PostgreSQL BYTEA

AspektHodnotenie
Zložitosť setupuNÍZKA (len ALTER TABLE)
Čas setupu10 minút
ÚdržbaSTREDNÁ

Výhody:

  • Jednoduché (žiadna extra služba)
  • ACID transakcie
  • Backup zahrnutý (s DB)
  • Žiadna separátna autentifikácia

Nevýhody:

  • Explózia veľkosti databázy
  • Pomalé query (načítavanie blobov)
  • Nočná mora s backupmi (obrovské dumpy)
  • Problémy s pamäťou
  • Neoptimalizované pre veľké súbory

Verdikt: "Zdá sa jednoduché, stáva sa nočnou morou"

Pokušenie je pochopiteľné:

sql
CREATE TABLE test_screenshots (
    id SERIAL PRIMARY KEY,
    test_id INTEGER,
    filename VARCHAR(255),
    content BYTEA,  -- Ulož obrázok sem! Jednoduché!
    content_type VARCHAR(100),
    created_at TIMESTAMP DEFAULT NOW()
);

-- Upload screenshotu:
INSERT INTO test_screenshots (test_id, filename, content, content_type)
VALUES (123, 'login.png', '\xDEADBEEF...', 'image/png');

-- Jednoduché! Čo sa môže pokaziť?

Riešenie 2: Filesystem

AspektHodnotenie
Zložitosť setupuNÍZKA (mkdir /uploads)
Čas setupu5 minút
ÚdržbaVYSOKÁ (manuálne backupy)

Výhody:

  • Rýchle (priamy prístup k disku)
  • Jednoduché (žiadne závislosti)
  • Nulové extra náklady

Nevýhody:

  • Nie je škálovateľné (jeden server)
  • Žiadny backup (ak nie manuálny)
  • Žiadna redundancia
  • Ťažké zdieľanie medzi servermi
  • Peklo s cestami
  • Problémy s oprávneniami

Verdikt: "OK pre vývoj, ZLÉ pre produkciu"

Reálne problémy:

bash
# Problém 1: Peklo s cestami
/var/uploads/test-123/screenshot.png           # Server 1
/home/jenkins/uploads/test-123/screenshot.png  # Server 2
C:\uploads\test-123\screenshot.png             # Windows

# Problém 2: Koľko súborov máme?
find /uploads -type f | wc -l
# Výstup: 1,245,678 súborov

# Ako backupujem 1M+ súborov?
rsync -avz /uploads backup:/uploads  # 6 hodín!

# Problém 3: Plný disk
df -h /uploads
# Výstup: 100% využité

# Problém 4: Viac serverov za load balancerom
# Server 1 má súbory, Server 2 nemá = náhodné 404

Riešenie 3: ownCloud / Nextcloud

AspektHodnotenie
Zložitosť setupuVYSOKÁ (docker-compose, redis, mysql, cron)
Čas setupu2-3 dni
ÚdržbaVYSOKÁ (aktualizácie, backupy, monitoring)

Výhody:

  • Webové UI (dobré pre manuálne uploady)
  • Správa používateľov
  • Zdieľanie súborov a verziovanie
  • Desktop/mobile synchronizácia

Nevýhody:

  • Overkill pre automatické uploady
  • Komplexná konfigurácia
  • Vysoká spotreba zdrojov
  • Pomalé API pre hromadné operácie

Verdikt: "Výborné pre tímové zdieľanie, zlý nástroj pre automatizáciu"

Riešenie 4: AWS S3 (SAAS)

AspektHodnotenie
Zložitosť setupuSTREDNÁ (AWS účet, IAM, politiky)
Čas setupu2-3 dni (schvaľovací proces)
ÚdržbaNÍZKA (spravovaná služba)
NákladyVYSOKÉ (platba za GB + requesty)

Výhody:

  • Nekonečne škálovateľné
  • Vysoká dostupnosť (99.99%)
  • Vstavaný backup a CDN
  • S3 API štandard
  • Nulová údržba

Nevýhody:

  • Náklady (hlavne pre dev/test)
  • Zložitosť IAM
  • Schvaľovací proces
  • Problémy so suverenitou dát
  • Nedá sa spustiť lokálne

Verdikt: "Perfektné pre produkciu, ale závisí od SAAS vs On-Premise stratégie"

Reálne náklady pre testovacie prostredie:

PoložkaVyužitieNáklady
Storage50GB$1.15
GET requesty1M$0.40
PUT requesty100K$0.50
Prenos dát50GB$4.50
Mesačne~$7
× 5 prostredí$35/mesiac
Ročne$420

Za testovacie dáta!


Kapitola 2: PostgreSQL BYTEA (a prečo NIE)

Problém 1: Explózia veľkosti databázy

sql
-- Pred (bez screenshotov):
SELECT pg_size_pretty(pg_database_size('testdb'));
-- Výstup: 2 GB

-- Po 1 mesiaci (so screenshotmi):
SELECT pg_size_pretty(pg_database_size('testdb'));
-- Výstup: 52 GB

1000 testov × 30MB priemerne = 30GB len za screenshoty!

Problém 2: Pomalé query

sql
-- Query PRED screenshotmi:
SELECT * FROM test_results WHERE test_id = 123;
-- Čas vykonania: 5ms

-- Query PO (s JOIN načítavajúcim BLOBy):
SELECT tr.*, ts.content  -- Načítava 5MB blob!
FROM test_results tr
LEFT JOIN test_screenshots ts ON ts.test_id = tr.test_id;
-- Čas vykonania: 2500ms

PostgreSQL načíta CELÝ blob do pamäte. 10 screenshotov × 500KB každý = 5MB načítaných!

Problém 3: Nočná mora s backupmi

ScenárPredPo
Čas pg_dump2 minúty2 hodiny
Veľkosť dumpu100MB55GB
Čas obnovy5 minút4 hodiny
Týždenný backup storage700MB385GB

Problém 4: Problémy s pamäťou a pripojeniami

java
@Test
public void testLogin() {
    TestResult result = testRepository.findById(123);

    // Toto načíta VŠETKY screenshoty do pamäte!
    List<Screenshot> screenshots = result.getScreenshots();

    // Každý screenshot = 500KB
    // 10 screenshotov = 5MB
    // 100 súbežných testov = 500MB RAM!
}
// OOM Exception prichádza!

Vyčerpanie connection poolu:

  • Query načíta 5MB blob = drží pripojenie 5+ sekúnd
  • Pool s 20 pripojeniami = vyčerpaný za sekundy
  • Ostatné query timeoutujú

Čísla

MetrikaDopad
Veľkosť databázy+60GB (30× nárast)
Čas backupu2 min → 2 hodiny (60× pomalšie)
Čas query5ms → 2500ms (500× pomalšie)
Čas VACUUM1 min → 2 hodiny (120× pomalšie)
Pamäť na operáciu+500MB

Verdikt: "Databáza je pre DÁTA, nie SÚBORY!"


Kapitola 3: MinIO riešenie (a prečo ÁNO)

Správna architektúra

MinIO pre súbory:

  • Setup: 5 minút (docker run)
  • Náklady: $0 (self-hosted, on-premise)
  • Škálovateľnosť: Nekonečná (cluster)
  • Výkon: Optimalizovaný pre veľké súbory

PostgreSQL pre metadáta:

  • screenshot_id, test_id, filename
  • minio_path: s3://bucket/path
  • size, created_at
┌──────────────────┐
│   PostgreSQL     │  ← Len metadáta (maličké!)
│                  │
│  id: 123         │
│  path: "s3://test-screenshots/123.png"
│  size: 245KB     │
└────────┬─────────┘
         │ Referencia (len cesta)
         ↓
┌──────────────────┐
│     MinIO        │  ← Skutočné súbory (veľké!)
│  (On-Premise)    │
│                  │
│  /test-screenshots/
│    ├─ 123.png    │
│    ├─ 124.png    │
│    └─ 125.mp4    │
└──────────────────┘

Implementácia

java
@Entity
@Table(name = "test_screenshots")
public class TestScreenshot {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private Long testId;
    private String filename;
    private String minioPath;  // s3://bucket/path - NIE obsah!
    private Long fileSize;
    private String contentType;
    private LocalDateTime createdAt;

    // ŽIADNE BYTEA pole!
}

@Service
public class ScreenshotService {

    @Autowired
    private MinioClient minioClient;

    public TestScreenshot saveScreenshot(Long testId, MultipartFile file) {
        // 1. Upload do MinIO (file storage)
        String filename = generateFilename(testId, file.getOriginalFilename());
        String minioPath = uploadToMinio(file, filename);

        // 2. Ulož metadáta do PostgreSQL (maličký záznam!)
        TestScreenshot screenshot = new TestScreenshot();
        screenshot.setTestId(testId);
        screenshot.setMinioPath(minioPath);  // Len string cesty!
        screenshot.setFileSize(file.getSize());

        return repository.save(screenshot);  // Rýchle! Len metadáta!
    }
}

Rozdiel

sql
-- Veľkosť tabuľky PRED (s BYTEA):
SELECT pg_size_pretty(pg_total_relation_size('test_screenshots'));
-- Výstup: 48 GB

-- Veľkosť tabuľky PO (len metadáta):
SELECT pg_size_pretty(pg_total_relation_size('test_screenshots'));
-- Výstup: 245 MB

-- To je 196× menšie!
MetrikaPred (BYTEA)Po (MinIO)Zlepšenie
Veľkosť tabuľky48 GB245 MB196× menšie
Čas query2500ms5ms500× rýchlejšie

Kapitola 4: Use Case - Test Automation

Definícia problému

Denná generáciaMnožstvo
Testy500
Screenshoty500 × 7 × 300KB = 1.05GB
Videá500 × 20MB = 10GB
Logy500 × 4 × 75KB = 150MB
Spolu/deň~11.2GB
Mesačne~336GB
Ročne~4TB

Storage potrebný: ÁNO! Databázový storage: NIE!

Postup riešenia

1

Test začína

→ Vytvor TestRun záznam v PostgreSQL (len metadáta)
2

Test beží:

  • Vytvor screenshoty → Upload do MinIO → Ulož referenciu v PostgreSQL
  • Nahraj video → Upload do MinIO → Ulož referenciu v PostgreSQL
  • Generuj logy → Upload do MinIO → Ulož referenciu v PostgreSQL
3

Test končí

→ Aktualizuj status TestRun

Výsledky query:

  • Rýchle! (len PostgreSQL metadáta)
  • Načítaj súbory on-demand (MinIO streaming cez presigned URL)

Kapitola 5: Porovnanie výkonu

Benchmark: 1000 screenshotov (300MB celkom)

Výkon uploadu:

MetrikaPostgreSQL BYTEAMinIO
Čas45 sekúnd8 sekúnd
Pamäť (špička)800MB100MB
Nárast veľkosti DB+300MB+150KB (metadáta)

Výkon query (100 screenshotov):

MetrikaPostgreSQL BYTEAMinIO
Čas query8.5 sekúnd12ms + 1.2s (paralelné sťahovanie)
Prenesené dáta30MB15KB (len cesty)
Pamäť350MB2MB

Výkon backupu:

MetrikaPostgreSQL BYTEAMinIO + PostgreSQL
Čas backupu2h 15min15 minút
Veľkosť dumpu52GB250MB + 50GB súborov

To je 9× rýchlejšie!

Porovnanie nákladov za 1 rok

MetrikaPostgreSQL BYTEAMinIO + PostgreSQL
Veľkosť databázy4.2 TB2 GB
File storageN/A4 TB
Backup storage29.4 TB4 TB
Čas backupu/deň8 hodín20 minút
Čas query2-5 sekúnd5-10ms
Pamäť/query500MB+2-5MB
Náklady na hardware$40,000$3,000

Úspora: $37,000!


Kapitola 6: Najlepšie praktiky

ČO ROBIŤ

java
// 1. Ukladaj metadáta do DB, súbory do MinIO
@Entity
public class Document {
    private String minioPath;  // Referencia na cestu
    private Long fileSize;
    private String contentType;
    // ŽIADNY byte[] content!
}

// 2. Použi presigned URL pre dočasný prístup
String url = minioService.getPresignedUrl(path, 3600);
response.redirect(url);  // Klient sťahuje priamo

// 3. Streamuj veľké súbory (nenačítavaj všetko do pamäte)
InputStream stream = minioService.getObject(path);
// Streamuj do odpovede...

// 4. Organizuj súbory do bucketov/priečinkov
String path = String.format("test-screenshots/%d/%s/%s",
    testId, dateFolder, filename);

// 5. Nastav lifecycle politiky (automatické mazanie starých súborov)
// mc ilm add --expiry-days 90 local/test-screenshots/temp

ČO NEROBIŤ

java
// 1. Neukladaj súbory do databázy
@Column(columnDefinition = "BYTEA")
private byte[] fileContent;  // NIE!

// 2. Nenačítavaj všetky súbory do pamäte
List<byte[]> allFiles = new ArrayList<>();
for (Screenshot s : screenshots) {
    allFiles.add(s.getContent());  // OOM!
}

// 3. Nepoužívaj filesystem na viacerých serveroch
String path = "/var/uploads/" + filename;  // Neškáluje sa!

// 4. Neprehltávaj chyby uploadu
try {
    minioService.upload(file);
} catch (Exception e) {
    // Súbor stratený navždy!
}

Kapitola 7: Migračná stratégia

Krok za krokom migrácia z PostgreSQL BYTEA

sql
-- 1. Pridaj nové stĺpce
ALTER TABLE test_screenshots ADD COLUMN minio_path VARCHAR(500);
ALTER TABLE test_screenshots ADD COLUMN migrated BOOLEAN DEFAULT false;
java
// 2. Migračná služba (dávkové spracovanie)
@Service
public class BlobMigrationService {

    public void migrateScreenshots() {
        List<Screenshot> batch = repository.findByMigratedFalse(100);

        for (Screenshot screenshot : batch) {
            // Upload do MinIO
            String minioPath = minioService.uploadBytes(
                screenshot.getContent(),
                String.format("test-screenshots/%d/%s",
                    screenshot.getTestId(),
                    screenshot.getFilename())
            );

            // Aktualizuj záznam
            screenshot.setMinioPath(minioPath);
            screenshot.setContent(null);  // Vymaž BYTEA
            screenshot.setMigrated(true);
            repository.save(screenshot);
        }
    }
}
sql
-- 3. Po dokončení migrácie
ALTER TABLE test_screenshots DROP COLUMN content;
VACUUM FULL test_screenshots;  -- Uvoľni miesto!

Záver

Rozhodovacia matica

Ukladaj do PostgreSQLUkladaj do MinIO
Malé dáta (<1KB)Veľké súbory (>100KB)
Často dotazovanéBinárne dáta
Potrebuje transakcieMediálne súbory
Štruktúrované dátaLogy, backupy

SAAS vs On-Premise

Vyber AWS S3 (SAAS) akVyber MinIO (On-Premise) ak
Cloud-first stratégiaPožadovaná suverenita dát
Potrebná globálna distribúciaNižšie dlhodobé náklady
Nechceš spravovať infraštruktúruPlná kontrola nad infraštruktúrou
Rozpočet na opakované nákladyUž máš hardware

Hybridný prístup: MinIO pre dev/test, AWS S3 pre produkciu. Rovnaké API = rovnaký kód!

Kľúčové poznatky

Databáza = Metadáta (rýchle query, malá veľkosť)
├─ id: 123
├─ filename: "screenshot.png"
├─ path: "s3://bucket/123.png"  ← Len referencia!
└─ size: 245KB

MinIO = Súbory (optimalizovaný storage, streaming)
└─ s3://bucket/123.png (245KB)  ← Skutočný súbor

Reálne výsledky

MetrikaPred (BYTEA)Po (MinIO)
Databáza52GB2GB (26× menšia)
Čas backupu2 hodiny15 min (8× rýchlejšie)
Čas query2.5 sekundy5ms (500× rýchlejšie)
Pamäť/operácia500MB2MB (250× menej)
Náklady na hardware$40,000$3,000 (92% lacnejšie)

ROI: Neoceniteľné!


Tento článok je výsledkom rokov skúseností s rôznymi storage riešeniami. Každý prístup mal svoje miesto v čase, každý naučil niečo nové. MinIO je aktuálne najlepšie on-premise riešenie pre object storage, zatiaľ čo AWS S3 zostáva top voľbou pre SAAS stratégiu.

P.S.: Databáza je pre DÁTA, MinIO/S3 je pre SÚBORY. Zapamätaj si to!

P.P.S.: S3-kompatibilné API znamená flexibilitu - vyvíjaj na MinIO, deployuj na AWS, alebo naopak!

Všetky články