PageSourceSearch

https://www.canva.dev/_next/static/chunks/pages/blog/engineering/canvas-compute-backend-6573eb76120fd1b3.js

js canva.dev collected 2026-10-02 02:35:30 UTC 19,852 bytes, 1 lines download raw bytes

1!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="eb5ad674-72d2-424c-aa21-90e4a4fd6a32",e._sentryDebugIdIdentifier="sentry-dbid-eb5ad674-72d2-424c-aa21-90e4a4fd6a32")}catch(e){}}(),(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[99076],{72993:(e,n,t)=>{"use strict";t.d(n,{Q:()=>o});var i=t(65321);t(97109);var a=t(90286),s=t(21790),r=t(83561);let o=e=>(0,i.jsx)(a.W,{blog:s.h,navbar:r.v_,layout:e.layout,children:e.children})},74182:(e,n,t)=>{(window.__NEXT_P=window.__NEXT_P||[]).push(["/blog/engineering/canvas-compute-backend",function(){return t(96966)}])},96966:(e,n,t)=>{"use strict";t.r(n),t.d(n,{default:()=>d,meta:()=>u});var i=t(72993),a=t(85803),s=t(65321),r=t(39084);let o={src:"/_next/static/media/img-0-bcdNAOybjhyMU3k6.c165186b.png",height:345,width:982,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAADCAMAAACZFr56AAAADFBMVEX6+vry8vLm5ub39/ddzGAUAAAACXBIWXMAAAsSAAALEgHS3X78AAAAG0lEQVQImQXBAQEAAACCIM3/nwNMdaOskG1AHQJMACpvx0CZAAAAAElFTkSuQmCC",blurWidth:8,blurHeight:3},c={src:"/_next/static/media/img-0-YVnkqY1HjmnuQGln.d2ded2e9.png",height:305,width:906,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAADCAMAAACZFr56AAAAD1BMVEX09PT9/f3c3Nzv7+/p6emmyGPWAAAACXBIWXMAAAsSAAALEgHS3X78AAAAHElEQVQImQXBAQEAAAiDMDj2z+zGoG7jIDOUxB4CZwAqASokgAAAAABJRU5ErkJggg==",blurWidth:8,blurHeight:3},h={src:"/_next/static/media/img-0-8116cGiWQO8tBUQB.1681e3b3.png",height:629,width:587,blurDataURL:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAICAMAAAAC2hU0AAAADFBMVEX2+Pn9/v7y9PXn5/CsYMchAAAACXBIWXMAAAsSAAALEgHS3X78AAAAJ0lEQVQImS2JwQ0AMBCCEPffuak5H4QgQBKITRtsaV3dcdPxcr6KPghGAD1bpw/AAAAAAElFTkSuQmCC",blurWidth:7,blurHeight:8};function l(e){let n={a:"a",em:"em",li:"li",p:"p",strong:"strong",ul:"ul",...(0,r.R)(),...e.components},{Heading:t,Image:i}=n;return t||m("Heading",!0),i||m("Image",!0),(0,s.jsxs)(s.Fragment,{children:[(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.em,{children:"Originally published by"}),"\n",(0,s.jsx)(n.a,{href:"https://au.linkedin.com/in/samuelkillin",children:(0,s.jsx)(n.em,{children:"Sam Killin"})})," ",(0,s.jsx)(n.em,{children:"at"}),"\n",(0,s.jsx)(n.a,{href:"https://product.canva.com/canvas-compute-backend/",children:"product.canva.com"})," ",(0,s.jsx)(n.em,{children:"on September 15, 2015."})]}),"\n",(0,s.jsx)(n.p,{children:"A great engineer once told me that it doesn't matter how big your\norganisation is, you'll always experience growing pains. Canva is far\nfrom immune to these pains, and we're constantly reevaluating our\ninfrastructure to ensure that systems designed for Canva of old are\nstill up to scratch for Canva of the present, and will be in good stead\nto support the Canva of the future."}),"\n",(0,s.jsxs)(n.p,{children:["My first task as the newest, most starry-eyed member of Canva's\nengineering team was to develop a new backend system, the ",(0,s.jsx)(n.em,{children:"Compute\nService"}),", aimed at tackling one such growing pain: the increasing\nprocessing burden of password hashing and the signing of private CDN\nURLs on our various backend services."]}),"\n",(0,s.jsx)(n.p,{children:"Previously, these CPU intensive hashing and signing functions were\ntightly coupled to the systems requiring this functionality, giving us\nno ability to deploy and scale infrastru
1cture focussed on tackling this\nvery particular type of high CPU load separate from the infrastructure\nrequiring it. It was an all or nothing, scale up all our services, or\nfall behind."}),"\n",(0,s.jsx)(t,{depth:2,id:"wait-so-what-does-it-do",children:"Wait, so what does it do?"}),"\n",(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.em,{children:"Compute Service"})," performs two tasks."]}),"\n",(0,s.jsxs)(n.p,{children:["Firstly, it hashes passwords with the\n",(0,s.jsx)(n.a,{href:"http://bcrypt.sourceforge.net/",children:"BCrypt"})," encryption utility. ",(0,s.jsx)(n.em,{children:"BCrypt"})," wraps\n",(0,s.jsx)(n.em,{children:"Blowfish"}),", an (intentionally) CPU and time intensive hashing function."]}),"\n",(0,s.jsxs)(n.p,{children:["Secondly, it signs temporary URLs that reference private media. We\nheavily leverage CDN services to efficiently distribute media to our\nusers. In the case of private media, (eg. images uploaded into a user's\nprivate image library), we craft and sign special URLs that include an\nexpiration time, after which our CDN provider will not serve any content\non that URL. We do this as to ensure we don't have references to private\nmedia floating around the internet indefinitely. URLs are signed with a\n",(0,s.jsx)(n.em,{children:"SHA1"})," hash, which is an order of magnitude less time and CPU intensive\nto calculate than as is with ",(0,s.jsx)(n.em,{children:"Blowfish"}),", however we sign an awful lot of\nthese URLs."]}),"\n",(0,s.jsxs)(n.p,{children:["It should be noted that the ",(0,s.jsx)(n.em,{children:"Compute Service"})," is designed to handle CPU\ndemanding functions that are synchronous, or inlined into a single\nrequest. For other computationally demanding tasks, such as the export\nof Canva designs, we place that work on a queue for asynchronous\nprocessing and result retrieval at a point in the future. For tasks such\nas hashing a password during a new user signup, this sort of\nasynchronous processing is not available to us."]}),"\n",(0,s.jsx)(t,{depth:2,id:"so-howd-you-do-it",children:"So how'd you do it"}),"\n",(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.em,{children:"Compute Service"})," is comprised of a cluster of new ",(0,s.jsx)(n.em,{children:"Amazon EC2"}),"\ninstances, fronted by an ",(0,s.jsx)(n.em,{children:"Amazon Elastic Load Balancer"}),". These instances\nserve three main RPCs: one for hashing a password, one for checking a\npassword, and one for computing signed URLs for a batch of private\nmedia."]}),"\n",(0,s.jsx)(t,{depth:2,id:"the-end-goal",children:"The end goal"}),"\n",(0,s.jsxs)(n.p,{children:["We had three goals for the ",(0,s.jsx)(n.em,{children:"Compute Service"}),":"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Graceful failure"}),": In the event of the ",(0,s.jsx)(n.em,{children:"Compute\nService"})," becoming unavailable, we wanted the rest of our systems to\nremain as functional as possible, albeit degraded."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"The right tool for the right job"}),": The ",(0,s.jsx)(n.em,{children:"Compute\nService"})," performs a very particular set of tasks that are CPU\nintensive, rather than being memory or I/O bound. As this service\nwould run as its own dedicated autoscaling group, separate from the\nfive other autoscaling groups that run the rest of Canva's backend\nservices, we had the opportunity to choose an EC2 instance type\nspecifically tailored for these kinds of tasks."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"#cloud"}),": We wanted to be able to raise and lower\nthe capacity of the ",(0,s.jsx)(n.em,{children:"Compute Service"})," independently from the needs of\nthe rest of our backend services."]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["We have also recently released features as part of ",(0,s.jsx)(n.em,{children:"Canva for Work"}),",\nsuch as ",(0,s.jsx)(n.em,{children:"Photo Folders"}),", that require efficient signing of (potentially\nlarge) batches of private media URLs. We wanted to leverage the inherent\nparallelizability of these batch signing tasks by distributing them\nacross the multiple cores of each instance with the goal of reducing the\naverage latency of these requests."]}
1),"\n",(0,s.jsx)(t,{depth:2,id:"microservices",children:"Microservices"}),"\n",(0,s.jsxs)(n.p,{children:["The design for the ",(0,s.jsx)(n.em,{children:"Compute Service"})," essentially boils down to that as\ndescribed by the\n",(0,s.jsx)(n.a,{href:"http://martinfowler.com/articles/microservices.html",children:"Microservice Architecture pattern"}),".\n",(0,s.jsx)(n.em,{children:"Microservice Architectures"}),", as opposed to ",(0,s.jsx)(n.em,{children:"Monolithic Architectures"}),",\nare those where functionally distinct software components are also\ndeployed on separate machines or clusters. Communication between these\nMicroservices is generally over a RESTful HTTP API or some form of RPC,\nallowing for individual components to be written in different languages,\nand have different deployment cycles, as well as the advantages listed\nabove."]}),"\n",(0,s.jsx)(t,{depth:2,id:"the-right-tool-for-the-job",children:"The right tool for the job"}),"\n",(0,s.jsxs)(n.p,{children:["It takes a cursory glance at ",(0,s.jsx)(n.a,{href:"http://www.ec2instances.info/",children:"www.ec2instances.info"})," to see that the modern day\nengineer is blessed with a lot of choices when it comes to ",(0,s.jsx)(n.em,{children:"Amazon EC2"}),"\ninstance types. For the ",(0,s.jsx)(n.em,{children:"Compute Service"}),", we narrowed down our choices\nto the ",(0,s.jsx)(n.em,{children:"t2.micro"})," type and the ",(0,s.jsx)(n.em,{children:"c4.8xlarge"})," type. The ",(0,s.jsx)(n.em,{children:"t2.micro"})," is\nAmazon's most underpowered, but also cheapest instance type. We were\ncurious about how well an army of ",(0,s.jsx)(n.em,{children:"t2.micro"})," instances might perform,\nand how cost effective it would be. As a point of comparison, we\ncompared against the performance of a behemoth ",(0,s.jsx)(n.em,{children:"c4.8xlarge"})," instance,\nweighing in at 132 ECUs across 36 cores. For reference, ECU refers to\n",(0,s.jsx)(n.em,{children:"equivalent compute unit"}),", Amazon's standardised measure of clock speed\nacross all cores of an instance. The ",(0,s.jsx)(n.em,{children:"c4.8xlarge"})," comes from Amazon's\n",(0,s.jsx)(n.em,{children:'"compute"'})," category of instances, and is the beefiest therein. Somewhat\nunsurprisingly, engineering prevails, and we settled on neither of the\ntwo, and instead found a compromise with the ",(0,s.jsx)(n.em,{children:"c4.large"})," type."]}),"\n",(0,s.jsxs)(n.p,{children:["We ruled out the ",(0,s.jsx)(n.em,{children:"t2.micro"})," by examining password hashing performance.\nAs you are unable to distribute a single password hashing task across\nmultiple instances, each password will end up on a single ",(0,s.jsx)(n.em,{children:"t2.micro"}),"\ncore, each of which is around four times slower than each core on\nAmazon's ",(0,s.jsx)(n.em,{children:'"compute"'})," type instances. Using ",(0,s.jsx)(n.em,{children:"t2.micros"})," would have given\nus finer grain control over machine utilisation, but at the cost of\nrequest latency for password related operations. To compare the\ndifferent instance types, we measured the performance on an ",(0,s.jsx)(n.em,{children:"m3.medium"}),",\nand assumed the latency of the password hashing task would scale\ninversely with the number of ECUs per core on each instance type:"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(i,{src:o,alt:""})}),"\n",(0,s.jsxs)(n.p,{children:["We then compared the batch URL signing performance across the\n",(0,s.jsx)(n.em,{children:'"compute"'})," instance types. Again, we estimated the performance on each\ninstance type by assuming that the latency of signing a batch of URLs\nwould scale inversely with the number of ECUs per core and the number of\ncores:"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(i,{src:c,alt:""})}),"\n",(0,s.jsxs)(n.p,{children:["The latency of URL signing is significantly reduced when undertaken by\nthe ",(0,s.jsx)(n.em,{children:'"compute"'})," type machines, as compared to the ",(0,s.jsx)(n.em,{children:"m3.medium"})," machines\n(that handle the vast majority of Canva's general purpose load). This is\nespecially apparent with the larger types which provide a greater number\nof cores per machine, across which the batch signing tasks are\ndistributed. It should be noted that Amazon's pricing scheme for these\ninstances is linearly proportional to the ECU of each type. This means\nthat deploying eight ",(0,s.jsx)(n.em,{children:"c4.large"})," instances (at 8 ECU each) delivers\napproximately the same raw computational power, at approximately the\nsame price, as a single ",(0,s.jsx)(n.em,{children:"c4.4xlarge"})," at 62 ECU, albeit with a different\nhierarchy of machines and cores."]}),"\n",(0,s.jsxs)(n.p,{children:["Examining existing traffic, we estimated that we only require\napproximately 6 ECU's of peak compute capacity to handle URL signing and\npassword hashing tasks in Canva's immediate future. As such, we settled\non deploying a single ",(0,s.jsx)(n.em,{children:"c4.large"})," instance, which provides 8 ECU's of\ncapacity. We underestimated this (within an order of magnitude), and\ncurrently run between two and four ",(0,s.jsx)(n.em,{children:"c4.large"})," instances to handle\ncompute tasks. Several assumptions in our modelling contributed to this\nmisestimation. For example, we made the assumption that computational\ntasks could be perfectly distributed across multiple workers, when in\nfact there are diminishing returns when increasing the parallelism of\nany system. Errors were also introduced through the estimation of model\ninputs such as the benchmark time taken to hash passwords and sign\nprivate URLs on ",(0,s.jsx)(n.em,{children:"m3.medium"})," instances. These errors in estimation and\nassumption compound, adding to the misestimation in overall required\ncapacity. We also based our calculations on historical average hourly\nthroughput at peak times, which is less volatile than our\nsecond-to-second throughput. To ensure that we always have enough\ncapacity to handle any spikes in this instantaneous peak demand (with\nsome capacity to spare) we like to have auto-scaling policies that err\non the side of over-deploying rather than under-deploying instances."]}),"\n",(0,s.jsx)(n.p,{children:"As we continue to grow, we look forward to exploring the use of larger\ncompute type instances. The various compute instance types scale up\nlinearly across capacity and price, however the larger types offer more\ncores per machine. This hints at opportunities to further reduce latency\nthrough increased parallelization of each request once we grow into the\nshoes of those beefier instance types."}
1),"\n",(0,s.jsx)(t,{depth:2,id:"results",children:"Results"}),"\n",(0,s.jsxs)(n.p,{children:["We undertook load testing of our folder service (which delegates a large\namount of computational work to the ",(0,s.jsx)(n.em,{children:"Compute Service"}),") with the ",(0,s.jsx)(n.em,{children:"Compute\nService"})," initially disabled and then enabled with a single instance in\norder to understand the effectiveness of delegating those tasks to\nseparate instances. The following graph demonstrates two bursts of\ntraffic to the folder service, first with the ",(0,s.jsx)(n.em,{children:"Compute Service"}),"\ndisabled, and second with the ",(0,s.jsx)(n.em,{children:"Compute Service"})," enabled. As can be seen,\nthe request volume is approximately equal over the same approximate time\nperiod in both tests."]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(i,{src:h,alt:""})}),"\n",(0,s.jsxs)(n.p,{children:["Examining the folder service latency (the middle graph), we can see that\nenabling the ",(0,s.jsx)(n.em,{children:"Compute Service"})," successfully reduces latency on folder\nrequests by approximately half. This correlates with the two-fold\nparallelisation provided by the ",(0,s.jsx)(n.em,{children:"c4.large"})," instances (with a little bit\nof inefficiency a la\n",(0,s.jsx)(n.a,{href:"https://en.wikipedia.org/wiki/Amdahl%27s_law",children:"Ahmdal's law"}),"), as compared with the single core\n",(0,s.jsx)(n.em,{children:"m3.medium"}),"."]}),"\n",(0,s.jsxs)(n.p,{children:["Before delving into the document service latency (the last graph) I\nshould explain that document service is largely independent of the\n",(0,s.jsx)(n.em,{children:"Compute Service"}),". It is, however, served from the same instances that\nmanage the folder service. With that in mind, you may notice the point\nof interest. With CDN URLs being signed locally, the burst of load on\nthe folder service propagated across multiple services, even if those\nservices are not dependant on those functions delegated to the ",(0,s.jsx)(n.em,{children:"Compute\nService"}),". This reduced coupling between services gives us greater\noverall systematic resiliency across Canva's backend."]})]})}function d(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(l,{...e})}):l(e)}function m(e,n){throw Error("Expected "+(n?"component":"object")+" `"+e+"` to be defined: you likely forgot to import, pass, or provide it.")}
1let u={title:"Canva's compute backend",abstract:"Developing the Compute Service, aimed at the increasing processing burden of password hashing and the signing of private CDN URLs.",image:"./images/thumbnail.png",publishedDate:new Date(14422788e5),state:"live",tags:["Performance"],authors:"[email protected]",post:{title:"Canva's compute backend",abstract:"Developing the Compute Service, aimed at the increasing processing burden of password hashing and the signing of private CDN URLs.",image:"engineering/canvas-compute-backend/images/thumbnail.png",publishedDate:14422788e5,state:"live",tags:["Performance"],authors:[{name:"Sam Killin",url:"https://au.linkedin.com/in/samuelkillin"}],category:"Performance",slug:"/blog/engineering/canvas-compute-backend/"},otherPosts:[{title:"Trust but test: Vendor security testing at Canva",abstract:"How we validate vendor security at Canva by going beyond compliance.",publishedDate:1710028801e3,image:"engineering/trust-but-test/images/thumbnail.jpg",state:"live",tags:["Security","TPRM","Vendor Security","Penetration Testing"],authors:[{name:"Kane Narraway",url:"https://www.linkedin.com/in/kane-n/",photo:"https://avatars.slack-edge.com/2024-06-04/7217367716323_2bb01de708b06933beeb_192.jpg"},{name:"CJ Fairhead",url:"https://www.linkedin.com/in/chris-fairhead/"}],category:"Security",slug:"/blog/engineering/trust-but-test/"},{title:"How to design in every language at once",abstract:"The technology that powers Continuous Localization at Canva.",image:"engineering/how-to-design-in-every-language-at-once/images/thumbnail.png",publishedDate:0x17c9b0a39b1,state:"live",tags:["Localization","Design","Language Translation","UI/UX","Infrastructure"],authors:[{name:"Minh Cung",url:"https://www.linkedin.com/in/minhcung/"},{name:"Simon Hammond",url:"https://www.linkedin.com/in/simon-hammond-990250209/",photo:"https://avatars.slack-edge.com/2025-04-03/8709210079537_da9840abab8d16912406_192.png"}],category:"Localization",slug:"/blog/engineering/how-to-design-in-every-language-at-once/"},{title:"Deep Learning for Infinite (Multi-Lingual) Keywords",abstract:"How we used a CLIP-inspired model to suggest keywords for template labeling in multiple languages.",image:"engineering/deep-learning-for-infinite-multi-lingual-keywords/images/thumbnail.png",publishedDate:0x185e600d247,state:"live",tags:["Machine Learning","Deep Learning","NLP","Recommendations","Contrastive Learning"],authors:[{name:"Sachinthaka Abeywardana",url:"https://www.linkedin.com/in/sachinabeywardana/",photo:"https://avatars.slack-edge.com/2025-12-08/10100687813488_7cbe837347e64364e2fa_192.png"}],category:"Machine Learning",slug:"/blog/engineering/deep-learning-for-infinite-multi-lingual-keywords/"}]};d.getLayout=e=>(0,s.jsx)(i.Q,{children:(0,s.jsx)(a.L,{...u,children:e})})}},e=>{e.O(0,[33096,39058,38460,87726,11886,75319,84624,77411,88586,18716,38456,90252,31965,54047,62156,86664,36492,13206,85803,90636,46593,38792],()=>e(e.s=74182)),_N_E=e.O()}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.