2 gigatavun skannattu arkisto asuu S3-bucketissa ja käyttäjä haluaa sivun 900. PDFlibPas pystyy palvelemaan kyseisen sivun lataamatta koko tiedostoa: LoadFromRangeSource rakentaa omien tavualuekutsujesi päälle vain luettavan virran, jonka kohtaa voi vapaasti siirtää, ja välittää sen TPDFDocument-dokumentille, joten jäsennin hakee ristiviitetaulukot, yhden sivupuun haaran ja yhden sisältövirran
Siirtokerros on tässä vanha ja tylsä osa. HTTP-palvelimet ovat ilmoittaneet tavualueista vuosikymmeniä, nykyään RFC 9110 §14:ssä, ja jokainen objektivarasto puhuu samaa murretta. PDF:n puoli on yhtä lailla selvillä: ISO 32000-1 §7.5.8 määrittelee linearisoinnin nimenomaan siksi, että lukija voi piirtää ensimmäisen sivun tiedoston alusta. Delphistä on puuttunut keskellä oleva pala, osa, joka päättää, mitä alueita kysytään, kuinka monta niistä pidetään ja miten kaksinkertainen kysyminen vältetään
Mitä LoadFromRangeSource tarvitsee siirtokerrokseltasi?
Kaksi asiaa, eikä kumpikaan ole tietovirta. PDFlibPas kysyy auktoritatiivisen SourceSizein ja tyypin TPDFlibRangeReadEvent mukaisen synkronisen lukukutsun, joka on esitelty muodossa function(Sender: TObject; Offset: Int64; Buffer: Pointer; Count: LongInt): LongInt of object. Sisäisesti pari muuttuu TCallbackByteRangeSourceksi, joka näyttää SourceSizein ja ReadRangein, käärittynä virtaan, jonka omistajuus siirtyy dokumentille. Kutsukohteesi ja sen taustajärjestelmä jäävät sinulle: dokumentti vapauttaa kääreen close-, clear- tai reload-vaiheessa, mutta ei koskaan koske metodiosittimen takana olevaan siirtoobjektiin
Sopimus on tarkoituksella salliva toiseen suuntaan ja tiukka toiseen. Lyhyt luku on laillinen ja tarkoittaa vain, että jäsennin kysyy uudelleen. Poikkeuksen nostava kutsu muunnetaan lyhyeksi luvuksi ja raukeaa normaalin latausvirheen polkuun. Kutsu, joka väittää kirjoittaneensa enemmän kuin Count tavua, rajataan, koska virheellinen palveluntarjoaja ei saa kirjoittaa välimuistipuskurin yli. Salasanayritykset rakentavat tuoreen aluevirran ja tuoreen jäsennystilan saman kutsulähteen päälle, joten epäonnistunut yritys ei voi jättää jälkeensä vanhentunutta kohtaa, ikkunaa tai salauksen purkutilaa
type
TObjectStoreSource = class
private
FClient: TRangeHttpClient;
FSize: Int64;
public
function ReadRange(Sender: TObject; Offset: Int64;
Buffer: Pointer; Count: LongInt): LongInt;
function IsResident(Sender: TObject; Offset: Int64;
Count: LongInt): Integer;
property Size: Int64 read FSize;
end;
function TObjectStoreSource.ReadRange(Sender: TObject; Offset: Int64;
Buffer: Pointer; Count: LongInt): LongInt;
begin
{ yksi estävä GET, jonka otsikkona Range: bytes=Offset-(Offset+Count-1) }
Result := FClient.FetchInto(Offset, Count, Buffer);
end;
{ ... }
Lib := TPDFlib.Create;
Src := TObjectStoreSource.Create(BucketUrl);
try
if Lib.LoadFromRangeSource(Src.Size, Src.ReadRange, '',
65536, 8 * 1024 * 1024, 2, Src.IsResident) = 1 then
Lib.SelectPage(900);
finally
Lib.Free; { vapauttaa käärevirran }
Src.Free; { siirtokerroksesi, elinkaaresi }
end;
Kuinka paljon aluevälimuisti oikeasti pitää sisällään?
Oletuksena 4 MiB, jaettuna chunk-tasuisiin ikkunoihin ja poistettuna LRU-periaatteella. Aiempi yhden ikkunan suunnittelu kasvoi sillä pituudella, jota kutsuja pyysi, joten yksi suuri peräkkäinen luku ohitti nimellisen chunk-koon, kun taas satunnainen hyppy heitti edellisen ikkunan heti pois. Nykyinen välimuisti tasaa jokaisen lähteen siirtymän ChunkSizeen, hakee tasan yhden chunkin jokaista hutia kohden ja valvoo kovaa tavubudjettia usean ikkunan yli. Antamasi nimenomainen budjetti nostetaan vähintään yhdeksi täydeksi chunkiksi, joten yksittäinen luku etenee aina chunk kerrallaan ja huippukuorma pysyy ennustettavana. ChunkSize alle 4096 putoaa takaisin oletukseen 64 KiB
Toistuvien lukujen kirjanpito on se osa, joka kannattaa kytkeä telemetriaasi. PDFlibPas tunnistaa toiston tasatun chunkin alusta ja ylläpitää järjestettyjä yhtenäisiä välejä, mikä erottaa aidon ensimmäisen haun uudelleenhausta poiston jälkeen ja pitää kirjanpidon kasvamatta lineaarisesti tiedoston koon mukana. GetRangeSourceCacheInfo palauttaa kokonaiskuvan JSON-muodossa, SetRangeSourceCacheLimit muuttaa budjetin kokoa ajonaikaisesti ja ClearRangeSourceCache pudottaa ikkunat ja nollaa tilastot yhdessä. Budjetin pienentäminen ajonaikaisesti säilyttää historian ja laskee budjettiin perustuvat vapautukset poistoina, joten nouseva repeatedReads tasaisen hitsin rinnalla on merkki siitä, että työjoukko ei enää mahdu
var
Info: WideString;
begin
Lib.SetRangeSourceCacheLimit(16 * 1024 * 1024);
Lib.SelectPage(900);
if Lib.GetRangeSourceCacheInfo(Info) = 1 then
{ "windowCount", "cacheLimitBytes", "cachedBytes", "hits", "misses",
"evictions", "sourceReads", "sourceBytes", "repeatedReads",
"coalescedRequests", "coalescedSourceReads" }
LogRangeStats(Info);
end;
Mitä tapahtuu, kun useat säikeet haluavat saman chunkin?
Ne odottavat yhtä pyyntöä, eivät useita. Klassisessa TStreamissa on yksi sijaintikohdistin, ja kaksi säiettä, jotka kumpikin lukitsevat oikein, voivat silti saada sen ylikirjoitetuksi Seek- ja Read-kutsun välissä, joten PDFlibPasin laiskat objektit ja segmentoidut luvut käyttävät absoluuttista ReadAtia, joka ei koskaan siirrä kohdistinta. Jokaisella tasatulla chunkilla on yksi käynnissä oleva pyyntö, jonka kaikki kyseisen chunkin kutsujat jakavat, vierekkäiset jonossa olevat chunkit yhdistetään ennen kuin lähdeluku alkaa, ja yksi fyysinen luku on rajoitettu 16 MiB:ään, joten rinnakkainen sivutyö ei paisu toisteisiksi pieniksi pyynnöiksi eikä yhdeksi järjettömän suureksi pyynnöksi. Yhdistelyikkuna on oletuksena 2 ms ja koskee vain kunkin ReadAtin ensimmäistä puuttuvaa chunkia; sijaintiin perustuva Read ei koskaan odota sitä, ja nollan antaminen poistaa alkukeräysviiveen kokonaan, mikä merkitsee pitkille peräkkäisille skannauksille, jotka muuten kasaavaisivat odotusta chunk kerrallaan. Sijainti, välimuistin metadata ja lähdeluvut ovat kolmen erillisen lukon takana, ja itse lähdekutsu sarjallistetaan, minkä ansiosta tietokanta- tai objektivarastosovitin, jossa ei ole sisäistä säiesuojausta, kelpaa käyttöön muuttumattomana. Odottajat saavat datasta omat kappaleensa, joten myöhempi LRU-poisto ei voi mitätöidä jo luovutettua puskuria
Voitko kysyä, onko sivu 900 valmis, ilman sen hakemista?
Kyllä, ja juuri sitä varten valinnainen saatavuuskutsu on. Tavallinen lukukutsu ei erota jo saapuneita tavuja niistä, jotka vaativat estävän edestakaisen pyynnön, ja koeluku laukaisisi juuri sen latauksen, jota yrität välttää. TPDFlibRangeAvailabilityEvent vastaa yhteen ainoaan kysymykseen, voidaanko täydellinen alue lukea heti, eikä sillä saa hakea mitään; välimuistin jo kattamat tavut lasketaan aina saatavilla oleviksi. GetRangeSourceDataAvailability yhdistää epäsuorat objektit ristiviitetietueisiin kirjattuihin fyysisiin tallennusalueisiin, ratkaisee pakatut objektit niiden objektivirtasäiliöihin, korjaa siirtyneen PDF-otsikon ja jäsentää objektin vasta, kun koko alue on läpäissyt ei-hakevan koeluvun, joten puuttuva polku ei koskaan kutsu lukukutsuasi
Läpikäynti on rajattu, ei tyhjentävä. Sivukysely kulkee vain sivupuun haarassa, joka sisältää kohdesivun, ja lisää sitten sivun sisällön, resurssit, annotaatiot ja perityt sivuattribuutit, ohittaen Parent- ja P-takaisinkytkennät, joten yksittäinen sivu tai widget ei voi laajentua taaksepäin koko dokumenttiin. Objektigraafi on rajattu 100 000 pyydettyyn objektiin ja syvyyteen 256, virtaobjektit jäsennetään sanakirja ensin -periaatteella, ja täyden jäsennyksen varavaihtoehto sallitaan vain tallennetuille objekteille enintään 4 MiB. JSON-raportti yhdistää päällekkäiset ja vierekkäiset välit ennen laskentaa, joten requiredBytes ja missingBytes lasketaan yhdistetyistä requiredRanges- ja missingRanges-taulukoista, joiden end on mukaan luettu päätepiste. Jo saatavilla olevan objektin kysely voi täyttää aluevälimuistin; puuttuvan objektin kysely jättää lukutilastot koskemattomiksi
var
Report: WideString;
Status: Integer;
begin
Status := Lib.GetRangeSourceDataAvailability(PDF_RANGE_DATA_PAGE, 900,
Report);
if Status = PDF_RANGE_DATA_AVAILABLE then
RenderPageNow
else if Status = PDF_RANGE_DATA_NOT_AVAILABLE then
{ Report sisältää "missingBytes"-kentän sekä yhdistetyt "missingRanges"-alueet }
ShowProgress(Report)
else if Status = PDF_RANGE_DATA_NOT_PRESENT then
ShowMissingFeature; { esim. tiedostossa ei ole lainkaan AcroFormia }
end;
Miksi esilatauksen on iteroidava
Koska nykyisten missingRangesien lukeminen kerran ei tee sivusta saatavilla olevaa. Puuttuva sivupuun solmu tai objektivirta paljastaa seuraavan riippuvuuskerroksen vasta saapuessaan, joten PDFlibPasin esilataustyö ajaa kysely–haku–uudelleenkysely -silmukan, kunnes sivu, lomake tai objektigraafi on täysin saatavilla tai tavu- tai kierrosraja pysäyttää sen. Työ käyttää omaa lukijaansa ja pientä toissijaista välimuistia, jonka tietolähde välittää absoluuttiset luvut alkuperäiselle aluevirralle, mikä pitää jäsennystilan erillään etualan TSmartPDFReaderista, vaikka työ oikeasti lataamat tavut laskeutuvat jaettuun päävälimuistiin. Aluevirtaa kohden on yksi työsäie, mikä vastaa lähdekutsun jo vaatimaa sarjallistamista, ja jono valitsee neljällä prioriteettitasolla ja sitten antojärjestyksellä tason sisällä. MaxBytes laskutetaan fyysisinä chunkitavuina, joten jäsennin, joka pyytää yhtä tavua käsittelemättömän chunkin sisältä, maksaa silti koko chunkin, kun taas jaetussa välimuistissa jo olevat chunkit eivät maksa työlle mitään. Jonossa olevan työn peruuttaminen saavuttaa lopputilan ilman yhtään lähdelukua; käynnissä oleva työ tarkistetaan ennen jokaista riippuvuuskierrosta ja jokaista lähdechunkia, ja aluevirran vapauttaminen odottaa käynnissä olevan kutsun paluuta sen sijaan, että yrittäisi keskeyttää sen
var
Job: Integer;
Info: WideString;
begin
Job := Lib.StartRangeSourcePrefetch(PDF_RANGE_DATA_PAGE, 901,
PDF_RANGE_PREFETCH_PRIORITY_HIGH, 8 * 1024 * 1024, 65536);
if Lib.WaitForRangeSourcePrefetch(Job, 5000) =
PDF_RANGE_PREFETCH_STATE_COMPLETED then
PrepareNextPage
else
Lib.CancelRangeSourcePrefetch(Job);
{ "passes", "plannedRanges", "sourceReads", "fetchedBytes" ja viimeisin
täysi saatavuusraportti, joten LIMIT_REACHED pysyy erotettavissa
tilasta FAILED }
Lib.GetRangeSourcePrefetchInfo(Job, Info);
end;
Milloin tämä rapautuu koko tiedoston lataukseksi
Aluelataus on vetoa tiedoston asetteluun, eivät kaikki tiedostot kunnioita sitä. Linearisoitu tiedosto ISO 32000-1 §7.5.8:n mukaan on hyvä tapaus: ensimmäisen sivun osio lämmitetään avauksessa, rajattuna sekä olemassa olevalla 4 MiB:n turvakynnyksellä että nykyisellä välimuistibudjetilla, joten lämmittely ei heti poista suurinta osaa itsestään. Linearisoimaton tiedosto ratkeaa silti trailerin ja lopun lähellä olevan ristiviiteketjun kautta, mikä maksaa pari ylimääräistä edestakaista pyyntöä, ei katastrofia. Oikea jyrkänne on vioittunut tiedosto, joka pakottaa korjauspolulle, koska ristiviitetaulukon rakentaminen uudelleen merkitsee objektiotsikoiden etsimistä koko dokumentista, ja se on koko tiedoston lataus, joka saapuu chunk kerrallaan. Viive on toinen rehellinen raja: 60 ms:llä per pyyntö satunnaiskäytön jäsennys, joka tarvitsee neljäkymmentä käsittelemätöntä chunkia, viettää yli kaksi sekuntia matkalla riippumatta siitä, kuinka hyvä välimuisti on, ja juuri sen piilottamista varten lukualustusargumentti ja prioriteettijono ovat olemassa. Sama kuri näkyy suoran käytön lähestymistavassa suurten PDF-tiedostojen yhdistämiseen ja jakamiseen, ja tämä välimuisti on pohjalla sekä rinnakkaisessa sivunpiirrossa että katselimen levysivuvälimuistissa
Aluelähteen API, saatavuuskysely ja esilatauksen aikatauluttaja kuuluvat vakiopakettiin PDFlibPas Delphi PDF Library Delphelle, C++Builderille ja Free Pascalille; tuotesivulla on täysi LoadFromRangeSourcein parametrireferenssi sekä esilatauksen prioriteetti- ja tilavakiot