allan ebdrup bloghoved ny

Node.JS, MongoDB og CDN i skøn forening.

Vi arbejder på et projekt, hvor vi implementerer en arkitektur, jeg har drømt om længe. Arkitekturen bliver brugt til en webapplikation.

Meget overordnet er webapplikationen delt op i to logiske såvel som fysiske komponenter: Frontend og backend

Frontenden indeholder alt, der skal til for at rendere brugergrænsefladen. Samtlige komponenter i frontenden ligger på CDN. Det vil sige, at alt html, html-templates, css, billeder og javascript ligger på CDN, som fuldstændig statiske filer. Der er ikke en eneste stump html, der ikke kommer fra CDN i hele løsningen.

Frontendens eneste interface mod backenden er JSONP-kald, hvor der udelukkende sendes data som JSON-strenge tilbage. Der er altså intet html-markup overhovedet i backenden.
Opdelingen mellem frontend og backend er logisk såvel som fysisk. Frontenden kører simpelthen fra et andet domæne end backenden, og på helt andre servere.

Selve frontenden er lagdelt i JavaScript-koden, model, præsentation og så videre er sepereret, og vi gør brug af observer-patterns og en objektorienteret komponent-model. Selve applikationen er en single-page application, der gør det muligt at holde alt session-state i brugerens browser. Alt templating foregår udelukkende fra JavaScript.

Backenden er bygget med Node.JS og MongoDB. Backenden er også lagdelt. Der er blandt andet et lag, der tager sig af sikkerhed, og et lag der tager sig af at returnere ensartede fejl og statuskoder.

Fordelene ved arkitekturen i forhold til en mere traditionel arkitektur er mange:

  • Ingen session state på webserveren. Da applikationen er en single-page application, kan hver enkelt kald til webserveren ryge til en vilkårlig webserver i backenden. Den eneste session-state der er på serveren er et login-token til at styre sikkerheden. Dette token ligger pt i MongoDB. Det er altså ikke er nødvendigt at køre med sticky-ip. Hvilket gør løsningen mere robust og mere skalerbar.

  • Der er kun et programmeringssprog i hele teknologi-stakken: JavaScript. Serialisering af data foregår sågar i en delmængde af JavaScript: JSON.

  • Ingen licensomkostninger - alt er open source.

  • Lave driftsomkostninger. Når alt undtagen data ligger på CDN, belastes backenden meget mindre end på en mere traditionel arkitektur. Selv med rigtig mange besøgende. Det betyder at prisen for at drive backenden bliver meget lav. Der skal simpelthen mindre serverkraft til. Prisen for CDN er lav. Vi bruger Googles app engine, hvor prisen alene afgøres udfra hvor meget data du sender. Pt. er prisen $0.12/GB.

  • Skalerbar. En CDN skalerer i sagens natur ufatteligt godt. Og backenden skalerer også utroligt godt. Dels fordi Node.JS bruger non-blocking-io og generelt er hurtig, og dels fordi NoSQL-databasen MongoDB performer så godt. Derudover er MongoDB bygget til at skalere ud på rigtig mange servere, hvis det skulle blive nødvendigt.

  • Meget hurtige loadtider for brugeren. En CDN er i sagens natur den hurtigste måde at levere statisk indhold internationalt, med de mange geografisk spredte servere i et CDN. Node.JS og MongoDB er en hurtig kombination. Vores responstid på kald til backenden, målt fra brugerens browser, inklusiv al netværkstid, ligger mellem 200 ms og 500 ms, afhængig af hvor meget data der returneres (op til 200 KB, der gzippes). Det er gennemsnitstiden, målt på brugere fra hele verden.

  • Når du bygger din backend, bygger du samtidig et API til din applikation, som du kan gøre offentlig tilgængelig for dine brugere. Du slår simpelhen to fluer med et smæk. Samtidig gør den skarpe opdeling mellem backend og frontend, at samarbejdet mellem backend og frontend-teams effektiviseres.

  • Den del af dit website der ikke kræver backend-kald, er immun overfor denial of service-angreb. Det har vi ikke testet i praksis, så udsagnet skal tages med et gran salt. Men når hele dit statiske website ligger på CDN, er det spredt på rigtigt mange servere. Jeg tror det bliver svært for en angriber at lægge Googles CDN ned. Du vil ihvertfald kunne overleve, at dit website bliver nævnt på CNN, med tilhørende besøgsstorm.

  • Skemaløs database. Når man først har prøvet at arbejde med en skemaløs database, har man ikke lyst til at gå tilbage til en relationel database. Den skemaløse database gør blandt andet, at frontendudvikleren kan lave meget mere arbejde, uden at skulle involvere backend-folkene. Når backend-folkene endelig inddrages, er det i halvdelen af tilfældene blot som sparringspartnere, ikke som udførende.

  • Ingen ORM - da alt er JSON-strenge og JavaScript-objekter

Arkitekturen og teknologistakken har indtil videre været en drøm at arbejde med. Jeg håber, du føler dig inspireret. Kommentarer og spørgsmål modtages med kyshånd.

Kommentarer (27)
sortSortér kommentarer
  • Ældste først
  • Nyeste først
  • Bedste først
#3 Allan Ebdrup Blogger

Hej Allan. Spændende læsning. Kan du uddybe lidt mere omkring det med login-token i databasen samt hvad sticky-ip og problemet med ditto er?

Ang. sticky-ip, så er der flere problemer med sticky-ip.

1) Du kan ikke loadbalancere optimalt, da requests fra samme ip altid skal til samme webserver, da session state ligger på den webserver. Det er et problem hvis du har mange rigtigt lange sessions, eller hvis du har mange besøgende med samme IP. Jeg har hørt om amerikanske firmaer der har 100.000 mennesker siddende bag samme IP.

2) Robusthed. Hvis en webserver går ned dør alle de sessions der ligger på den og brugeren skal logge ind igen.

Der er flere andre løsninger end sticky ip. Men det elegante ved en single-page application er at du bare har alt dit session state liggende i browserens hukommelse, det behøver ikke at komme op på serveren. (bortset fra lige login token, som du af sikkerhedsgrunde bliver nød til at checke serverside) Da du ikke laver sideskift, lever alle dine JavaScript objekter lige så længe som brugeren har browseren åben med din applikation. De dør først når brugern navigerer væk fra din applikation.

Hver eneste kald til webserveren kan altså ramme en vilkårlig webserver i din webfarm. Og det er yderst minimalt hvad du har på backenden for at holde øje med session. Samtidig kan du gå amok i "session state" på browseren hvis du skulle have lyst til det. Det kan man faktisk lave nogle ultra fede ting med. Jeg har tidligere delt en video med et eksempel: http://www.version2.dk/blog/et-interrupt-en-pipeline-og-en-koe-saa-spark...

Backend hostes på Amazon EC2 (s)

  • 0
  • 0
#6 Allan Ebdrup Blogger

Jeg har læst at mongo dB ikke altid er det fede at bruge, noget med datata.

Til vores projekt er MongoDB det rigtige, til andre kunne CouchDB sikkert bruges. Prøv at læse det du linker til igennem igen. Der er sået tvivl om ægtheden af det indlæg med ankepunkterne (som er skrevet anonymt, og omhandler en gammel version af MongoDB). Hvis du kigger på listen af firmaer der står frem og fortæller om deres brug af MongoDB, så er jeg ikke bekymret.

De data vi har i vores løsning ville ikke passe ned i CouchDB's noget simplere dokument-model.

Vi kan godt leve med eventual persistency, og har ikke brug for fuld ACID.

CouchDB's RESTful API ville ikke kunne understøtte den opdeling vi har mellem frontend og backend, med cross domain JSONP-kald.

Og den måde data skal behandles fra vores datakilder, gør at Node.JS passer ned i den struktur rigtigt godt.

  • 2
  • 0
#7 Mikkel Bundgaard

Det lyder spændende. Jeg håber du vil skrive lidt om dine erfaringer når I har rullet det ud.

Jeg lavede på et tidspunkt et projekt i meget mindre skala, men hvor ideen var at statiske sider skulle kalde backenden med JSON. Her opdagede jeg så hvad det betød at google, bing m.fl. ikke indekserer javascript, men kun statisk tekst.

Så min største anke mod at bruge din ide på alt, er hvis det er dynamisk indhold, så som nyheder etc. så bliver det ikke indexeret.

  • 1
  • 0
#9 Vijay Prasad

Jeg har også brugt en "javascript-frontend" løsning tidligere, men, igen med udfording ifht. indeksering - så, det kunne jeg godt tænke mig at høre lidt mere om :-)

(Vores løsning var noget der genererede statiske HTML filer på deploy-tidspunktet og ellers kun anvendte dynamiske sider til bruger-funktionalitet - men ikke rigtigt noget der egner sig til f.eks. et CMS-system hvor indhold er dynamisk)

Mvh,

  • 0
  • 0
#10 Allan Ebdrup Blogger

Jeg har også brugt en "javascript-frontend" løsning tidligere, men, igen med udfording ifht. indeksering - så, det kunne jeg godt tænke mig at høre lidt mere om :-)

Google er skam begyndt at indeksere ting der kræver at JavaScript kører:

http://googlewebmastercentral.blogspot.com/2011/11/get-post-and-safely-s...

Det var også på tide! Google har blokeret for udviklingen af fornuftige arkitekture i meget lang tid.

I vores løsning er der derudover pt. intet dynamisk der skal indekseres, på selve websitet der hører til webapplikationen. Og webapplikationen kræver login, og har intet indhold der skal indekseres af søgemaskiner.

  • 1
  • 0
#15 Allan Ebdrup Blogger

Kan du fortælle lidt om hvad din webapplikation gør og hvor meget trafik den skal kunne håndtere?

Webapplikationen er en SaaS løsning. Den minder en del om Google Analytics, den installeres på samme måde. Men hvor GA målet trafik på dit site, så logger Muscula JavaScript fejl i produktion.

Selv om du har unit-testet din JavaScript-kode kan der stadig være fejl i den. Hvis du skal være 100% sikker på at din JavaScript-kode ikke har fejl skal du køre den med alle tænkelig input den kan tage (DOM, variable) og det skal du gøre på alle browsere (IE, Firefox, Chrome, Safari, Opera, ...) Det skal du gøre i alle versioner af disse browsere, på alle OS'es (WinXP, Win7, Linux, Mac, ...) og du skal gøre det på alle devices (iPad, iPhone, alle Android devices, ...) I praksis er det umuligt at være 100% sikker på at ens JavaScript er uden fejl.

Derudover er JavaScript fejl uforudsigelige, de kan pluselig opstå når der kommer en ny version af IE, Chrome eller en anden browser, når du ligger en ny vare i din webshop, når du releaser en ny version af dit website, eller af en anden af vitterligt millioner af grunde.

Hvis du ikke logger JavaScript fejl i produktion, så aner du ikke at de forekommer, og du mister brugere helt uden at du ved det.

I praksis har vi set, at alle vores brugere logger JavaScript fejl.

Vi har lavet en løsning der installeres på 5 minutter og så har du en log over fejlene så du opdager dem og kan rette dem.

Ang. trafik, så skal vores logger-script hentes på samtlige af vores kunders sidevisninger (pt. bliver det hentet over to gange i sekundet, det bliver meget meget mere). Vores Backend skal også kunne logge alle de JavaScript-fejl der opstår. Vi skal kunne håndtere at en kunde får lagt noget ged i produktion og logger JavaScript-fejl på hver eneste sidevisning, selvom kunden har fx 1.5 millioner sidevisninger om dagen, som en af vores potientelle beta-testere har.

  • 0
  • 0
#17 Allan Ebdrup Blogger

Hvilke værktøjer har i benyttet til jeres udviklings/test/build miljø for at understøtte denne type setup bedst muligt?

Godt spørgsmål Rune.

Som IDE bruger vi Eclipse, Visual Studion, Emacs osv.

Til versionsstyring bruger vi github (Og udviklerens lokale filer ligger også på dropbox for nogle af tingene, for løbende backup)

Til frontenden har vi et egenudviklet buildsystem, og vi bruger både vores egen obfuscator og Closure compileren fra Google

Til unittest af frontend bruger vi qunit fra jQuery-folkene

Vi bruger også jsHint

Arkitekturen giver meget vide rammer for hvor frontenden kan ligge, da den bare er statiske filer, så den ligger forskellige steder til test og staging(QA).

Jeg spørge lige en kollega om han vil svare lidt mere på backend-delen.

  • 0
  • 0
#19 Martin Skøtt

Hvilke værktøjer har i benyttet til jeres udviklings/test/build miljø for at understøtte denne type setup bedst muligt?

Jeg spørge lige en kollega om han vil svare lidt mere på backend-delen.

Backenden er udviklet 100% i Emacs på Linux. Min arbejdsstation kører Debian unstable og vores servere kører Debian stable. Da udviklingen af såvel Node.JS og MongoDB går ret hurtigt backporter jeg selv pakkerne fra unstable til stable.

Til test bruger vi Vows, som både bruges til unit testing og test af de JSONP services frontenden taler med.

På server siden bruger vi NGinx til at tage sig af SSL kommunikationen. Vi bruger Supervisor til at holde styr på Node processerne. Supervisor sørger blandt andet for at genstarte Node hvis den crasher hvilket vi havde oplevede et par gange med en tidlige version af MongoDB driveren til Node.

Deployment foregår via. Git og Tarsnap bruges til backup. Til overvågning bruger vi Munin med en plugin til at monitorere MongoDB.

Servervedligehold er pr. manuelt, men jeg har til hensigt at begynde at bruge Puppet eller Chef på et tidspunkt.

Martin, backendudvikler på Muscula.com

  • 2
  • 0
#20 Martin Skøtt

Hej Allan. Spændende læsning. Kan du uddybe lidt mere omkring det med login-token i databasen samt hvad sticky-ip og problemet med ditto er?

Allan skrev ikke så meget om login token så det retter jeg lige op på :)

Når en bruger logger ind på frontenden får han et login token, som også gemmes i databasen. Frontenden sender token med ved alle senere kald til backenden så den ved hvilken bruger det er der foretager dem. Det vigtige er at token kun er et bevis på login - der er ikke bundet nogen anden form for tilstand til token.

Backenden gemmer tokens i databasen så det er ligegyldigt hvilken backend server senere forespørgsler sendes til - så længe den kan verificere token. Vi bruger databasen, men vi kunne sagtens have brugt Redis eller Memcached i stedet.

Martin, backendudvikler på Muscula.com

  • 0
  • 0
#22 Allan Ebdrup Blogger

Forretningsmodellen er en SaaS løsning med forskellige tiers hvor den månedlige abonnemet har forskellig pris og forskellige ting/features indeholdt. Flere detaljer kan jeg ikke give lige nu for vi arbejder stadig med det.

  • 0
  • 0
#23 Jonas Swiatek

Det er altid fedt når produkter som MongoDB bliver taget i brug et nyt sted. Det er et fabelagtigt produkt.

Dog syntes jeg ikke er svartiderne på 200-500ms virker specielt gode. Enten er der enormt meget latenstid involveret fra klienten til serveren, eller også bliver der foretaget for mange ikke-asynkrone operationer i node.js (f.eks. iteration over store datasæt?)

Kan du fortælle lidt om hvor svartiderne opstår?

  • 0
  • 0
#24 Allan Ebdrup Blogger

Dog syntes jeg ikke er svartiderne på 200-500ms virker specielt gode. Enten er der enormt meget latenstid involveret fra klienten til serveren, eller også bliver der foretaget for mange ikke-asynkrone operationer i node.js (f.eks. iteration over store datasæt?)

Svartiderne måles i brugerens browser. Tiden starter når et script tag indsættes i DOM'en og tiden stoppes når der er returneret et JavaScript objekt, dvs. når JSON strengen fra backenden, er parset og klar til brug i brugerens browser.

Jeg har i tankerne at skrive et andet blogindlæg om præcis hvordan vi måler dette - hvis jeg liiiige finder tid. :-)

Jeg checkede lige op på vores målinger. Og svartidene løber faktisk fra 106.38 millisekunder til 459.99 millisekunder (fra forskellige backend-kald), og det er ikke op til 200KB data der returneres men op til 700KB (der gzippes).

Brugerne kommer fra Danmark, USA, Indien, Mexico, Chile, Frankrig og 32 andre lande.

Vores backend-folk siger at set fra backenden er svartiderne meget hurtigere, men vi måler de faktisk tiderne som de er oplevet fra brugeren. Det er meget mere interresant, for det er jo det brugeren oplever. Der er ikke meget latenstid og der bliver ikke foretaget mange ikke-asynkrone operationer i node.js.

Det korte af det lange er at applikationen føles enormt hurtig for brugeren.

Har du prøvet at måle på samme måde, for backend-kald fra hele verden og set hurtigere svartider?

  • 0
  • 0
#25 Jonas Swiatek

Jeg checkede lige op på vores målinger. Og svartidene løber faktisk fra 106.38 millisekunder til 459.99 millisekunder (fra forskellige backend-kald), og det er ikke op til 200KB data der returneres men op til 700KB (der gzippes).

Ah, okey - jeg troede det var 200KB der blev gzippet ;)

Har du prøvet at måle på samme måde, for backend-kald fra hele verden og set hurtigere svartider?

100-200ms svar er rigtig godt på global skala for den type kald. 500ms i den høje ende, men hvis det skyldes dataen skal sendes halvvejs rundt om jorden er det jo fint nok. Det jeg reagerede på var hvis det til 500ms for selve web applikationen at lave et svar.

Normalt måler jeg hvor hurtigt sidste punkt (typisk reverse proxy serveren - f.eks. Varnish), er om at svare på et request. Og typisk vil jeg foretage den måling på samme netværk, for at undgå at få tilføjet latency som min applikationskode i sig selv ikke kan gøre noget ved.

Ikke fordi det er irrelevant at vide hvor meget der tilføjes når folk sidder i Australien, men mere fordi det er lidt en anden type øvelse at bringe den slags svartider ned. Det kunne tilgængæld være spændene at høre om god bud på hvordan det nedbringes, som ikke "bare" er at etablere applikations instanser i Australien (hvis der er andet man kan gøre end dét).

  • 0
  • 0
#26 Peter Lind

Ikke fordi det er irrelevant at vide hvor meget der tilføjes når folk sidder i Australien, men mere fordi det er lidt en anden type øvelse at bringe den slags svartider ned. Det kunne tilgængæld være spændene at høre om god bud på hvordan det nedbringes, som ikke "bare" er at etablere applikations instanser i Australien (hvis der er andet man kan gøre end dét).

Hvis du får ekstra latency pga. distancen mellem backend-server og klient, så har du ikke andre muligheder for at nedbringe den latency end at flytte tættere.

Du kan selvfølgelig se på andre scenarier (såsom at flytte mindre data, generere ting hos klienten eller andre ideer) men du kan vel ikke på nogen måde nedbringe respons-tiden mellem backend og klient på andre måder end at flytte serveren tættere på (forudsat at kaldet til serveren forbliver det samme)?

  • 0
  • 0
#27 Allan Ebdrup Blogger

100-200ms svar er rigtig godt på global skala for den type kald. 500ms i den høje ende, men hvis det skyldes dataen skal sendes halvvejs rundt om jorden er det jo fint nok. Det jeg reagerede på var hvis det til 500ms for selve web applikationen at lave et svar.

Ja det fik jeg ikke forklaret godt nok, men det var det jeg mente når jeg skrev "målt fra brugerens browser" i blogindlæget. Jeg kunne også forestille mig at det at parse 700KB JSON, koster nogle millisekunder, specielt hvis brugeren bruger en oldgammel PC med en oldgammel browser.

Gunden til at vi måler der, er at det vil altid være første step i at identificere et "problem" set fra brugerens synspunkt. Hvis man så konstaterer et problem, så kan man måle mere andre steder for at finde ud af hvad problemet mere præcist er.

Hvis denne måling er god nok, er alt til gengæld godt.

Den måling du laver på Varnish, ville nok svare mere til en måling lavet på vores CDN, hvor alle de statiske filer ligger, ikke?

Alt indhold der kommer fra vores backend er beskyttet af logintoken, kan opdateres fra flere kilder og skal ikke caches (andet end i MongoDB).

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