NovinkaZverejnili sme 50+ Laravel balíkov ako open source
Custom AI apps, agents and automation — Roundly ConsultingRoundly
Všetky balíky
Media Library for Laravel

Deduplikácia a integrita

Pri zapnutej deduplikácii sa rovnaké bajty na rovnakom (disk, visibility) uložia raz a zdieľajú sa referenciou, pokiaľ je zdieľaný súbor na disku stále prítomný — rovnako zdieľajú originál aj attach() a kópia na tom istom disku. Bezpečné zdieľanie zaručuje počítadlo referencií na uloženom súbore: fyzický originál sa odstráni — mazaním, presunom na iný disk či náhradou — len vtedy, keď na ten istý súbor (disk + cesta) už neukazuje žiadny iný riadok, vrátane soft-deleted riadkov, takže soft delete s následným obnovením nič nestratí ani pri zdieľanom súbore:

// Identical bytes on the same (disk, visibility) are stored once.
$a = MediaLibrary::for($userA)->add($path)->toBucket('docs');   // the source path is only read
$b = MediaLibrary::for($userB)->add($path)->toBucket('docs'); // same bytes -> shared storage

// Deleting $a keeps the file alive while $b still references it.
MediaLibrary::delete($a); // the original stays until the last reference is gone

$b->verifyIntegrity();    // bool — false on drift or a missing file

Kontrolný súčet obsahu (predvolene sha256) je základom dedup aj voliteľnej kontroly integrity pri čítaní — nastavte verify_checksum_on_read, aby sa originál pri streame/sťahovaní prehašoval a pri nezhode vyhodil ChecksumMismatch. Každý súbor sa overí algoritmom, ktorým sa vypočítal jeho checksum, takže po zmene media.checksum_algorithm sa staršie médiá nikdy nenahlásia ako zmenené. Varianty sa nikdy nezdieľajú: každé médium si generuje vlastné. Zdieľaný originál sa ani neprepíše — náhrada pri jednom z riadkov zapíše nové bajty na vlastnú cestu — a adresáre sa upracú až vtedy, keď v nich nezostane nič, na čo ukazuje iný riadok.

Rozsah dedup — kto smie súbor zdieľať

Pri predvolenom rozsahu global sa rovnaké bajty na jednom disku s jednou viditeľnosťou uložia raz pre všetkých vlastníkov. Je to najúspornejšie rozloženie, no pre aplikáciu s viacerými tenantmi nesprávne: upload loga tenanta B, ktoré tenant A nahral skôr, sa stane riadkom ukazujúcim na súbor tenanta A — a ak generátor ciest dáva tenanta na začiatok, aj pod cestou tenanta A. V rámci balíka je to bezpečné, no čokoľvek, čo maže súbory či prefix cesty tenanta mimo balíka — skript pri odchode zákazníka, lifecycle pravidlo bucketu —, odstráni súbor, ktorý iný tenant stále používa, a za súbor tenanta B platí vo využití úložiska tenant A. Zúžte ho cez deduplicate_scope:

// config/media.php
'deduplicate' => true,
'deduplicate_scope' => 'owner',   // or 'path_prefix', or your own DeduplicationScope class
deduplicate_scopeOriginál zdieľa len s
global (default)Akýkoľvek riadok — správanie všetkých skorších verzií.
ownerMédiá toho istého vlastniaceho modelu (v ľubovoľnej skupine). Globálne médiá zdieľajú len s globálnymi; draft nezdieľa nikdy — kým nie je naviazaný, nemá vlastníka.
path_prefixSúbor pod rovnakým prefixom adresára, aký generátor ciest dá novému médiu, pred jeho segmentom uuid (tenants/42/ pre tenants/42/{uuid}/). Určené pre generátor ciest, ktorý dáva tenanta na začiatok; pri predvolenom rozložení {uuid}/ zdieľa ako global.
a class nameVlastný DeduplicationScope, vytvorený z kontajnera.

Alebo uveďte vlastný DeduplicationScope, ktorý sa vytvorí z kontajnera:

use Illuminate\Database\Eloquent\Builder;
use RoundlyConsulting\MediaLibrary\Contracts\DeduplicationScope;
use RoundlyConsulting\MediaLibrary\Models\Media;

// config/media.php: 'deduplicate_scope' => App\Media\TenantDeduplicationScope::class,
final class TenantDeduplicationScope implements DeduplicationScope
{
    /** Narrow the rows with identical bytes; null = this media stores its own copy. */
    public function constrain(Builder $candidates, Media $media): ?Builder
    {
        $tenant = $media->custom_properties['tenant_id'] ?? null;

        return $tenant === null ? null : $candidates->where('custom_properties->tenant_id', $tenant);
    }
}
  • $candidates sú nezmazané riadky na tom istom disku s rovnakou viditeľnosťou a checksumom; $media je ukladaný riadok (pri pridaní neuložený) s nastaveným vlastníkom, skupinou, tokenom draftu a vlastnými vlastnosťami. Rozsah platí pre pridania a náhrady — attach() a kópia na tom istom disku zdieľajú súbor, ktorý im odovzdáte, zámerne a bez ohľadu na rozsah.
  • Opätovná kontrola pod zámkom: pri každom rozsahu okrem global sa rozsah na vybranom riadku vyhodnotí znova, kým je uzamknutý, takže riadok, ktorý sa medzitým presunul k inému vlastníkovi či na inú cestu, sa nezdieľa — nové médium si uloží vlastnú kópiu.
  • Súbežné uploady rovnakých bajtov v jednom rozsahu sa pred potvrdením navzájom nevidia: každý si uloží vlastnú kópiu. Nič sa nestratí a nič sa nezdieľa naprieč rozsahmi.
  • Zmena rozsahu ovplyvní len nové pridania a náhrady; riadky, ktoré už súbor zdieľajú, ho zdieľajú ďalej.
  • php artisan about zobrazí rozsah vedľa dedup. Ďalšia hlavná verzia zmení predvolenú hodnotu na owner — ak chcete po prechode zachovať doterajšie správanie, nastavte výslovne 'deduplicate_scope' => 'global'.

Meranie uložených bajtov

Každý riadok pozná vlastné bajty — veľkosť originálu plus veľkosť, ktorú si každý variant zaznamenal pri zápise ($media->storedSize()). Súčet cez riadky však po zdieľaní originálov ráta priveľa, preto MediaLibrary::usage() počíta to, čo disky naozaj obsahujú:

use RoundlyConsulting\MediaLibrary\Facades\MediaLibrary;
use RoundlyConsulting\MediaLibrary\Models\Media;

$usage = MediaLibrary::usage();                     // every row, soft-deleted included

$usage->originalBytes;            // each stored original once, per distinct (disk, path)
$usage->variantBytes;             // every recorded variant size (variants are per-row files)
$usage->totalBytes();             // originalBytes + variantBytes — what the disks hold
$usage->referencedOriginalBytes;  // originals counted once per row (the sum of size)
$usage->savedBytes();             // referencedOriginalBytes - originalBytes — what sharing saves

// Scope it with any media query: an owner, a bucket, a column of your own.
MediaLibrary::usage(Media::query()->forModel($team)->withTrashed());
MediaLibrary::usage(Media::query()->inBucket('invoices'));
  • Identitou je (disk, path), tá istá, z ktorej vychádza počítadlo referencií zdieľaných originálov, takže každý uložený súbor sa započíta raz.
  • Podľa query: súbor, ktorý zdieľajú dva rozsahy, sa započíta raz vo využití každého z nich a raz v celkovom súčte. S rozsahom dedup, ktorý zodpovedá spôsobu merania (owner a Media::query()->forModel($team)), sa žiadny súbor nezapočíta dvom vlastníkom, takže súčet za vlastníkov sedí s celkom.
  • Soft-deleted riadky si súbory ponechávajú až do force delete, preto ich predvolená query zahŕňa; ak ich chcete vynechať, odovzdajte query bez withTrashed().
  • Variant zaznamenaný skôr, než sa veľkosti zaznamenávali, sa ráta ako 0, kým ho nezmeria media:backfill-variant-sizes. MeasureStorageUsageAction je to isté meranie pre dependency injection; pod MediaLibrary::fake() číta uložené riadky ďalej.

Súbežné zápisy a rollbacky

Riadok, ktorý začne zdieľať súbor, je pre ostatné spojenia neviditeľný, kým sa nepotvrdí, takže samotné počítadlo referencií by ho mohlo prehliadnuť. Balík túto medzeru uzatvára zámkami riadkov a mazaním až po potvrdení transakcie:

  • Každý spôsob, ako riadok začne zdieľať originál — deduplikované pridanie či náhrada, attach() aj kópia na tom istom disku —, ho uloží pod zámkom (SELECT … FOR UPDATE, vo vlastnej transakcii — vo vašej ako savepoint) na riadku, ktorý súbor už ukladá. Súbežné zmazanie či presun tohto riadku počká, kým sa nový riadok potvrdí, a potom ho uvidí. Stačí uzamknúť jeden riadok: kým súbor stále ukladá, kontrola žiadneho iného riadku ho neodstráni.
  • Ak zdieľaný riadok po získaní zámku už neexistuje — alebo ukladá iné bajty —, deduplikované pridanie či náhrada si uloží vlastnú kópiu, zatiaľ čo attach() a copy() vyhodia ModelNotFoundException.
  • Náhrada uzamkne vlastný riadok skôr, než sa rozhodne, či originál prepíše na mieste, takže nikdy neprepíše bajty, ktoré sa chystá zdieľať nový riadok.
  • Force delete odstráni súbory až po potvrdení vonkajšej transakcie (bez transakcie hneď): rollback ich ponechá a počítadlo referencií potom vidí aj riadok, ktorý medzitým začal originál zdieľať v inom spojení. Rovnako uvoľňujú staré súbory aj presun, presun variantov, naviazanie draftu a náhrada.
  • Na skutočnom databázovom engine môžu dva takéto toky skončiť v deadlocku — každý drží riadok, ktorý chce ten druhý. Databáza jeden z nich preruší chybou deadlocku a volajúci ho môže zopakovať.

Prejavte lásku k open source

Tento balík je zadarmo pod licenciou MIT. Ak vám šetrí čas, jednorazový príspevok alebo členstvo na Patreone nám pomôže ho ďalej udržiavať, testovať a dokumentovať.

Ďalšie spôsoby podpory vrátane kryptomien

Odoslaním daru súhlasíte s našimi podmienkami prijímania darov.

Chcete to zabudovať do svojho produktu?

Naše balíky integrujeme do zákazkových Laravel a AI riešení. Napíšte nám, na čom pracujete, a ozveme sa do 48 hodín.