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,545