gentle introduction to backend architecture
(and node.js)
Laurent Perrin
CTO @Front
@l_perrin / frontapp.com
Jargon
Server? API? Scalability?
What the web used to be
- Static pages
- Hyperlinks
- Essentially a file server
Then came dynamic pages
- Perl / Python / PHP
- Databases
- Server returns the result of the execution of a script
Today: APIs
Scalability
Handle more requests when you add hardware
GET /api/1/companies/front/conversations/7743297 HTTP/1.1
Host: app.frontapp.com
Connection: keep-alive
Accept: application/json, text/plain, */*
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.65 Safari/537.36
X-Front-Version: 10
Referer: https://app.frontapp.com/inboxes/shared/laurent/unassigned/7743297
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8,fr;q=0.6
Cookie: ajs_anonymous_id=%2253a9d0f4-499a-4751-8026-cbad7c45f13e%22; km_ai=laurent%40frontapp.com;
HTTP/1.1 200 OK
Content-Encoding: gzip
Content-Type: application/json; charset=utf-8
Date: Wed, 03 Dec 2014 22:50:47 GMT
ETag: W/"3002-269069657"
Vary: Accept-Encoding
X-Front-Version: 10
transfer-encoding: chunked
Connection: keep-alive
{"id":7743297,"url":"/api/1/companies/front/conversations/7743297","type":"conversation","message_type":"email","status":"unassigned","archived":false,"trashed":false,"replied":false,"reacted":false,"subject":"Mentioned by Priscilla Zorrilla","summary":" Reply above this line to comment in Front Hello Laurent! Priscilla Zorrilla @mentioned you in a conversation you follow. \"@jeremy @remi @laurent What the heck is this guy talking about? What an inter","updated_at":1417646915000,"num_messages":1,"has_attachments":false,"contact":{"id":9536,"avatar_type":"contact","class":"contact","source":"email","url":"/api/1/companies/front/contacts/9536","name":null,"handle":"hello@frontapp.com","display_name":"hello@frontapp.com","job":null,"bio":null,"avatar":null,"initials":"H","linked_inbox_id":null,"color":"hsl(133,60%,70%)","inbox_alias":null,"message_type":null,"extra":null,"card_id":9536,"links":[]},"inboxes":["laurent"],"assignee":null,"followers":[],"tags":[],"drafts_glimpse":[],"snoozes":[],"messages":[{"id":11058349,"url":"/api/1/companies/front/messages/11058349","type":"email","class":"message","status":"imported","ext_id":"72a679c33da7dd2f@frontapp.com","conversation_id":7743297,"conversation_url":"/api/1/companies/front/conversations/7743297","from":{"id":9536,"avatar_type":"contact","class":"contact","source":"email","url":"/api/1/companies/front/contacts/9536","name":null,"handle":"hello@frontapp.com","display_name":"hello@frontapp.com","job":null,"bio":null,"avatar":null,"initials":"H","linked_inbox_id":null,"color":"hsl(133,60%,70%)","inbox_alias":null,"message_type":null,"extra":null,"card_id":9536,"links":[],"role":"from"},"recipients":[{"id":9536,"avatar_type":"contact","class":"contact","source":"email","url":"/api/1/companies/front/contacts/9536","name":null,"handle":"hello@frontapp.com","display_name":"hello@frontapp.com","job":null,"bio":null,"avatar":null,"initials":"H","linked_inbox_id":null,"color":"hsl(133,60%,70%)","inbox_alias":null,"message_type":null,"extra":null,"card_id":9536,"links":[],"role":"from"},{"id":1052,"avatar_type":"inbox","cla
{
"id": 7743297,
"url": "/api/1/companies/front/conversations/7743297",
"type": "conversation",
"message_type": "email",
"status": "unassigned",
"archived": false,
"trashed": false,
"replied": false,
"reacted": false,
"subject": "Mentioned by Priscilla Zorrilla",
"summary": " Reply above this line to comment in Front Hello Laurent! Priscilla Zorrilla @mentioned you in a conversation you follow. \"@jeremy @remi @laurent What the heck is this guy talking about? What an inter",
"updated_at": 1417646915000,
"num_messages": 1,
"has_attachments": false,
"contact": {
"id": 9536,
"avatar_type": "contact",
"class": "contact",
"source": "email",
"url": "/api/1/companies/front/contacts/9536",
"name": null,
"handle": "hello@frontapp.com",
"display_name": "hello@frontapp.com",
"job": null,
"bio": null,
"avatar": null,
"initials": "H",
"linked_inbox_id": null,
"color": "hsl(133,60%,70%)",
"inbox_alias": null,
"message_type": null,
"extra": null,
"card_id": 9536,
"links": []
},
"inboxes": ["laurent"],
"assignee": null,
"followers": [],
"tags": [],
"drafts_glimpse": [],
"snoozes": [],
"messages": [{
"id": 11058349,
"url": "/api/1/companies/front/messages/11058349",
"type": "email",
"class": "message",
"status": "imported",
"ext_id": "72a679c33da7dd2f@frontapp.com",
"conversation_id": 7743297,
"conversation_url": "/api/1/companies/front/conversations/7743297",
"from": {
"id": 9536,
"avatar_type": "contact",
"class": "contact",
"source": "email",
"url": "/api/1/companies/front/contacts/9536",
"name": null,
"handle": "hello@frontapp.com",
"display_name": "hello@frontapp.com",
"job": null,
"bio": null,
"avatar": null,
"initials": "H",
"linked_inbox_id": null,
"color": "hsl(133,60%,70%)",
"inbox_alias": null,
"message_type": null,
"extra": null,
"card_id": 9536,
"links": [],
"role": "from"
},
"recipients": [{
"id": 9536,
"avatar_type": "contact",
"class": "contact",
"source": "email",
"url": "/api/1/companies/front/contacts/9536",
"name": null,
"handle": "hello@frontapp.com",
"display_name": "hello@frontapp.com",
"job": null,
"bio": null,
"avatar": null,
"initials": "H",
"linked_inbox_id": null,
"color": "hsl(133,60%,70%)",
"inbox_alias": null,
"message_type": null,
"extra": null,
"card_id": 9536,
"links": [],
"role": "from"
}, {
"id": 1052,
"avatar_type": "inbox",
"class": "inbox",
"source": "email",
"url": "/api/1/companies/front/contacts/1052",
"name": "Laurent",
"handle": "laurent@frontapp.com",
"display_name": "Laurent",
"job": null,
"bio": "CTO, co-founder and chaos monkey @FrontApp (YC S14)",
"avatar": "/api/1/companies/front/cards/260683/avatar/1417451414548",
"initials": "L",
"linked_inbox_id": 5279,
"color": "#E15151",
"inbox_alias": "laurent",
"message_type": "email",
"extra": null,
"card_id": 260683,
"links": [{
"id": 278476,
"card_id": 260683,
"type": "",
"url": "https://github.com/lperrin",
"username": null,
"info": null
}, {
"id": 267300,
"card_id": 260683,
"type": "twitter",
"url": "https://twitter.com/l_perrin",
"username": "l_perrin",
"info": "{\"num_followers\":225,\"num_following\":191,\"num_tweets\":341}"
}],
"role": "to"
}, {
"id": 3108314,
"avatar_type": "contact",
"class": "contact",
"source": "email",
"url": "/api/1/companies/front/contacts/3108314",
"name": null,
"handle": "39c83f23b4913f169d38181afc1ab3da4a2c9dccba0a18a4003bf5396fbfdf80@notifications.frontapp.com",
"display_name": "39c83f23b4913f169d38181afc1ab3da4a2c9dccba0a18a4003bf5396fbfdf80@notifications.frontapp.com",
"job": null,
"bio": null,
"avatar": null,
"initials": "3",
"linked_inbox_id": null,
"color": "hsl(222,60%,70%)",
"inbox_alias": null,
"message_type": null,
"card_id": 2915269,
"links": [],
"role": "reply-to"
}],
"date": 1417646915000,
"inbound": true,
"blurb": " Reply above this line to comment in Front Hello Laurent! Priscilla Zorrilla @mentioned you in a conversation you follow. \"@jeremy @remi @laurent What the heck is this guy talking about? What an inter",
"num_comments": 0,
"comments": [],
"subject": "Mentioned by Priscilla Zorrilla",
"body": "<!doctype html> <html> <head> <meta/> <title>You have been invited to join Front</title> <meta name=\"viewport\"/> </head> <body style=\"background:#edeff0;\"> <center> <table cellpadding=\"0\" cellspacing=\"0\" border=\"0\"> <tbody> <tr> <td align=\"left\"><br/><br/></td> </tr> <tr> <td valign=\"top\"> <table cellpadding=\"0\" cellspacing=\"0\" border=\"0\" align=\"center\" style=\"background:#fff;\"> <tbody> <tr> <td width=\"30\" align=\"left\"> </td> <td width=\"400\" valign=\"top\"> <center> <br><br> <div> Reply above this line to comment in Front <br> </div> <h2 style=\"color:#0c0c0e;font-family:'Helvetica Neue', Helvetica, Arial, sans-serif;font-size:24px;font-weight: 400;\">Hello Laurent!</h2> <p style=\"color:#717982;line-height:24px;font-family:'Helvetica Neue', Helvetica, Arial, sans-serif;font-size:14px;\"><strong style=\"color:#28292e!important\">Priscilla Zorrilla</strong> @mentioned you in a conversation you follow.</p> <p style=\"color:#717982;line-height:24px;font-family:serif;font-size:17px;font-style:italic;\"> \"@jeremy @remi @laurent What the heck is this guy talking about? What an interesting character! \" </p> <br> <p style=\"color:#c6cbd0;line-height:24px;font-family:'Helvetica Neue', Helvetica, Arial, sans-serif;font-size:11px;margin: 0;text-transform: uppercase;\">Email preview</p> <table cellpadding=\"0\" cellspacing=\"0\" style=\"border: 1px solid #c6cbd0;width: 400px;\"> <tr> <td width=\"15\" align=\"left\"> </td> <td> <p style=\"color:#717982;line-height:24px;font-family:'Helvetica Neue', Helvetica, Arial, sans-serif;font-size:12px;text-align:left;\"><strong style=\"color:#28292e!important\">From:</strong> Dean Jackson</p> <p style=\"color:#717982;line-height:24px;font-family:'Helvetica Neue', Helvetica, Arial, sans-serif;font-size:12px;margin: -15px 0 15px;text-align:left;\"><strong style=\"color:#28292e!important\">Subject:</strong> Re: Dean in Front!</p> <p style=\"border-top: 1px solid #c6cbd0;color:#717982;line-height:24px;font-family:'Helvetica Neue', Helvetica, Arial, sans-serif;font-size:12px;margin: -5px 0 15px;padding-top: 10px;text-align:left;\"><div class=\"markdown\"> <p>Hi,</p> <p>If you really want, yeah, you can call me.</p> <p>Personally, I currently have no interest in your product (like Chuck Norris, I work alone), but I am responsible for the IT for <a href=\"http://www.kulturliste-duesseldorf.de\">a non-profit</a> that <em>could benefit enormously from your service</em> (if I've understood it correctly), but there's no way we can afford >$100/month for our 20+ voluntary staff. Our current IT costs are $0, due to Google Apps and my donation of my time and a VPS.</p> <p>I only registered due to a <a href=\"https://github.com/deanishe/alfred-mailto/issues/3\">feature request</a> by someone that uses both your service and an <a href=\"http://www.deanishe.net/alfred-mailto/\">Alfred workflow</a> I wrote, who wanted my workflow to support Front (which it can't because neither Front.app nor frontapp.com support the standard <code>mailto:</code> protocol).</p> <p>I honestly don't know what to say about that. An email client that doesn't support the <code>mailto:</code> protocol is, shall we say, unconventional…</p> <p>If you really want to call me, my phone number is +49 176 31351986.</p> <p>Regards,<br> Dean</p> </div> <style> div#kuli-sig { font-family: 'Trebuchet MS'; font-size: 12px; } div#kuli-sig table { font-size: 12px; } div#kuli-sig hr { height: 1px; border: 0; text-align: left; margin: 12px 0; width: 50%; color: #aaa; background: #aaa; } </style> <div id=\"kuli-sig\"> <hr/> <a href=\"http://www.kulturliste-duesseldorf.de/\"> <img src=\"http://www.kulturliste-duesseldorf.de/Bilder/logo-email.jpg\"/> </a> <p><strong>Kulturliste Düsseldorf e.V.</strong><br/> Dean Jackson<br/> Technik<br/> </p> <table> <tr> <td>E-Mail:</td> <td><a href=\"mailto:admin@kulturliste-duesseldorf.de\">admin@kulturliste-duesseldorf.de</a></td> </tr> <tr> <td>Tel.:</td> <td>+49 176 31 35 19 86</td> </tr> <tr> <td>Post:</td> <td>Postfach 25 01 62, 40093 Düsseldorf</td> </tr> </table> <p> <a href=\"http://www.kulturliste-duesseldorf.de/\">www.kulturliste-duesseldorf.de</a> </p> <p> <table> <tr> <td colspan=\"2\"><strong>Spendenkonto:</strong></td> </tr> <tr> <td colspan=\"2\"> Kulturliste e.V.<br/> Stadtsparkasse Düsseldorf </td> </tr> <tr> <td>BLZ</td> <td>300 501 10</td> </tr> <tr> <td>Kto.</td> <td>100 655 1558</td> </tr> <tr> <td>IBAN</td> <td>DE54 3005 0110 1006 5515 58</td> </tr> <tr> <td>BIC</td> <td>DUSSDEDDXXX</td> </tr> </table> </p> <p> Amtsgericht Düsseldorf: VR 10824<br/> Finanzamt Mitte/Süd: Steuernummer 106/5748/2599<br/> </p> </div> <div class=\"markdown\"> <p>On 3 Dec 2014, at 23:18, Priscilla Zorrilla wrote:</p> <blockquote class=\"front-blockquote front-blockquote\"> <p>Hey Dean!</p> <p>We can determine together if Front is a good fit for your company and save you time on evaluation. Are you available for a 10 min call?</p> <p>Priscilla</p> <p>—<br> Priscilla Zorrilla<br> Customer Success \\o/<br> Mobile: +1 407-461-6401<br> Sent with FrontApp</p> </blockquote> </div></p> </td> <td width=\"15\" align=\"left\"> </td> </tr> </table> <br> <br><br> <a href=\"https://web.frontapp.com/api/1/noauth/open_in_front/L2Zyb250L3VuaWZpZWQvYWxsLzc3NDE2NzUvMTEwNTc3MzUvNzc5Mjk=\" title=\"View in Front\" style=\"font-weight:500;font-family:'Helvetica Neue', Helvetica, Arial, sans-serif;background:#42A145;border:solid 1px #42A145;padding:20px 40px;letter-spacing:1px;color:#fff !important;text-decoration:none;font-size:14px;clear:both;\">View in Front</a> <br><br><br><br> <hr style=\"border:none;border-bottom:1px solid #ccc;width:50%\"> <br> <span style=\"display:block;border:none;font-family:'Helvetica Neue', Helvetica, Arial, sans-serif;color:#717982 !important;font-size:11px;line-height:16px;border:none;text-decoration:none;margin: 10px 0 0;\">If you have any questions, feedbacks or problems feel free to email <a href=\"mailto:hello@frontapp.com\" style=\"color:#28292e!important\">hello@frontapp.com</a> <br><br>Thanks and welcome! <br><br></span> <br> </center> </td> <td width=\"30\" align=\"left\"> </td> </tr> <tr> <td width=\"30\" align=\"left\"> </td> <td width=\"400\" valign=\"top\"> <center> <a href=\"#\" title=\"Front\" style=\"border:none;text-decoration:none\"><img src=\"https://web.frontapp.com/img/email/front-icon.jpg\" alt=\"Front\" width=\"41\" style=\"border:none\"></a> <br><br><br> </center> </td> <td width=\"30\" align=\"left\"> </td> </tr> </tbody> </table> <br><br><br> </td> </tr> </tbody> </table> </center> <br/><img src=\"http://r.frontapp.com/o/038k/594c54af/vll2zv3e.gif\" height=\"1\" width=\"1\" alt=\"null\" border=\"0\"/> </body> </html> ",
"attachments": [],
"reply_to": {
"id": 3108314,
"avatar_type": "contact",
"class": "contact",
"source": "email",
"url": "/api/1/companies/front/contacts/3108314",
"name": null,
"handle": "39c83f23b4913f169d38181afc1ab3da4a2c9dccba0a18a4003bf5396fbfdf80@notifications.frontapp.com",
"display_name": "39c83f23b4913f169d38181afc1ab3da4a2c9dccba0a18a4003bf5396fbfdf80@notifications.frontapp.com",
"job": null,
"bio": null,
"avatar": null,
"initials": "3",
"linked_inbox_id": null,
"color": "hsl(222,60%,70%)",
"inbox_alias": null,
"message_type": null,
"card_id": 2915269,
"links": [],
"role": "reply-to"
},
"forwarded_by": null,
"recipient_opened_date": null
}],
"drafts": [],
"activities": [],
"link": "https://web.frontapp.com/api/1/noauth/open_in_front/L2Zyb250L3VuaWZpZWQvYWxsLzc3NDMyOTc="
}
Use-case
1 server, many apps
request / response
generate a text response quickly
app.get('/api/1/companies/:company_slug', function fetchCompany(req, res, next) {
if (!req.user || req.user.className !== 'teammate')
return res.locals.sendError({status: 'authentication_required', reason: 'You are not signed in.'});
if (req.user.company.slug !== req.params.company_slug)
return res.locals.sendError({status: 'forbidden', reason: 'You do not belong to this company.'});
if (req.user.accountStatus === 'blocked')
return res.locals.sendError({status: 'forbidden', reason: 'This account is blocked. Please contact your administrator.'});
if (!req.user.isActive())
return res.locals.sendError({status: 'forbidden', reason: 'This account is not active. Please contact your administrator.'});
var company = CompanyResource.fetchBySlug(req.params.company_slug);
if (!company)
return res.locals.sendError({status: 'not_found', reason: 'Company does not exist.'});
res.send(company.toJSON());
});
- RAM: ~ns
- Hard drive: ~10ms
- Network: ~100ms
Latency
Most backends are I/O bound
Processes / Threads
"Virtual" CPUs managed by the OS
Model: 1 thread / request
But…
- ~1Mb / thread
- VMs have Global Interpretor Locks
- Concurrency bugs
Async I/O
// about to send a request…
var response = fetchOverNetwork(request); // block here
doSomething(response);
// about to send a request…
fetchOverNetworkAsync(request);
// this is executed immediately
// about to send a request…
fetchOverNetworkAsync(request);
// this is executed immediately
var response = waitForRequest(request); // block here
doSomething(response);
sendRequestsAsync();
while (response = waitUntilNextEvent()) {
doSomething(response);
sendRequestsAsync();
}
GET /api/1/companies/front/conversations/774
GET /api/1/companies/front/conversations/7743297 HTTP/1.1
Host: app.frontapp.com
Connection: keep-alive
Accept: application/json, text/plain, */*
User-Agent: Mozilla/5.0 (Macintosh;
GET /api/1/companies/front/conversations/7743297 HTTP/1.1
Host: app.frontapp.com
Connection: keep-alive
Accept: application/json, text/plain, */*
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.65 Safari/537.36
X-Front-Version: 10
Referer: https://app.frontapp.com/inboxes/shared/laurent/unassigned/7743297
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8,fr;q=0.6
Cookie: ajs_anonymous_id=%2253a9d0f4-499a-4751-8026-cbad7c45f13e%22; km_ai=laurent%40frontapp.com;
node.js
- UNIX System V: select/poll — 1983
- Windows: IOCP — 1995
- FreeBSD: kqueue — 2000
- Linux: epoll — 2002
- NIO: Java — 2002
- Netty: Java — 2004
- Twisted: Python — 2002
- EventMachine: Ruby — 2006
- node.js: JavaScript — 2009
node.js is the first async only platform
NPM — npmjs.org
app.all('/api/1/companies/:company_slug*', function loadCompany(req, res, next) {
if (!req.user || req.user.className !== 'teammate')
return res.locals.sendError({status: 'authentication_required', reason: 'You are not signed in.'});
if (req.user.company.slug !== req.params.company_slug)
return res.locals.sendError({status: 'forbidden', reason: 'You do not belong to this company.'});
if (req.user.accountStatus === 'blocked')
return res.locals.sendError({status: 'forbidden', reason: 'This account is blocked. Please contact your administrator.'});
if (!req.user.isActive())
return res.locals.sendError({status: 'forbidden', reason: 'This account is not active. Please contact your administrator.'});
CompanyResource.fetchBySlug(req.params.company_slug, function (err, company) {
if (err)
return res.locals.sendError(err);
req.company = company;
next();
});
});
Real-time APIs
- Request/Reply → Persistent connections
- ~10 connections → ~1000 connections
Real world examples
Liveradio metadata crawler
- Thousands of radio stations
- "XML" feeds need to be polled every ~10s
- Real-time SHOUTcast streams
Front IMAP sync
- Push IMAP
- Thousands of IMAP connections
- Spread over 3 servers today
Conclusion
/opt/git/front/server/domain/reqcache.js:44 in ""
/opt/git/front/server/resources/company_res.js:149 in "CompanyResource.deserialize"
/opt/git/front/server/resources/company_res.js:76 in ""
/opt/git/front/server/storage/cache_db.js:27 in ""
/opt/git/front/server/connectors/mc/index.js:93 in ""
domain.js:183 in "b"
/opt/git/front/server/connectors/mc/memcached.js:19 in "Object.callback"
/opt/git/front/node_modules/memcached/lib/memcached.js:679 in "memcached.delegateCallback"
/opt/git/front/node_modules/memcached/lib/memcached.js:738 in "Client.rawDataReceived"
/opt/git/front/node_modules/memcached/lib/memcached.js:662 in "BufferBuffer
Error: read ECONNRESET
at errnoException (net.js:901:11)
at onread (net.js:556:19)
Questions?
Laurent Perrin
@l_perrin
github.com/lperrin
coderwall.com/p/u/lperrin
intro_nodejs
By Laurent Perrin
intro_nodejs
- 1,555