CINXE.COM
<!DOCTYPE html><html><head><meta charSet="utf-8"/><meta http-equiv="x-ua-compatible" content="ie=edge"/><meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/><style id="typography.js">html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block}audio:not([controls]){display:none;height:0}progress{vertical-align:baseline}[hidden],template{display:none}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit;font-weight:bolder}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}svg:not(:root){overflow:hidden}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}figure{margin:1em 40px}hr{box-sizing:content-box;height:0;overflow:visible}button,input,optgroup,select,textarea{font:inherit;margin:0}optgroup{font-weight:700}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-input-placeholder{color:inherit;opacity:.54}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}html{font:112.5%/1.722 'Lato',sans-serif;box-sizing:border-box;overflow-y:scroll;}*{box-sizing:inherit;}*:before{box-sizing:inherit;}*:after{box-sizing:inherit;}body{color:hsla(0,0%,0%,0.8);font-family:'Lato',sans-serif;font-weight:400;word-wrap:break-word;font-kerning:normal;-moz-font-feature-settings:"kern", "liga", "clig", "calt";-ms-font-feature-settings:"kern", "liga", "clig", "calt";-webkit-font-feature-settings:"kern", "liga", "clig", "calt";font-feature-settings:"kern", "liga", "clig", "calt";}img{max-width:100%;margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.722rem;}h1{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.722rem;color:hsla(0,0%,0%,0.9);font-family:'Neuton',sans-serif;font-weight:700;text-rendering:optimizeLegibility;font-size:2rem;line-height:1.1;}h2{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.722rem;color:hsla(0,0%,0%,0.9);font-family:'Neuton',sans-serif;font-weight:700;text-rendering:optimizeLegibility;font-size:1.51572rem;line-height:1.1;}h3{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.722rem;color:hsla(0,0%,0%,0.9);font-family:'Neuton',sans-serif;font-weight:700;text-rendering:optimizeLegibility;font-size:1.31951rem;line-height:1.1;}h4{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.722rem;color:hsla(0,0%,0%,0.9);font-family:'Neuton',sans-serif;font-weight:700;text-rendering:optimizeLegibility;font-size:1rem;line-height:1.1;}h5{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.722rem;color:hsla(0,0%,0%,0.9);font-family:'Neuton',sans-serif;font-weight:700;text-rendering:optimizeLegibility;font-size:0.87055rem;line-height:1.1;}h6{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.722rem;color:hsla(0,0%,0%,0.9);font-family:'Neuton',sans-serif;font-weight:700;text-rendering:optimizeLegibility;font-size:0.81225rem;line-height:1.1;}hgroup{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.722rem;}ul{margin-left:1.722rem;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.722rem;list-style-position:outside;list-style-image:none;}ol{margin-left:1.722rem;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.722rem;list-style-position:outside;list-style-image:none;}dl{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.722rem;}dd{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.722rem;}p{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.722rem;}figure{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.722rem;}pre{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.722rem;font-size:0.85rem;line-height:1.722rem;}table{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.722rem;font-size:1rem;line-height:1.722rem;border-collapse:collapse;width:100%;}fieldset{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.722rem;}blockquote{margin-left:0;margin-right:1.722rem;margin-top:0;padding-bottom:0;padding-left:1.93725rem;padding-right:0;padding-top:0;margin-bottom:1.722rem;font-size:1.1487rem;line-height:1.722rem;color:hsla(0,0%,0%,0.59);border-left:0.64575rem solid;border-color:#612423;}form{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.722rem;}noscript{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.722rem;}iframe{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.722rem;}hr{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:calc(1.722rem - 1px);background:hsla(0,0%,0%,0.2);border:none;height:1px;}address{margin-left:0;margin-right:0;margin-top:0;padding-bottom:0;padding-left:0;padding-right:0;padding-top:0;margin-bottom:1.722rem;}b{font-weight:700;}strong{font-weight:700;}dt{font-weight:700;}th{font-weight:700;}li{margin-bottom:calc(1.722rem / 2);}ol li{padding-left:0;}ul li{padding-left:0;}li > ol{margin-left:1.722rem;margin-bottom:calc(1.722rem / 2);margin-top:calc(1.722rem / 2);}li > ul{margin-left:1.722rem;margin-bottom:calc(1.722rem / 2);margin-top:calc(1.722rem / 2);}blockquote *:last-child{margin-bottom:0;}li *:last-child{margin-bottom:0;}p *:last-child{margin-bottom:0;}li > p{margin-bottom:calc(1.722rem / 2);}code{font-size:0.85rem;line-height:1.722rem;}kbd{font-size:0.85rem;line-height:1.722rem;}samp{font-size:0.85rem;line-height:1.722rem;}abbr{border-bottom:1px dotted hsla(0,0%,0%,0.5);cursor:help;}acronym{border-bottom:1px dotted hsla(0,0%,0%,0.5);cursor:help;}abbr[title]{border-bottom:1px dotted hsla(0,0%,0%,0.5);cursor:help;text-decoration:none;}thead{text-align:left;}td,th{text-align:left;border-bottom:1px solid hsla(0,0%,0%,0.12);font-feature-settings:"tnum";-moz-font-feature-settings:"tnum";-ms-font-feature-settings:"tnum";-webkit-font-feature-settings:"tnum";padding-left:1.148rem;padding-right:1.148rem;padding-top:0.861rem;padding-bottom:calc(0.861rem - 1px);}th:first-child,td:first-child{padding-left:0;}th:last-child,td:last-child{padding-right:0;}a{color:#4665b7;text-decoration:none;}a:hover,a:active{color:hsla(0,0%,0%,0.8);}h1,h2,h3,h4,h5,h6{margin-top:3.444rem;}blockquote > :last-child{margin-bottom:0;}blockquote cite{font-size:1rem;line-height:1.722rem;color:hsla(0,0%,0%,0.8);font-weight:400;}blockquote cite:before{content:"— ";}@media only screen and (max-width:480px){blockquote{margin-left:-1.2915rem;margin-right:0;border-left:0.32288rem solid;border-color:#612423;padding-left:0.96862rem;}}</style><style data-href="/styles.6661cafb0f80a69e6cc3.css">footer{background-color:rgba(0,0,0,.9);padding:20px 0 40px;color:hsla(0,0%,100%,.7)}footer .Footer-module--container--3Dvn1{display:grid;grid-template-columns:50% 50%;margin-left:auto;margin-right:auto;max-width:50rem;padding:20px}footer a{color:#ddd;margin:10px}footer a:hover{color:#fff}footer .Footer-module--copyright--2uglW{text-align:right}header{display:flex;justify-content:space-between;align-items:center;padding:1rem;position:-webkit-sticky;position:sticky;top:0;background:hsla(0,0%,100%,.75);-webkit-backdrop-filter:blur(7px);backdrop-filter:blur(7px);z-index:9;margin-left:auto;margin-right:auto;height:70px}header a{text-decoration:none;color:#3c4856;font-size:1.1rem;margin-top:0}header h1{margin-top:5px}header nav a{color:#757575;transition:color .2s ease;text-decoration:none;display:inline-block;position:relative;font-size:16px}header nav a:hover{color:#4f647d}header nav a strong{margin:1px;color:#4f647d;font-size:.8rem}header .Header-module--activeNav--25noA{color:#4f647d;font-weight:700}header .Header-module--we-are-hiring--3Vkey span{color:red}.Header-module--brand--nQIOf{margin-top:40px;display:flex;flex-direction:row;flex-wrap:wrap}.Header-module--main-nav--2C0n8{display:flex;list-style:none;padding:0;margin-top:30px;overflow:auto;overflow:-moz-scrollbars-none;scrollbar-width:none;-ms-overflow-style:none!important}.Header-module--main-nav--2C0n8::-webkit-scrollbar{width:0!important}.Header-module--main-nav--2C0n8 li{font-size:1.3rem}.Header-module--main-nav--2C0n8 li+li{margin-left:1.5rem}@media (max-width:1000px){header{display:block;height:120px}.Header-module--brand--nQIOf{margin-top:0;margin-bottom:0}nav{margin-top:-50px}nav ul{margin:0}}@media (max-width:768px){header strong{display:none}.Header-module--main-nav--2C0n8 li+li{margin-left:1rem}}@media (max-width:520px){header nav a{font-size:.7rem;border:1px solid #eee;padding:2px;border-radius:5px}}body{margin:0;background:#fafbfb}h1,h2,h3,h4,h5,h6{margin-top:1rem}main{margin-left:auto;margin-right:auto;max-width:50rem;display:flex;flex-direction:column;min-height:100vh;padding:20px}article{height:90%}article:hover{box-shadow:0 11px 22px 12px #eee;border-radius:5px}.pill{display:inline-block;height:24px;padding:0 10px;margin-left:5px;margin-right:5px;margin-bottom:10px;color:#929fb3;text-transform:uppercase;font-size:12px;line-height:22px;border-radius:2px;border:1px solid}code.language-text{background:#d4d4d4!important;color:#000!important;font-family:monospace!important;text-shadow:none!important;padding-left:5px!important;padding-right:5px!important}.gatsby-highlight{font-size:14px}.projects{text-align:center}.project-box{float:left;width:50%;padding:20px}.code-latex{padding:1em;margin:.5em 0;overflow:auto;border-radius:.3em;color:#f8f8f2;background:#272822}.MJXc-display{text-align:initial!important}@media (max-width:600px){.project-box{width:100%}}.PostsListing-module--article-list--3ReSK{display:grid;grid-template-columns:50% 50%}.PostsListing-module--article-list--3ReSK .PostsListing-module--article-box--3M6_I{margin-bottom:30px;display:inline-block;width:auto;transition:all .25s ease;padding-top:20px}.PostsListing-module--article-list--3ReSK .PostsListing-module--article-box--3M6_I .PostsListing-module--author-image--1wgum{width:30px;height:30px;display:inline-block;border-radius:15px;margin-bottom:0}.PostsListing-module--article-list--3ReSK .PostsListing-module--article-box--3M6_I .PostsListing-module--author-description--uwweV{display:inline-block;margin-left:10px}.PostsListing-module--article-list--3ReSK .PostsListing-module--article-box--3M6_I .PostsListing-module--thumbnail-container--c94Mm{height:200px;overflow:hidden;text-align:center;display:flex;align-items:center;justify-content:center;background:#ececec}.PostsListing-module--article-list--3ReSK .PostsListing-module--article-box--3M6_I .PostsListing-module--thumbnail-container--c94Mm img{margin-bottom:0}.PostsListing-module--article-list--3ReSK .PostsListing-module--article-box--3M6_I .PostsListing-module--author-name--3k7M0{margin-top:0;margin-bottom:0}.PostsListing-module--article-list--3ReSK .PostsListing-module--article-box--3M6_I:hover{cursor:pointer}.PostsListing-module--article-list--3ReSK .PostsListing-module--article-box--3M6_I .PostsListing-module--right--2MxCO{padding:15px;color:#3c4856;margin-top:-20px}.PostsListing-module--article-list--3ReSK .PostsListing-module--article-box--3M6_I .PostsListing-module--right--2MxCO .PostsListing-module--meta--3cFzL{margin-bottom:20px;font-size:.7rem}@media (max-width:768px){.PostsListing-module--article-list--3ReSK{grid-template-columns:100%}}.Bio-module--avatar--v_AB0{border-radius:50%;width:70px;float:left;margin:5px 20px}.PostTags-module--tag-container--ksSgQ a{text-decoration:none;display:inline-grid}.PostTags-module--tag-container--ksSgQ span{font-size:.8rem;font-weight:500;padding:.3rem .6rem;margin:.3rem;border-radius:3px;background:rgba(141,154,169,.21);color:rgba(0,0,0,.54)}.SocialLinks-module--social-links--1RDpJ{position:fixed;top:100px;left:30px;flex-direction:row;flex-wrap:wrap;justify-content:center;align-content:center;align-items:center;margin:15px 0}.SocialLinks-module--social-links--1RDpJ div{margin:5px 15px;cursor:pointer}.SocialLinks-module--share-count--3wXKp{text-align:center}@media (max-width:1200px){.SocialLinks-module--social-links--1RDpJ{position:relative;top:0}.SocialLinks-module--social-links--1RDpJ div{display:inline-grid;position:relative}}.post-module--post-meta--1ci95{color:#757575;margin-bottom:.5rem;font-size:.7rem}.post-module--pagination--1F-V0{display:flex;flex-wrap:wrap;justify-content:space-between;list-style:none;padding:0;margin-top:4rem}code[class*=language-],pre[class*=language-]{color:#f8f8f2;background:none;text-shadow:0 1px rgba(0,0,0,.3);font-family:Consolas,Monaco,Andale Mono,Ubuntu Mono,monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto;border-radius:.3em}:not(pre)>code[class*=language-],pre[class*=language-]{background:#272822}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#f8f8f2}.namespace{opacity:.7}.token.constant,.token.deleted,.token.property,.token.symbol,.token.tag{color:#f92672}.token.boolean,.token.number{color:#ae81ff}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#a6e22e}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url,.token.variable{color:#f8f8f2}.token.atrule,.token.attr-value,.token.class-name,.token.function{color:#e6db74}.token.keyword{color:#66d9ef}.token.important,.token.regex{color:#fd971f}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}</style><meta name="generator" content="Gatsby 2.22.11"/><title data-react-helmet="true">A lua-nginx Client for Pub/Sub | Wingify Engineering</title><link data-react-helmet="true" rel="icon" type="image/png" href="/images/favicon.png"/><meta data-react-helmet="true" name="description" content="Introduction Lua as a part of the OpenResty package, is extensively used in our in-house Dynamic CDN (DACDN) module. CDN generally is used…"/><meta data-react-helmet="true" name="image" content=""/><meta data-react-helmet="true" property="og:url" content="https://engineering.wingify.com//posts/a-lua-nginx-client-for-pubsub/"/><meta data-react-helmet="true" property="og:type" content="article"/><meta data-react-helmet="true" property="og:title" content="A lua-nginx Client for Pub/Sub"/><meta data-react-helmet="true" property="og:description" content="Introduction Lua as a part of the OpenResty package, is extensively used in our in-house Dynamic CDN (DACDN) module. CDN generally is used…"/><meta data-react-helmet="true" property="og:image" content=""/><meta data-react-helmet="true" property="fb:app_id" content=""/><meta data-react-helmet="true" name="twitter:card" content="summary_large_image"/><meta data-react-helmet="true" name="twitter:creator" content="wingify_engg"/><meta data-react-helmet="true" name="twitter:title" content="A lua-nginx Client for Pub/Sub"/><meta data-react-helmet="true" name="twitter:description" content="Introduction Lua as a part of the OpenResty package, is extensively used in our in-house Dynamic CDN (DACDN) module. CDN generally is used…"/><meta data-react-helmet="true" name="twitter:image" content=""/><script data-react-helmet="true" type="application/ld+json">[{"@context":"http://schema.org","@type":"WebSite","url":"https://engineering.wingify.com/","name":"A lua-nginx Client for Pub/Sub","alternateName":"Wingify Engineering - Blog"},{"@context":"http://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"https://engineering.wingify.com//posts/a-lua-nginx-client-for-pubsub/","name":"A lua-nginx Client for Pub/Sub","image":""}}]},{"@context":"http://schema.org","@type":"BlogPosting","url":"https://engineering.wingify.com/","name":"A lua-nginx Client for Pub/Sub","alternateName":"Wingify Engineering - Blog","headline":"A lua-nginx Client for Pub/Sub","image":{"@type":"ImageObject","url":""},"description":"Introduction Lua as a part of the OpenResty package, is extensively used in our in-house Dynamic CDN (DACDN) module. CDN generally is used…"}]</script><link href="//fonts.googleapis.com/css?family=Neuton:700|Lato:400,400i,700" rel="stylesheet" type="text/css"/><style type="text/css"> .anchor.before { position: absolute; top: 0; left: 0; transform: translateX(-100%); padding-right: 4px; } .anchor.after { display: inline-block; padding-left: 4px; } h1 .anchor svg, h2 .anchor svg, h3 .anchor svg, h4 .anchor svg, h5 .anchor svg, h6 .anchor svg { visibility: hidden; } h1:hover .anchor svg, h2:hover .anchor svg, h3:hover .anchor svg, h4:hover .anchor svg, h5:hover .anchor svg, h6:hover .anchor svg, h1 .anchor:focus svg, h2 .anchor:focus svg, h3 .anchor:focus svg, h4 .anchor:focus svg, h5 .anchor:focus svg, h6 .anchor:focus svg { visibility: visible; } </style><script> document.addEventListener("DOMContentLoaded", function(event) { var hash = window.decodeURI(location.hash.replace('#', '')) if (hash !== '') { var element = document.getElementById(hash) if (element) { var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop var clientTop = document.documentElement.clientTop || document.body.clientTop || 0 var offset = element.getBoundingClientRect().top + scrollTop - clientTop // Wait for the browser to finish rendering before scrolling. setTimeout((function() { window.scrollTo(0, offset - 0) }), 0) } } }) </script><script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= 'https://www.googletagmanager.com/gtm.js?id='+i+dl+'';f.parentNode.insertBefore(j,f); })(window,document,'script','dataLayer', 'GTM-T9CS925');</script><link rel="sitemap" type="application/xml" href="/sitemap.xml"/><link rel="manifest" href="/manifest.webmanifest"/><meta name="theme-color" content="#c62828"/><link rel="apple-touch-icon" sizes="48x48" href="/images/favicon.png"/><link rel="alternate" type="application/rss+xml" title="/atom.xml" href="/atom.xml"/><link as="script" rel="preload" href="/webpack-runtime-e67a7c97db3322d008c9.js"/><link as="script" rel="preload" href="/framework-9fe058f4359556db0d38.js"/><link as="script" rel="preload" href="/app-99ec7086382d867c7c8c.js"/><link as="script" rel="preload" href="/styles-823ae8103e36ae8a7f9f.js"/><link as="script" rel="preload" href="/29107295-b32bd7b51275495551b1.js"/><link as="script" rel="preload" href="/6b3c31c87dfb891be24ad1723dc89e8083e2fee1-3aeae6b2992d33209c1a.js"/><link as="script" rel="preload" href="/component---src-templates-post-js-86eb8f5f76ee576dcf22.js"/><link as="fetch" rel="preload" href="/page-data/posts/a-lua-nginx-client-for-pubsub/page-data.json" crossorigin="anonymous"/><link as="fetch" rel="preload" href="/page-data/app-data.json" crossorigin="anonymous"/></head><body><noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-T9CS925" height="0" width="0" style="display: none; visibility: hidden"></iframe></noscript><div id="___gatsby"><div style="outline:none" tabindex="-1" id="gatsby-focus-wrapper"><header><h1><a href="/"><span class="Header-module--brand--nQIOf"><img src="https://wingify.com/wp-content/themes/wingify/images/labs/engg_blog.png" width="30px" height="30px" alt="Wingify Engineering"/><span style="margin-top:5px;margin-left:15px">Wingify Engineering</span></span></a></h1><nav><ul class="Header-module--main-nav--2C0n8"><li><a href="/">Posts</a></li><li><a href="/labs">Labs</a></li><li><a href="/about">About</a></li><li><a href="https://github.com/wingify">Github</a></li><li><a href="/atom.xml">Feed</a></li><li><a href="https://wingify.com/careers/" class="Header-module--we-are-hiring--3Vkey" target="_blank"><span>We are Hiring</span></a></li></ul></nav></header><main><div><h1>A lua-nginx Client for Pub/Sub</h1><img class="Bio-module--avatar--v_AB0" src="/images/team/vasu_gupta.png" alt="Vasu Gupta"/><p>Written by <strong>Vasu Gupta</strong> <div></div></p><p class="post-module--post-meta--1ci95">December 04, 2020<!-- --> — <!-- -->8<!-- --> Min Read<!-- --> <!-- --> | <span class="disqus-comment-count" data-disqus-url="https://engineering.wingify.com/posts/a-lua-nginx-client-for-pubsub/">...</span></p><div><h2 id="introduction" style="position:relative;"><a href="#introduction" aria-label="introduction permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Introduction</h2> <p>Lua as a part of the OpenResty package, is extensively used in our in-house <a href="https://engineering.wingify.com/posts/dynamic-cdn/">Dynamic CDN (DACDN)</a> module. CDN generally is used for quick content delivery but our in-house CDN works both as content delivery as well as a data acquisition service for gathering copious amounts of data of our client's visitors. Since the collection of data at a high throughput requires a sophisticated queue mechanism, therefore, one of the core parts of our DACDN is to publish packets to <a href="https://cloud.google.com/pubsub/docs/overview">Google Cloud Pub/Sub</a>. </p> <p>Pub/Sub lets any number of publishers publish data, ideally to a topic, which could be subscribed to by any number of subscribers. This messaging queue was an ideal choice for us since it can accept publish throughput up to 200 MB/s and subscriber throughput up to 400 MB/s. It can retain unacknowledged data for 7 days, can provide reliability through application-level acknowledgments, and is based on “at-least-once” delivery semantics. Pub/Sub also comes with an ability to store attributes which is for storing metadata of a payload in a key-value format. Some other perks we enjoyed with Pub/Sub are:</p> <ul> <li><strong>Global availability:</strong> Pub/Sub acts as a global service and is available in all Google Cloud Zones; transferring data between our data centers wouldn’t be through our regular internet provider but would use the underlying Google network.</li> <li><strong>A simple REST API:</strong> Since there is no library for Pub/Sub in Lua, we can quickly write our own publisher/subscriber by using their REST APIs.</li> <li><strong>Self Managed:</strong> There was no need to create a capacity model or deployment strategy or to set up monitoring and alerting.</li> </ul> <h2 id="problem" style="position:relative;"><a href="#problem" aria-label="problem permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Problem</h2> <p>The challenge arises when the lua-nginx module by default does not provide a way to push messages to Pub/Sub. Google Pub/Sub provides support for executing HTTPS requests for publishing messages. Still, Lua out of the box does not provide an interface for making external HTTP requests. So, there is a well-maintained library called <a href="https://github.com/ledgetech/lua-resty-http">lua-resty-http</a> which helps us for doing the same. Using this library still doesn’t fix our problems completely. We need to come up with a solution that can handle a throughput of 30k requests/sec while maintaining non-blocking behavior. For handling such high throughput we can’t just pick one packet and execute a new HTTP request for the same, this would be highly unoptimized which would result in an increase in response time. Hence, we need to design a solution that could meet our requirements.</p> <h2 id="approaches" style="position:relative;"><a href="#approaches" aria-label="approaches permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Approaches</h2> <ol> <li> <p><strong>Brute-force Approach</strong></p> <ul> <li>For every request we receive at the DACDN end, we can form a payload out of it and then can execute HTTP requests for each of them separately. Though this approach is easy to Implement and highly intuitive, a highly blocking behavior will be experienced at the request sender's end, thereby increasing response time. High machine configs with more workers can overcome this, but it will ultimately lead to a higher system cost. Also, as per this <a href="https://cloud.google.com/pubsub/pricing#message_ingestion_and_delivery">Google Doc</a>: “A minimum of 1000 bytes per publish, push or pull request is assessed regardless of message size.” This implies that messages smaller than 1000 bytes are still billed for a whole 1000 bytes therefore also being cost-ineffective.</li> </ul> </li> <li> <p><strong>Optimized Approach</strong></p> <ol> <li> <p>Techniques that work for us in optimizing the publish rate</p> <ol> <li> <p><strong>Data Batching at Worker level using timers:</strong> This methodology reduced our response time by 3x when compared to publishing messages without it. Even this <a href="https://cloud.google.com/pubsub/pricing#message_ingestion_and_delivery">Google Doc</a> advises pushing bulk data in publish requests for reducing cost. The idea is pretty simple but immensely effective. Steps are as follows:</p> <ol> <li>Accept the incoming request and form a data packet out of it</li> <li>Add this data packet in a buffer which is nothing but a Lua table that is used to hold packets for a while</li> <li> <p>As soon as the batch size is reached:</p> <ol> <li>Initialize a timer that will work outside the worker context giving it a non-blocking characteristic.</li> <li>In this timer context, create a batch of data for the HTTP request body.</li> <li>Use the keep-alive property of HTTP connection reuse for getting the connection from the connection pool, if not present then a new connection will be automatically created.</li> <li>Use the connection and request body to execute an HTTPS request for publishing packet</li> <li>Remove the packets from the buffer that were successfully sent</li> </ol> </li> <li>Above process repeats itself as the new data keeps on coming</li> </ol> </li> <li><strong>Connection Reuse or HTTP keep-alive:</strong> HTTP keep-alive, or HTTP connection reuse, is the idea of using the same TCP connection to send and receive multiple HTTP requests/responses, as opposed to opening a new one for every single request/response pair. It enhances HTTP performance by using less network traffic due to fewer setting up and tearing down of TCP connections and reducing latency on subsequent requests due to avoidance of initial TCP handshake.</li> </ol> </li> <li> <p><strong>Edge Case</strong></p> <ul> <li>There can be a scenario where a certain topic’s batch is never going to be full or maybe filled very slowly due to low throughput. So, in that case, we also need to run a recursive nginx timer in the background that will be repeatedly fired up in a specific interval of time and will be responsible for checking any stale data present in the buffer. If yes, then create a batch of those remaining data and push them immediately to Pub/Sub.</li> </ul> </li> <li> <p><strong>Architecture</strong></p> <ul> <li>Here is a very high-level overview of what is going on behind the scenes:</li> </ul> <div style="text-align:center; margin: 10px;"> <span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 690px; "> <a class="gatsby-resp-image-link" href="/static/9097d8e2fa87f7f743cf779114358d78/4b190/lua-resty-pubsub-architecture.jpg" style="display: block" target="_blank" rel="noopener"> <span class="gatsby-resp-image-background-image" style="padding-bottom: 87.86127167630057%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAASABQDASIAAhEBAxEB/8QAGAABAQEBAQAAAAAAAAAAAAAAAAMBAgT/xAAXAQEBAQEAAAAAAAAAAAAAAAAABAEC/9oADAMBAAIQAxAAAAHaQ9FU0muk+g6GP//EABsQAAEFAQEAAAAAAAAAAAAAAAEAAhESMgMh/9oACAEBAAEFAvEa2fAKLpL5sm66a//EABYRAAMAAAAAAAAAAAAAAAAAAAABEP/aAAgBAwEBPwGs/8QAFxEBAQEBAAAAAAAAAAAAAAAAAQAQMf/aAAgBAgEBPwFjDl//xAAaEAACAgMAAAAAAAAAAAAAAAAAARAhETFR/9oACAEBAAY/Ati4VCwXCj//xAAeEAACAgEFAQAAAAAAAAAAAAABEQAhMRBBUXGhsf/aAAgBAQABPyGiTPNQUgRyVwETYgowm65FCdzp6J85/9oADAMBAAIAAwAAABBsBz//xAAWEQEBAQAAAAAAAAAAAAAAAAAAATH/2gAIAQMBAT8Qi6mtP//EABcRAAMBAAAAAAAAAAAAAAAAAAABMRH/2gAIAQIBAT8QR6QOEj//xAAcEAEAAwEAAwEAAAAAAAAAAAABABEhMRBRgaH/2gAIAQEAAT8QRMymqVBjS6RT8inEmvuJBOjcDV2WBsNyxQUiOfPDRxppyJdK47P/2Q=='); background-size: cover; display: block;"></span> <img class="gatsby-resp-image-image" alt="lua resty pubsub architecture" title="lua resty pubsub architecture" src="/static/9097d8e2fa87f7f743cf779114358d78/15ec7/lua-resty-pubsub-architecture.jpg" srcset="/static/9097d8e2fa87f7f743cf779114358d78/9ac50/lua-resty-pubsub-architecture.jpg 173w, /static/9097d8e2fa87f7f743cf779114358d78/8d48c/lua-resty-pubsub-architecture.jpg 345w, /static/9097d8e2fa87f7f743cf779114358d78/15ec7/lua-resty-pubsub-architecture.jpg 690w, /static/9097d8e2fa87f7f743cf779114358d78/4b190/lua-resty-pubsub-architecture.jpg 800w" sizes="(max-width: 690px) 100vw, 690px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy"> </a> </span> </div> </li> </ol> </li> </ol> <h2 id="lets-proceed-to-the-fun-part---benchmarking" style="position:relative;"><a href="#lets-proceed-to-the-fun-part---benchmarking" aria-label="lets proceed to the fun part benchmarking permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Let’s proceed to the fun part - Benchmarking</h2> <p>The first thing we did was to test the optimized solution with Cloud Pub/Sub to see if it could handle the anticipated load. The primary purpose was to compare how our optimized approach performs against a simple initial brute-force approach. Our hope from this solution was that the system would be able to handle this traffic from the producer for a long time, without degrading the service.</p> <p>With our new client in place, we were ready to start pushing some serious load to Pub/Sub. We used JMeter scripts with Taurus as a Load Testing tool to send mock traffic through our DACDN to Pub/Sub. Following machine configs were used while the load test was carried out:</p> <ul> <li><strong>DACDN:</strong> 16 vCPUs, 16GB RAM</li> <li><strong>JMeter Machine:</strong> 18 vCPUs, 33 GB RAM</li> <li><strong>Average Packet Size:</strong> 1KB</li> </ul> <p>Note: All these configurations were kept identical before running load tests on two solutions.</p> <p>Here are the screenshots for Performance Testing Report and Stackdriver monitoring charts for the same load throughput but one without optimization and another with one.</p> <ol> <li> <p><strong>Figure 1:</strong> Performance Testing Report when load sent to brute-force solution</p> <div style="text-align:center; margin: 10px;"> <span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 690px; "> <a class="gatsby-resp-image-link" href="/static/4f3eff6d5cc5cfd72c9998766dba4834/29007/lua-resty-pubsub-figure1.png" style="display: block" target="_blank" rel="noopener"> <span class="gatsby-resp-image-background-image" style="padding-bottom: 50.28901734104046%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAIAAAA7N+mxAAAACXBIWXMAAAsSAAALEgHS3X78AAABTElEQVQoz11Ri27DMAjM/3/m1K5dHk1iMNiA3V2aTtqGThiHx+HLkLWK1mJe6hvV3Dyqt3r4nwAFJ84yc60+iGiptbVn7/35MosnactZkMLcDC8a7UjD4UCAAwTDSvZI6h5J9LKMDyJeU9pIiolq2mdixfjMC8aBk6muG2sxLT5sbFvKIOQi07aQZieqnI81eiv8AHlvUWRhzuAQqvtOLZp7GywaEZlZxuurnctjVSbOpqNux5D3g/4YGgesjrXQ4+6KqJRXde+tOQjDztKIgAjV3leMO97sEeYtibE6iW2k8PS6toYJPUtlscRlWWknRQxAfAczBNxz+RjpMvFlyi8cwXVOp7LjLJ9jvo18n+Q+MYLrF2cxNB7NEO8y1vsct9l/ELdFsDravxb/9f3AdfKsDTZANJI6rnXe7Dceu0V0j77s9i81rfiLgcZvOnxDHahb2lwAAAAASUVORK5CYII='); background-size: cover; display: block;"></span> <img class="gatsby-resp-image-image" alt="lua resty pubsub figure1" title="lua resty pubsub figure1" src="/static/4f3eff6d5cc5cfd72c9998766dba4834/1e043/lua-resty-pubsub-figure1.png" srcset="/static/4f3eff6d5cc5cfd72c9998766dba4834/991de/lua-resty-pubsub-figure1.png 173w, /static/4f3eff6d5cc5cfd72c9998766dba4834/e4d6b/lua-resty-pubsub-figure1.png 345w, /static/4f3eff6d5cc5cfd72c9998766dba4834/1e043/lua-resty-pubsub-figure1.png 690w, /static/4f3eff6d5cc5cfd72c9998766dba4834/e3189/lua-resty-pubsub-figure1.png 1035w, /static/4f3eff6d5cc5cfd72c9998766dba4834/b1001/lua-resty-pubsub-figure1.png 1380w, /static/4f3eff6d5cc5cfd72c9998766dba4834/29007/lua-resty-pubsub-figure1.png 1600w" sizes="(max-width: 690px) 100vw, 690px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy"> </a> </span> </div> </li> <li> <p><strong>Figure 2:</strong> Performance Testing Report when load sent to an optimized solution</p> <div style="text-align:center; margin: 10px;"> <span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 690px; "> <a class="gatsby-resp-image-link" href="/static/f8883c507583e2d6124eb2c3ef06ae3e/29007/lua-resty-pubsub-figure2.png" style="display: block" target="_blank" rel="noopener"> <span class="gatsby-resp-image-background-image" style="padding-bottom: 53.17919075144509%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsSAAALEgHS3X78AAABkUlEQVQoz4VQ224TMRDd7y+opbRJRSMFeOGBB34E8QU8oNA0aXazu/Z4PLZnbC+zSwUSqoR1NDqeOXNtPn3+snq3vbi8ef1m9erq9u16c7f5sL5/r7jbqP243d7/+Hb5/ev17frm4mqlsj9oSq3T/57UKb/kb0JMIbKCU2bOSs7Ag8HR2NE6A0rAE7FITLMsJiWiBH1ozjZMtUaWXX98sj05pLbLpZQ6pdATOSmTxMF7kJy1AQwBHKpASzQWUJVJZADjPBaWCI5ZJ51iAERUntl7xBhjzgUdkadaa2JpjLUUgg6GDoVl3jDncTQ654GGVGbPi2eZk1WqMa0kIkS0+GetNpHye/z5ofcWQCvWpZa652SKcXDBICtGiAaTEue1lB64UhTjogU/WhqM5gcLBBjmTTk3SGl3gl1rH1q37/xDCz9b2HdOB9KGgPHxhIcWj61f4A4n0q+W1lM0FOqhk+OZF8gC3ncUEmuy83mJ5udQP9vHjhPrmrnxoXSjnM1f9LNNFFMtE1L+J6pQT0wlpfwLZvRnBQvXcZ8AAAAASUVORK5CYII='); background-size: cover; display: block;"></span> <img class="gatsby-resp-image-image" alt="lua resty pubsub figure2" title="lua resty pubsub figure2" src="/static/f8883c507583e2d6124eb2c3ef06ae3e/1e043/lua-resty-pubsub-figure2.png" srcset="/static/f8883c507583e2d6124eb2c3ef06ae3e/991de/lua-resty-pubsub-figure2.png 173w, /static/f8883c507583e2d6124eb2c3ef06ae3e/e4d6b/lua-resty-pubsub-figure2.png 345w, /static/f8883c507583e2d6124eb2c3ef06ae3e/1e043/lua-resty-pubsub-figure2.png 690w, /static/f8883c507583e2d6124eb2c3ef06ae3e/e3189/lua-resty-pubsub-figure2.png 1035w, /static/f8883c507583e2d6124eb2c3ef06ae3e/b1001/lua-resty-pubsub-figure2.png 1380w, /static/f8883c507583e2d6124eb2c3ef06ae3e/29007/lua-resty-pubsub-figure2.png 1600w" sizes="(max-width: 690px) 100vw, 690px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy"> </a> </span> </div> </li> <li> <p><strong>Figure 3:</strong> Send Message operations count for brute-force solution</p> <div style="text-align:center; margin: 10px;"> <span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 690px; "> <a class="gatsby-resp-image-link" href="/static/c0174eb273f0c46bb7a63c8cb4c625ce/29007/lua-resty-pubsub-figure3.png" style="display: block" target="_blank" rel="noopener"> <span class="gatsby-resp-image-background-image" style="padding-bottom: 43.35260115606936%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAIAAAC9o5sfAAAACXBIWXMAAAsSAAALEgHS3X78AAAA80lEQVQoz4WRcU+EMAzF9/0/mwfoaeI/6mkiY80BmzsYbB342DxjjHc2vzSPva4rqTDGDMNp/Ypl/StijB4x+xA2EUJAds4JKOa4XohlWa5YIka0GUAIY8oOgnlm9ngvhInZZbw/wWWeUobL4nh8lLJomqppSilL5EzShVJ3pJ5V89L379ZS370mqyB6QF+RSqsLlErtiZ6IDsZIa5XWb7k10T5frq4gZXUepJByl2fEUET32+W6vqnrXSbbZ7bPdFj8AodK3eK3xTi282ymSYNx7IBzfRZgGNpv/YMWxVid6Dqt9QcwxmKF3vO/YNvGoN58Atqk+LyuPUAVAAAAAElFTkSuQmCC'); background-size: cover; display: block;"></span> <img class="gatsby-resp-image-image" alt="lua resty pubsub figure3" title="lua resty pubsub figure3" src="/static/c0174eb273f0c46bb7a63c8cb4c625ce/1e043/lua-resty-pubsub-figure3.png" srcset="/static/c0174eb273f0c46bb7a63c8cb4c625ce/991de/lua-resty-pubsub-figure3.png 173w, /static/c0174eb273f0c46bb7a63c8cb4c625ce/e4d6b/lua-resty-pubsub-figure3.png 345w, /static/c0174eb273f0c46bb7a63c8cb4c625ce/1e043/lua-resty-pubsub-figure3.png 690w, /static/c0174eb273f0c46bb7a63c8cb4c625ce/e3189/lua-resty-pubsub-figure3.png 1035w, /static/c0174eb273f0c46bb7a63c8cb4c625ce/b1001/lua-resty-pubsub-figure3.png 1380w, /static/c0174eb273f0c46bb7a63c8cb4c625ce/29007/lua-resty-pubsub-figure3.png 1600w" sizes="(max-width: 690px) 100vw, 690px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy"> </a> </span> </div> </li> <li> <p><strong>Figure 4:</strong> Send Message operations count for an optimized solution</p> <div style="text-align:center; margin: 10px;"> <span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 690px; "> <a class="gatsby-resp-image-link" href="/static/01d9f1e3e785bf7b680e7809e9b9aa83/29007/lua-resty-pubsub-figure4.png" style="display: block" target="_blank" rel="noopener"> <span class="gatsby-resp-image-background-image" style="padding-bottom: 43.35260115606936%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAIAAAC9o5sfAAAACXBIWXMAAAsSAAALEgHS3X78AAABFElEQVQoz42QUY/CIBCE+/9/mWfV5B5arYmnl9NGTa0FFiks0BvUqLkHc5svZFiYWUImhNBEQxzeVAjBpbLM3jr2nrExxmSQPiZrfAl4G3Uv+DK72XMjXCOi42Bs6F1AtjaD4wh9JbIHAWsI9/QIGTI5KlQ+J/C5otmSphVNFlSuz9s9HU98liz1ZbVLR7OlLjdIeZrTVRimlcoXNEkiBVXfdV0fDwfb9xinv3Y0LmFWo8LWze0X0rMf5ifoFOtm/dNutv3hZE8CWYRomPM5ht/enSar8RwzH9AVCD2pNDROP8rXPvBkkh1mBHNHrlXcKddKbrEKNN2NVv6lER7/OkRmzqgTRtFFKkPaev4XztG5U0L+Ainl/IxyipZAAAAAAElFTkSuQmCC'); background-size: cover; display: block;"></span> <img class="gatsby-resp-image-image" alt="lua resty pubsub figure4" title="lua resty pubsub figure4" src="/static/01d9f1e3e785bf7b680e7809e9b9aa83/1e043/lua-resty-pubsub-figure4.png" srcset="/static/01d9f1e3e785bf7b680e7809e9b9aa83/991de/lua-resty-pubsub-figure4.png 173w, /static/01d9f1e3e785bf7b680e7809e9b9aa83/e4d6b/lua-resty-pubsub-figure4.png 345w, /static/01d9f1e3e785bf7b680e7809e9b9aa83/1e043/lua-resty-pubsub-figure4.png 690w, /static/01d9f1e3e785bf7b680e7809e9b9aa83/e3189/lua-resty-pubsub-figure4.png 1035w, /static/01d9f1e3e785bf7b680e7809e9b9aa83/b1001/lua-resty-pubsub-figure4.png 1380w, /static/01d9f1e3e785bf7b680e7809e9b9aa83/29007/lua-resty-pubsub-figure4.png 1600w" sizes="(max-width: 690px) 100vw, 690px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy"> </a> </span> </div> </li> <li> <p><strong>Figure 5:</strong> Byte Cost for publishing messages to brute-force solution</p> <div style="text-align:center; margin: 10px;"> <span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 690px; "> <a class="gatsby-resp-image-link" href="/static/c0aaf3b8b17c0d8024f3f7d22a71719b/29007/lua-resty-pubsub-figure5.png" style="display: block" target="_blank" rel="noopener"> <span class="gatsby-resp-image-background-image" style="padding-bottom: 43.35260115606936%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAIAAAC9o5sfAAAACXBIWXMAAAsSAAALEgHS3X78AAAA70lEQVQoz51R0W6EIBDk/3+t0XrXpj/QtDEiwsIp7C7YLuo1Num91EwmM+sOZBcVvE/LMntPiF8PvrWUzCwoeS3Ma87CMUYFMfW3pQ/zMEedcEpoIjkkQAYkj+yQPeXA2VYtvyrf5Kyc1ZsLjYbnEdoRGu1aDa12YgWXEd4n9zGa3sEnhKOunbS9GJ+I1GXr6/6Guwq0wAr/1KX/1fgo4cdJ6E6BM/ZwIlbdf8P15qfBbqPWSXbR3m072LNtTlqGRVmYiQiUZclTPGAT7cLEdLYH6nOgLJyYFUwmAFBKmZARhSs2wXS39KteiMBaAPgGXcn1LLOxLEMAAAAASUVORK5CYII='); background-size: cover; display: block;"></span> <img class="gatsby-resp-image-image" alt="lua resty pubsub figure5" title="lua resty pubsub figure5" src="/static/c0aaf3b8b17c0d8024f3f7d22a71719b/1e043/lua-resty-pubsub-figure5.png" srcset="/static/c0aaf3b8b17c0d8024f3f7d22a71719b/991de/lua-resty-pubsub-figure5.png 173w, /static/c0aaf3b8b17c0d8024f3f7d22a71719b/e4d6b/lua-resty-pubsub-figure5.png 345w, /static/c0aaf3b8b17c0d8024f3f7d22a71719b/1e043/lua-resty-pubsub-figure5.png 690w, /static/c0aaf3b8b17c0d8024f3f7d22a71719b/e3189/lua-resty-pubsub-figure5.png 1035w, /static/c0aaf3b8b17c0d8024f3f7d22a71719b/b1001/lua-resty-pubsub-figure5.png 1380w, /static/c0aaf3b8b17c0d8024f3f7d22a71719b/29007/lua-resty-pubsub-figure5.png 1600w" sizes="(max-width: 690px) 100vw, 690px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy"> </a> </span> </div> </li> <li> <p><strong>Figure 6:</strong> Byte Cost for publishing messages to an optimized solution</p> <div style="text-align:center; margin: 10px;"> <span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 690px; "> <a class="gatsby-resp-image-link" href="/static/9790456378e926f19989b39871e5af45/29007/lua-resty-pubsub-figure6.png" style="display: block" target="_blank" rel="noopener"> <span class="gatsby-resp-image-background-image" style="padding-bottom: 42.77456647398844%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAIAAAC9o5sfAAAACXBIWXMAAAsSAAALEgHS3X78AAAA2UlEQVQoz3WRiW7DIAyGef+3TNqo0tSL2wYfJZBuzbJZn34Mv80hjHMuxtgUAPSfYObSArFWwlJH5JxNG4iollVVtCEsI9lxCBExR0P+btqmIj/XMXVRuna8klO2SlbICwfdsMquJ1457t5i6qz1pKs2pq4nsV/+cbU5ANzLZr1r2hbjHr25rZ73LBru6Xmz/hniLdPyYbXiy6+T95RZcBaYCCfGmQ+uStr6TQ3ESSgwR6EonLT2vKMjocGoCcKgTNy+yUSXICEUwPJWXBXLp2J38Xsl+RRceAHHYA5kL8YG4wAAAABJRU5ErkJggg=='); background-size: cover; display: block;"></span> <img class="gatsby-resp-image-image" alt="lua resty pubsub figure6" title="lua resty pubsub figure6" src="/static/9790456378e926f19989b39871e5af45/1e043/lua-resty-pubsub-figure6.png" srcset="/static/9790456378e926f19989b39871e5af45/991de/lua-resty-pubsub-figure6.png 173w, /static/9790456378e926f19989b39871e5af45/e4d6b/lua-resty-pubsub-figure6.png 345w, /static/9790456378e926f19989b39871e5af45/1e043/lua-resty-pubsub-figure6.png 690w, /static/9790456378e926f19989b39871e5af45/e3189/lua-resty-pubsub-figure6.png 1035w, /static/9790456378e926f19989b39871e5af45/b1001/lua-resty-pubsub-figure6.png 1380w, /static/9790456378e926f19989b39871e5af45/29007/lua-resty-pubsub-figure6.png 1600w" sizes="(max-width: 690px) 100vw, 690px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy"> </a> </span> </div> </li> </ol> <h2 id="so-what-was-the-difference" style="position:relative;"><a href="#so-what-was-the-difference" aria-label="so what was the difference permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>So what was the difference?</h2> <ul> <li>Clearly, the first thing we can notice is a reduction in Average Response time from 7ms to 3ms which is quite significant. Also, 90% of Response times is reduced from 10ms to 4ms which was a huge boost.</li> <li>Due to low response time, Taurus was able to send requests at DACDN at relatively higher throughput thereby completing his 20M requests 12 minutes earlier than the brute-force solution.</li> <li>Also, the optimized solution has a higher “Send Message operations count” than the brute-force solution and due to this the same DACDN’s resources were much better utilized.</li> <li>There was a clear difference in cost in both approaches by 2.5 times. So it would be cheaper for anyone to use the optimized solution for publishing messages at better throughput.</li> </ul> <h2 id="solution-as-a-library" style="position:relative;"><a href="#solution-as-a-library" aria-label="solution as a library permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Solution as a Library</h2> <p>We definitely wanted other lua-nginx developers to benefit from this solution and thus we have open-sourced this approach as a library on GitHub. You can get the library from <a href="https://github.com/wingify/lua-resty-pubsub">here</a> with detailed documentation for helping you out while incorporating this library in your system. If you feel that you can contribute to this library in any way then you are most welcome as an Open-source Contributor.</p> <h2 id="core-modules-of-the-library" style="position:relative;"><a href="#core-modules-of-the-library" aria-label="core modules of the library permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Core Modules of the library</h2> <ol> <li> <p><strong>producer.lua</strong></p> <ul> <li>Responsible for accepting data from users, validating them, and finally normalizing the optional configurations</li> <li>Creating as well as monitoring background nginx timers for data accumulation or data push</li> </ul> </li> <li> <p><strong>oauth_client.lua</strong></p> <ul> <li>Responsible for generating OAuth token for authorizing over https when making requests</li> <li>It also maintains a local cache so that the same token can be used repeatedly before token expiry</li> </ul> </li> <li> <p><strong>request.lua</strong></p> <ul> <li>Maintains all the connection related configurations and errors</li> <li>Responsible for actually making HTTPS requests to the Pub/Sub server for publishing packets</li> </ul> </li> <li> <p><strong>ring_buffer.lua</strong></p> <ul> <li>Module for storing the accumulated data in a table</li> <li>Provide methods for push, pop, length, and bytes used for the queue</li> </ul> </li> </ol> <h2 id="conclusion" style="position:relative;"><a href="#conclusion" aria-label="conclusion permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Conclusion</h2> <p>Using a combination of Pub/Sub Rest API and Nginx Timers for batching, there are some obvious pros to it: </p> <ul> <li>Better Pub/Sub write throughput in a non-blocking manner</li> <li>Reduced Cost by almost 2.5 times while publishing messages to Pub/Sub</li> <li>Improved response time and data acquisition</li> </ul> <h2 id="useful-resources" style="position:relative;"><a href="#useful-resources" aria-label="useful resources permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Useful resources</h2> <ul> <li><a href="https://github.com/openresty/lua-nginx-module">Lua Nginx Module</a></li> <li><a href="https://cloud.google.com/pubsub/docs/reference/rest">Google Pub/Sub Rest API</a></li> <li><a href="https://cloud.google.com/pubsub/quotas">Google Pub/Sub Quotas and limits</a></li> <li><a href="https://gettaurus.org/">Taurus Load Testing tool</a></li> </ul></div><hr/><div class="post-module--post-meta--1ci95"><div class="SocialLinks-module--social-links--1RDpJ"><div role="button" tabindex="0" class="SocialMediaShareButton SocialMediaShareButton--reddit"><div style="width:32px;height:32px"><svg viewBox="0 0 64 64" width="32" height="32" class="social-icon social-icon--reddit "><g><circle cx="32" cy="32" r="31" fill="#5f99cf"></circle></g><g><path d="m 52.8165,31.942362 c 0,-2.4803 -2.0264,-4.4965 -4.5169,-4.4965 -1.2155,0 -2.3171,0.4862 -3.128,1.2682 -3.077,-2.0247 -7.2403,-3.3133 -11.8507,-3.4782 l 2.5211,-7.9373 6.8272,1.5997 -0.0102,0.0986 c 0,2.0281 1.6575,3.6771 3.6958,3.6771 2.0366,0 3.6924,-1.649 3.6924,-3.6771 0,-2.0281 -1.6575,-3.6788 -3.6924,-3.6788 -1.564,0 -2.8968,0.9758 -3.4357,2.3443 l -7.3593,-1.7255 c -0.3213,-0.0782 -0.6477,0.1071 -0.748,0.4233 L 32,25.212062 c -4.8246,0.0578 -9.1953,1.3566 -12.41,3.4425 -0.8058,-0.7446 -1.8751,-1.2104 -3.0583,-1.2104 -2.4905,0 -4.5152,2.0179 -4.5152,4.4982 0,1.649 0.9061,3.0787 2.2389,3.8607 -0.0884,0.4794 -0.1462,0.9639 -0.1462,1.4569 0,6.6487 8.1736,12.0581 18.2223,12.0581 10.0487,0 18.224,-5.4094 18.224,-12.0581 0,-0.4658 -0.0493,-0.9248 -0.1275,-1.377 1.4144,-0.7599 2.3885,-2.2304 2.3885,-3.9406 z m -29.2808,3.0872 c 0,-1.4756 1.207,-2.6775 2.6894,-2.6775 1.4824,0 2.6877,1.2019 2.6877,2.6775 0,1.4756 -1.2053,2.6758 -2.6877,2.6758 -1.4824,0 -2.6894,-1.2002 -2.6894,-2.6758 z m 15.4037,7.9373 c -1.3549,1.3481 -3.4816,2.0043 -6.5008,2.0043 l -0.0221,-0.0051 -0.0221,0.0051 c -3.0209,0 -5.1476,-0.6562 -6.5008,-2.0043 -0.2465,-0.2448 -0.2465,-0.6443 0,-0.8891 0.2465,-0.2465 0.6477,-0.2465 0.8942,0 1.105,1.0999 2.9393,1.6337 5.6066,1.6337 l 0.0221,0.0051 0.0221,-0.0051 c 2.6673,0 4.5016,-0.5355 5.6066,-1.6354 0.2465,-0.2465 0.6477,-0.2448 0.8942,0 0.2465,0.2465 0.2465,0.6443 0,0.8908 z m -0.3213,-5.2615 c -1.4824,0 -2.6877,-1.2002 -2.6877,-2.6758 0,-1.4756 1.2053,-2.6775 2.6877,-2.6775 1.4824,0 2.6877,1.2019 2.6877,2.6775 0,1.4756 -1.2053,2.6758 -2.6877,2.6758 z" fill="white"></path></g></svg></div><div class="SocialMediaShareCount"><div class="SocialLinks-module--share-count--3wXKp"></div></div></div><div role="button" tabindex="0" class="SocialMediaShareButton SocialMediaShareButton--twitter"><div style="width:32px;height:32px"><svg viewBox="0 0 64 64" width="32" height="32" class="social-icon social-icon--twitter "><g><circle cx="32" cy="32" r="31" fill="#00aced"></circle></g><g><path d="M48,22.1c-1.2,0.5-2.4,0.9-3.8,1c1.4-0.8,2.4-2.1,2.9-3.6c-1.3,0.8-2.7,1.3-4.2,1.6 C41.7,19.8,40,19,38.2,19c-3.6,0-6.6,2.9-6.6,6.6c0,0.5,0.1,1,0.2,1.5c-5.5-0.3-10.3-2.9-13.5-6.9c-0.6,1-0.9,2.1-0.9,3.3 c0,2.3,1.2,4.3,2.9,5.5c-1.1,0-2.1-0.3-3-0.8c0,0,0,0.1,0,0.1c0,3.2,2.3,5.8,5.3,6.4c-0.6,0.1-1.1,0.2-1.7,0.2c-0.4,0-0.8,0-1.2-0.1 c0.8,2.6,3.3,4.5,6.1,4.6c-2.2,1.8-5.1,2.8-8.2,2.8c-0.5,0-1.1,0-1.6-0.1c2.9,1.9,6.4,2.9,10.1,2.9c12.1,0,18.7-10,18.7-18.7 c0-0.3,0-0.6,0-0.8C46,24.5,47.1,23.4,48,22.1z" fill="white"></path></g></svg></div></div><div role="button" tabindex="0" class="SocialMediaShareButton SocialMediaShareButton--facebook"><div style="width:32px;height:32px"><svg viewBox="0 0 64 64" width="32" height="32" class="social-icon social-icon--facebook "><g><circle cx="32" cy="32" r="31" fill="#3b5998"></circle></g><g><path d="M34.1,47V33.3h4.6l0.7-5.3h-5.3v-3.4c0-1.5,0.4-2.6,2.6-2.6l2.8,0v-4.8c-0.5-0.1-2.2-0.2-4.1-0.2 c-4.1,0-6.9,2.5-6.9,7V28H24v5.3h4.6V47H34.1z" fill="white"></path></g></svg></div><div class="SocialMediaShareCount"><div class="SocialLinks-module--share-count--3wXKp"></div></div></div><div role="button" tabindex="0" class="SocialMediaShareButton SocialMediaShareButton--linkedin"><div style="width:32px;height:32px"><svg viewBox="0 0 64 64" width="32" height="32" class="social-icon social-icon--linkedin "><g><circle cx="32" cy="32" r="31" fill="#007fb1"></circle></g><g><path d="M20.4,44h5.4V26.6h-5.4V44z M23.1,18c-1.7,0-3.1,1.4-3.1,3.1c0,1.7,1.4,3.1,3.1,3.1 c1.7,0,3.1-1.4,3.1-3.1C26.2,19.4,24.8,18,23.1,18z M39.5,26.2c-2.6,0-4.4,1.4-5.1,2.8h-0.1v-2.4h-5.2V44h5.4v-8.6 c0-2.3,0.4-4.5,3.2-4.5c2.8,0,2.8,2.6,2.8,4.6V44H46v-9.5C46,29.8,45,26.2,39.5,26.2z" fill="white"></path></g></svg></div><div class="SocialMediaShareCount"><div class="SocialLinks-module--share-count--3wXKp"></div></div></div><div role="button" tabindex="0" class="SocialMediaShareButton SocialMediaShareButton--telegram"><div style="width:32px;height:32px"><svg viewBox="0 0 64 64" width="32" height="32" class="social-icon social-icon--telegram "><g><circle cx="32" cy="32" r="31" fill="#37aee2"></circle></g><g><path d="m45.90873,15.44335c-0.6901,-0.0281 -1.37668,0.14048 -1.96142,0.41265c-0.84989,0.32661 -8.63939,3.33986 -16.5237,6.39174c-3.9685,1.53296 -7.93349,3.06593 -10.98537,4.24067c-3.05012,1.1765 -5.34694,2.05098 -5.4681,2.09312c-0.80775,0.28096 -1.89996,0.63566 -2.82712,1.72788c-0.23354,0.27218 -0.46884,0.62161 -0.58825,1.10275c-0.11941,0.48114 -0.06673,1.09222 0.16682,1.5716c0.46533,0.96052 1.25376,1.35737 2.18443,1.71383c3.09051,0.99037 6.28638,1.93508 8.93263,2.8236c0.97632,3.44171 1.91401,6.89571 2.84116,10.34268c0.30554,0.69185 0.97105,0.94823 1.65764,0.95525l-0.00351,0.03512c0,0 0.53908,0.05268 1.06412,-0.07375c0.52679,-0.12292 1.18879,-0.42846 1.79109,-0.99212c0.662,-0.62161 2.45836,-2.38812 3.47683,-3.38552l7.6736,5.66477l0.06146,0.03512c0,0 0.84989,0.59703 2.09312,0.68132c0.62161,0.04214 1.4399,-0.07726 2.14229,-0.59176c0.70766,-0.51626 1.1765,-1.34683 1.396,-2.29506c0.65673,-2.86224 5.00979,-23.57745 5.75257,-27.00686l-0.02107,0.08077c0.51977,-1.93157 0.32837,-3.70159 -0.87096,-4.74991c-0.60054,-0.52152 -1.2924,-0.7498 -1.98425,-0.77965l0,0.00176zm-0.2072,3.29069c0.04741,0.0439 0.0439,0.0439 0.00351,0.04741c-0.01229,-0.00351 0.14048,0.2072 -0.15804,1.32576l-0.01229,0.04214l-0.00878,0.03863c-0.75858,3.50668 -5.15554,24.40802 -5.74203,26.96472c-0.08077,0.34417 -0.11414,0.31959 -0.09482,0.29852c-0.1756,-0.02634 -0.50045,-0.16506 -0.52679,-0.1756l-13.13468,-9.70175c4.4988,-4.33199 9.09945,-8.25307 13.744,-12.43229c0.8218,-0.41265 0.68483,-1.68573 -0.29852,-1.70681c-1.04305,0.24584 -1.92279,0.99564 -2.8798,1.47502c-5.49971,3.2626 -11.11882,6.13186 -16.55882,9.49279c-2.792,-0.97105 -5.57873,-1.77704 -8.15298,-2.57601c2.2336,-0.89555 4.00889,-1.55579 5.75608,-2.23009c3.05188,-1.1765 7.01687,-2.7042 10.98537,-4.24067c7.94051,-3.06944 15.92667,-6.16346 16.62028,-6.43037l0.05619,-0.02283l0.05268,-0.02283c0.19316,-0.0878 0.30378,-0.09658 0.35471,-0.10009c0,0 -0.01756,-0.05795 -0.00351,-0.04566l-0.00176,0zm-20.91715,22.0638l2.16687,1.60145c-0.93418,0.91311 -1.81743,1.77353 -2.45485,2.38812l0.28798,-3.98957" fill="white"></path></g></svg></div></div></div></div></div><nav><ul class="post-module--pagination--1F-V0"><li><a rel="prev" href="/posts/maths-behind-bayesian-duration-calculator/">← <!-- -->Maths behind Bayesian Duration Calculator</a></li><li><a rel="next" href="/posts/kafka-streams-stateful-ingestion-with-processor-api/">Kafka Streams Stateful Ingestion with Processor API<!-- -->→</a></li></ul></nav><div id="disqus_thread"></div></main><footer><div class="Footer-module--container--3Dvn1"><div><a href="https://twitter.com/wingify_engg" target="_blank" rel="noopener noreferrer">Twitter</a><a href="https://github.com/wingify" target="_blank" rel="noopener noreferrer">GitHub</a><a href="https://engineering.wingify.com/atom.xml" target="_blank" rel="noopener noreferrer">RSS</a></div><div class="Footer-module--copyright--2uglW">Copyright © Wingify. All rights reserved.</div></div></footer></div><div id="gatsby-announcer" style="position:absolute;top:0;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border:0" aria-live="assertive" aria-atomic="true"></div></div><script id="gatsby-script-loader">/*<![CDATA[*/window.pagePath="/posts/a-lua-nginx-client-for-pubsub/";/*]]>*/</script><script id="gatsby-chunk-mapping">/*<![CDATA[*/window.___chunkMapping={"app":["/app-99ec7086382d867c7c8c.js"],"component---node-modules-gatsby-plugin-offline-app-shell-js":["/component---node-modules-gatsby-plugin-offline-app-shell-js-e385def15e29b6ed02a7.js"],"component---src-pages-404-js":["/component---src-pages-404-js-0c6355b7aa7c186b82f7.js"],"component---src-pages-about-js":["/component---src-pages-about-js-fc892947e3543d68cd59.js"],"component---src-pages-contact-js":["/component---src-pages-contact-js-8f1d647515267d9174b5.js"],"component---src-pages-docs-js":["/component---src-pages-docs-js-75320bb29b557bc73d6f.js"],"component---src-pages-index-js":["/component---src-pages-index-js-8f95e95b8fdd494d939f.js"],"component---src-pages-labs-js":["/component---src-pages-labs-js-ac5b96295395cd153578.js"],"component---src-templates-post-js":["/component---src-templates-post-js-86eb8f5f76ee576dcf22.js"]};/*]]>*/</script><script src="/component---src-templates-post-js-86eb8f5f76ee576dcf22.js" async=""></script><script src="/6b3c31c87dfb891be24ad1723dc89e8083e2fee1-3aeae6b2992d33209c1a.js" async=""></script><script src="/29107295-b32bd7b51275495551b1.js" async=""></script><script src="/styles-823ae8103e36ae8a7f9f.js" async=""></script><script src="/app-99ec7086382d867c7c8c.js" async=""></script><script src="/framework-9fe058f4359556db0d38.js" async=""></script><script src="/webpack-runtime-e67a7c97db3322d008c9.js" async=""></script></body></html>