Et Mozartøjeblik

Jeg sidder og skal have skrevet noget hobbykode.

Ude i datamuseum.dk har vi brug for et "bitarkiv" hvor vi kan arkivere historiske bits -- for evigt.

Det er denne lille tilføjelse der gør at det ikke bare bliver et par shell-scripts eller noget andet hurtigt klamp.

Selve lagringen kommer til at foregå i "WARC" fil-format, som er ved at blive defacto standarden i digital bevaring og efter at have tænkt lidt over tingene er jeg kommet frem til hvordan det skal hænge sammen for os.

Vi skal have en rå storage-service, hvor man kan tilføje et objekt eller hente det igen givet dets unikke "WARC-Record-ID".

Servicen leveres via HTTP/1.1 med POST, GET og HEAD requests og er utroligt simpel, hvilket naturligvis er med vilje.

Fordi WARC formatet er et "append only" format (et af grundene til at vi valgte det) skal alle POST requests serialiseres for ikke at have race-conditions og det gør også nogle boundary-conditions når en WARC fil når den ønskede størrelse nemmere at håndtere.

I teorien kunne man godt nøjes med at åbne WARC-filen i append mode og skrive hele records i en writev(2) operation, men da vi kan finde på at lagre hele diskimages kan det blive endog meget store write-operationer og det løser i særdeleshed ikke den race-condition der er når filen er ved at være fyldt.

Men alt i alt er det hele sådan set klar, jeg mangler bare lige at taste sourcekoden ind.

Hvilket fik mig til at tænke på en sætning Mozart skrev i et brev til sin far Leopold:

"Nun muß ich schließen, denn ich muß über Hals und Kopf schreiben; componirt ist schon alles, aber geschrieben noch nicht."

(For dem der ikke er helt stærke i tysk mere: "Nu må jeg slutte og skrive over hals og hoved; Alt er komponeret, men ikke nedskrevet.")

Vel er jeg ikke Mozart og dette ikke Idomeneo, men jeg må erkende at jeg lider af skriveblokering.

Ikke fordi jeg ikke kan, eller ikke vil, men jeg ledes ved at skulle skrive det samme kode endnu en gang.

Der skal bruges en tråd der accept(2)'er på en socket, der skal bruges en thread-pool, der skal bruges en HTTP/1.1 modtager/parser osv. osv.

Jeg har ikke tal på hvor mange gange jeg har skrevet den kode tidligere, og det er forsvindende få gange i forhold til hvor mange gange alle mulige andre har gjort det og det udstiller hvor antikt og ærligt talt utidssvarende UNIX API'et er.

Det er 30 år siden BSD4.2 blev releaset med socket(2) API'et, burde vi ikke i mellemtiden have kunnet blive enige om at bygge lidt bekvemme biblioteksrutiner ovenpå ?

F.eks:

int listen_socket(const char *addr);

Så vi ikke alle skal igennem hele socket(2), getaddrinfo(3), bind(2), listen(2) rutinen hver gang vi skal skrive en server ?

Og hvad med en:

struct http *recv_http1_header(int fd, double timeout);

Nu hvor vi er igang, så vi ikke alle skal sidde og lede efter [CR]?NL[CR]?NL og skille linierne og kigge efter ':' mens vi husker på HTTP's hjernelamme "continuation lines" og alt det der ?

Ja, jeg har kigget igennem /usr/ports for at se om der var noget jeg kunne bruge, men enten får jeg kvalme af kodekvaliteten, eller også trækker autocrap alt fra OpenSSL til g77 ind for at bygge noget der kalder sig noget i stil med "en simpel lille HTTP server."

Hele ideen i systembiblioteker er at de indeholder veldesignede implementeringer af standard funktionalitet som har et velgennemtænkt API og som er testet helt ud i hjørnerne, så man bare uden videre kan bruge dem, uden at skulle bekymre sig om de nu også gør tingene rigtigt.

Det er f.eks derfor vi ikke sidder og skriver vores egne kvadratrodsrutine eller Quicksort hver gang vi skal bruge dem.

Men UNIX API'et forstenede i slutningen af 1980erne og bortset fra IPv6's kvaksalverisk plastikkirugi er der intet sket siden og derfor ender vi med tusindevis af implementeringer af HTTP, XML, krypto-algoritmer og andre helt fundamentale ting.

Jeg er så træt af det pis, at jeg seriøst har siddet og kigget på om jeg skulle starte fra en kopi af Varnish kildeteksten, bare for at komme uden om min lede ved at skrive den N'te version af kode der burde have været en del af systembibliotekerne i mindst 15 år.

Det kommer til at vare lang tid før fremskridtet går for vidt.

phk

Kommentarer (29)
sortSortér kommentarer
  • Ældste først
  • Nyeste først
  • Bedste først
Jesper Louis Andersen

Det ser ud som om du har gang i en venti-style server. Det er helt sikkert en god ide. For en måned eller 2 siden hackede jeg en venti-server sammen i Erlang og backede den med LevelDB og det var omkring 250 liniers kode alt inklusive.

Jeg har stadig ikke fattet hvorfor der ikke er et content-addressable-store indbygget for længst i UNIX...

  • 0
  • 0
Morten Siebuhr

Lidt samme situation herskede i lang tid på Python-fronten, hvor der enten var Pythons noget svage standard-lib eller et af de store frameworks (Django, Zope, ...).

Det blev et par gutter ret trætte af og skrev deres DIY-framework, werkzeug. Det er alle stumperne til at lave et framework, men uden al magien. Man kan tage request-parseren, response-objekterne og så springe cookies, URL-routing, templating og så videre over. Eller omvendt.

Jeg er desværre ikke bekendt med et tilsvarende projekt til C.

(En af gutterne udgav i øvrigt et framework baseret på Werkzeug + oceaner af implicit state/magi/genveje som en første-april joke. Mange tog det desværre seriøst og begyndte at bruge det, så nu bliver det aktivt udviklet...)

  • 0
  • 0
Anonym

Det lyder som et problem fra de glade dos-dage hvor man nærmest skulle lave kode til hvert grafikkort. Men kan kun give dig ret. Man ser også problemet med ruby hvor man meget hurtigt har 3 -4 xml-parsere installeret.

"Det er noget forbandet rod, Wulff"

  • 0
  • 0
Jacob Christian Munch-Andersen

Et miljø designet med henblik på at gøre det nemt at skrive HTTP servere. Nogle menneskers tæer vil selvfølgelig nok krumme i en helbredsmæssigt uforsvarlig grad ved tanken om at skrive en server i JavaScript, men det virker overraskende godt.

  • 3
  • 0
Jesper Louis Andersen

Bortset fra CAS tror jeg egentlig ikke der er noget fællesskab...

Din protokol er stort set den samme, bort set fra at din er HTTP og deres er nemmere at gå til end HTTP. Selve data er naturligvis langt fra WARC fordi de antager at du løser dette med et andet program, f.eks. vac(1). Men til det formål du har virker WARC langt bedre tror jeg.

Hvilke tanker ligger der til grund for at holde data? Bare en passende ZFS-store og lidt fornuftigt kopi?

Iøvrigt, så hylder jeg at du ikke bare tager den korte vej "Det skal bare hældes i Dropbox!" - for det er en klytløsning :)

  • 0
  • 0
Poul-Henning Kamp Blogger

Men til det formål du har virker WARC langt bedre tror jeg.

Hvilke tanker ligger der til grund for at holde data?

Det at vi arbejder musealt flytter fokus lidt i forhold til andre datalagringsopgaver, det er ikke nok at bevare data, vi skal også bevare hvad vi ved om dem, "metadata", inklusive f.eks fotos af diskette-labels, hvordan de er kommet ind i samlingen osv. osv.

WARC formatet er ikke som sådan obligatorisk, men det at andre museer, f.eks DKB som "høster" det danske hjørne af internettet, kender og arbejder med WARC, gør det en andelse mere "stuerent" i det museale miljø.

Rent lagermæssigt kunne vi bare have brugt tar(1) hvis det var det, men WARC er tænkt til bevaringsopgaver og har derfor "indbygget" plads til data/metadata splittet, support for hash-integritet osv.

Med hensyn til at få WARC-filerne til at overleve har vi allerede løst første halvdel af opgaven når vi har brugt et format med indbygget hash-integritet. Den anden halvdel handler bare om at have kopier nok, så de ikke alle rammes af den samme skade.

I første omgang bruger vi diskspejling på den lokale server og et antal mere eller mindre off-line kopier, formodentlig noget med en kopi i min kælder og en kopi i Mogens kælder til at begynde med. Vi er en forening med relativt begrænsede resourcer :-)

Når vi kommer lidt igang håber jeg på en aftale med Det Nationale Bitmagasin (http://digitalbevaring.dk/det-nationale-bitmagasin/) om at de opbevarer en eller flere kopi af vores bits.

  • 1
  • 0
Lars Lundin

Det at vi arbejder musealt flytter fokus lidt i forhold til andre datalagringsopgaver, det er ikke nok at bevare data, vi skal også bevare hvad vi ved om dem, "metadata", inklusive f.eks fotos af diskette-labels, hvordan de er kommet ind i samlingen osv. osv.

Sådan nogenlunde apropos vil jeg lige nævne at til langtidslagring af deres scannede dokumenter har Vatikanet valgt at bruge FITS. Grundene var bl.a. gode muligheder for metadata og at standarden er åben.

  • 0
  • 0
Poul-Henning Kamp Blogger

Vatikanet valgt at bruge FITS.

Hmm jae. Det har den ulempe at du ikke kan lave en definitiv hash af artifaktets lagerform, fordi der efterfølgende kan og vil komme opdateringer af metadata.

Derfor foretrækker de fleste arkiver at gennem artifakter for sig og metadata for sig. Det er lidt nemmere at styre fordi det løser nogle underlige selvreference problemer.

F.eks kan et billede af en hulstrimmel sagtens være et artifakt i sig selv, selvom den (også) er metadata for de bits der er på strimmlen.

Vi planlægger at gemme metadata i DCMI/XML format, på samme måde som vi gemmer artifakter og hægte det hele sammen med stabile object-identifiers der bygges rundt om artifakternes (eller metadataenes) SHA256 hash.

Ulempen er at man ikke kan rette i metadata, men kun gemme en ny version. Det har fordele nok i sig selv til at vi ikke ser det som en ulempe.

  • 2
  • 0
Verner Langballe

Jeg faldt selv for et par år siden over et program der kan alt det jeg ønskede mig, dvs mail, dokumenter, Hjemmesider fax osv. det hedder greenstone (UNESCO er parthaver).

Jeg var lidt usikker på kvaliteten, så er der nogen der kender tilsvarende produkter eller den tre bogstavs kombination de garanteret beskrives med :).

  • 0
  • 0
Lars Lundin

"Hele ideen i systembiblioteker er at de indeholder veldesignede implementeringer af standard funktionalitet som har et velgennemtænkt API og som er testet helt ud i hjørnerne, så man bare uden videre kan bruge dem, uden at skulle bekymre sig om de nu også gør tingene rigtigt.

Det er f.eks derfor vi ikke sidder og skriver vores egne kvadratrodsrutine eller Quicksort hver gang vi skal bruge dem."

Istedet for
void qsort()
kunne jeg faktisk godt have ønsket mig at funktionen var af en integer type som returnerede nul når og kun når ingen af kaldene af sammenligningsfunktionen returnerede nul.

Det med et velgennemtænkt API synes jeg ikke altid er lige let.

  • 1
  • 0
Jimmy Selgen Nielsen

Hvad angår Twisted så har det været her i 12 år, så mon ikke de værste "lus" er ved at være fjernet :)
warc api'et ser ud til at være frigivet af Internet Archive i egen høje person, så mon ikke det i det mindste overholder spec'en (kode kvalitet er der desværre ingen garantier for).

Python i sig selv er vel stabilt nok.

Performance er vel nok den største indikator på at skrive det i C, men da der i forvejen er valgt REST, samt der stort set kun laves I/O, så formoder jeg at Python så rigeligt kan følge med.

Om ikke andet, så kunne selve opgaven med at udvikle løsningen da blive en del sjovere hvis man samtidigt kunne lære noget nyt, istedet for at bande over at skrive det samme om og om igen.

  • 1
  • 0
John Jensen

Jeg tror problemet løses kunne mindskes ved at omskrive/udvide api'en så race conditions, såvel som tiden der skal låses for at undgå dem, minimeres, i det mindste når det drejer sig om store diskimages.
Man kunne f.eks. arbejde med midlertidige resourcer, som oprettes ved et eller flere POSTs og som så ved endt upload tilføjes arkivet ved endnu et POST. Der er stadig mulighed for race condtions, men da alle bits nu er tilgængelige på servere, burde en atomisk append kunne udføres relativt hurtigt.

  • 0
  • 0
Lars Tørnes Hansen

libvevent (http://libevent.org/) ser ret interessant ud.

Libevent additionally provides a sophisticated framework for buffered network IO, with support for sockets, filters, rate-limiting, SSL, zero-copy file transmission, and IOCP.

Libevent includes support for several useful protocols, including DNS, HTTP, and a minimal RPC framework.

Desuden virker libevent på mange OS:

Libevent should compile on Linux, *BSD, Mac OS X, Solaris, Windows, and more.

  • 1
  • 0
John Jensen

Ja, bortset lige fra at den kan risikere at være på flere gigabyte...


Ja så er der selvfølgelig grænser for hvor hurtigt det kan gøres.

Hvis man nu i stedet for at for at skrive direkte til WARC filen, skrev de enkelte resourcer råt til disk og så lod en backend process stå for den serialiserede skrivning til WARC filen, så slap man for at finde/opfinde et HTTP bibliotek der kunne serialisere POSTs.

Det lyder ellers som et spændende projekt.
God fornøjelse.

  • 0
  • 0
Jesper Louis Andersen

Jeg er lidt overfølsom for at skrive det i noget andet, for det skal være utrolig robust kode og det er jeg bedst til at skrive i C

Mine umiddelbare bud ville være enten Erlang, C eller Go hvis det skal være robust. GC er ikke nødvendigvis et problem, men du skal have et sprog der enten styrer det med hård hånd (Erlang, done right), et sprog hvor du har virkeligt god kontrol med hvornår det allokerer (Go) eller også skal det styres manuelt (C).

  • 0
  • 0
Jesper Louis Andersen

Hvis man nu i stedet for at for at skrive direkte til WARC filen, skrev de enkelte resourcer råt til disk og så lod en backend process stå for den serialiserede skrivning til WARC filen, så slap man for at finde/opfinde et HTTP bibliotek der kunne serialisere POSTs.

Min løsning ville nok være at bruge en merkle-tree konstruktion over data. Du skriver chunks i batches af, say, 64 Kilobyte og samler dem så med en ny skrivning der referer til content addresserne for hver enkelt chunk. Laver du skemaet rigtigt har du O(1) append for alle reelle størrelser du kunne komme på at gemme.

Så snart data ligger der, så kan du svare fordi du kan hive chunks ud og samle dem. Eller du kan background-write chunks til en større fil hvis du vil.

Det har også den fordel at det vil være idempotent, så det kan ikke rigtigt race.

  • 0
  • 0
Troels Henriksen

Kunne du ikke uddybe de forbehold du har til de to ting? Specielt den sidste del.

Mine gæt:

1) Automatisk lagerstyring gør det sværere at håndtere situationer hvor man løber tør for hukommelse. Bevares, moderne overcommit-politikker skaber lignende problemer, men man har da mere af en chance. (Det er dog andre grunde til at det måske er håbløst alligevel.)

2) De færreste sprog gør det muligt at tjekke statisk om alle tænkelige fejlsituationer er håndteret. Det gør C som sådan heller ikke direkte, men med fejlkoder skal al fejlhåndtering foregå i koden lige omkring den kaldte funktion, og det er derfor nemmere at kontrollere om der sker noget overhovedet. Det er ikke helt så tydeligt med undtagelser.

  • 0
  • 0
Log ind eller Opret konto for at kommentere