<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Nik Rahmel on Medium]]></title>
        <description><![CDATA[Stories by Nik Rahmel on Medium]]></description>
        <link>https://medium.com/@nik.rahmel?source=rss-c3d7d944090c------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*leD_wH2FNNjq6HIb9mHYSw.jpeg</url>
            <title>Stories by Nik Rahmel on Medium</title>
            <link>https://medium.com/@nik.rahmel?source=rss-c3d7d944090c------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 14 Nov 2026 16:23:19 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@nik.rahmel/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[We have moved]]></title>
            <link>https://medium.com/bbc-news-labs/we-have-moved-d77e8623f0bd?source=rss-c3d7d944090c------2</link>
            <guid isPermaLink="false">https://medium.com/p/d77e8623f0bd</guid>
            <dc:creator><![CDATA[Nik Rahmel]]></dc:creator>
            <pubDate>Thu, 19 May 2022 15:59:27 GMT</pubDate>
            <atom:updated>2022-05-19T15:59:27.235Z</atom:updated>
            <content:encoded><![CDATA[<p>Please see <a href="http://bbcnewslabs.co.uk/">our website</a> for any future updates.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d77e8623f0bd" width="1" height="1" alt=""><hr><p><a href="https://medium.com/bbc-news-labs/we-have-moved-d77e8623f0bd">We have moved</a> was originally published in <a href="https://medium.com/bbc-news-labs">BBC News Labs</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[We have moved]]></title>
            <link>https://medium.com/iplayer-engineering/we-have-moved-81b2024b71c9?source=rss-c3d7d944090c------2</link>
            <guid isPermaLink="false">https://medium.com/p/81b2024b71c9</guid>
            <category><![CDATA[iplayer]]></category>
            <dc:creator><![CDATA[Nik Rahmel]]></dc:creator>
            <pubDate>Tue, 08 Oct 2019 12:17:37 GMT</pubDate>
            <atom:updated>2019-10-08T12:17:37.490Z</atom:updated>
            <content:encoded><![CDATA[<p>We have joined the joint BBC Design + Engineering publication. If you want to keep following us, keep an eye out on <a href="https://medium.com/bbc-design-engineering/iplayer/home">t</a>he <a href="https://medium.com/bbc-design-engineering/iplayer/home">iPlayer feature page</a> or just follow the <a href="https://medium.com/bbc-design-engineering">whole blog</a>!</p><p><a href="https://medium.com/bbc-design-engineering">BBC Design + Engineering</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=81b2024b71c9" width="1" height="1" alt=""><hr><p><a href="https://medium.com/iplayer-engineering/we-have-moved-81b2024b71c9">We have moved</a> was originally published in <a href="https://medium.com/iplayer-engineering">BBC iPlayer Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Bringing the BBC’s World Cup & Wimbledon online coverage to you]]></title>
            <link>https://medium.com/bbc-product-technology/bringing-the-bbcs-world-cup-wimbledon-online-coverage-to-you-bc1bb95f450d?source=rss-c3d7d944090c------2</link>
            <guid isPermaLink="false">https://medium.com/p/bc1bb95f450d</guid>
            <category><![CDATA[tv]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[bbc]]></category>
            <category><![CDATA[world-cup]]></category>
            <category><![CDATA[broadcasting]]></category>
            <dc:creator><![CDATA[Nik Rahmel]]></dc:creator>
            <pubDate>Mon, 25 Mar 2019 10:34:30 GMT</pubDate>
            <atom:updated>2019-03-25T10:34:30.591Z</atom:updated>
            <content:encoded><![CDATA[<p><em>This post is coauthored by engineers across the different teams involved: </em><a href="https://twitter.com/Marc_Burrows"><em>Marc Burrows</em></a><em>, </em><a href="https://twitter.com/AlexChesters"><em>Alex Chesters</em></a><em>, </em><a href="https://twitter.com/tdp_org"><em>Neil Craig</em></a><em>, </em><a href="https://twitter.com/silly_sal"><em>Sally Fletcher</em></a><em>, </em><a href="https://twitter.com/marclittlemore"><em>Marc Littlemore</em></a><em>, </em><a href="https://medium.com/@nik.rahmel"><em>Nik Rahmel</em></a><em>, and </em><a href="https://medium.com/@lloyd.wallis"><em>Lloyd Wallis</em></a><em>.</em></p><p>There is a lot of collaboration across the BBC’s Design &amp; Engineering teams to bring the World Cup and Wimbledon to the BBC. From the actual application, over scheduling behind the scenes, how the actual video transmission makes it way into iPlayer and BBC Sport and how this lets users actually watch a live broadcast.</p><p>This year we are also broadcasting for the first time in <a href="https://en.wikipedia.org/wiki/Ultra-high-definition_television">UHD</a> (Ultra-High Definition) resolution for the World Cup and selected Wimbledon matches.</p><p>Let’s start with how most people watch a match — they tune in into the match. BBC One is a pretty good bet to try if you’re in the UK, and if you’re lucky might see one of our ‘UHD Call To Actions’ on the screen and one of the commentators tell you this:</p><blockquote>You can watch this match in Ultra HD via the BBC iPlayer</blockquote><p>Someone decided that some of the World Cup matches are broadcast on the BBC in HD and UHD — more on the production side, and how our UHD trials worked this summer in <a href="https://www.bbc.co.uk/rd/blog/2018-07-ultra-high-definition-uhd-viewing">this blog post</a> from <a href="https://www.bbc.co.uk/rd">BBC R&amp;D</a> (Research and Development).</p><p>Once we know the schedule, we co-ordinate the schedule for our Audio/Video encoders to deliver the correct streams for the correct events at the correct times. For the World Cup, the UHD streams are actually completely separate episodes in the <a href="http://www.bbc.co.uk/blogs/bbcinternet/2009/02/what_is_pips.html">BBC programme structure</a>, but embargoed in the system so they do not show up anywhere, including programme guides.</p><p>For Wimbledon, we also can’t use the same episodes as on broadcast TV since the broadcasts sometimes switch between channels, and are split up, so the UHD broadcast is attached to the equivalent Webcasts.</p><p>For the UHD streams, the encoder, packagers and backups, as well as the <a href="https://en.wikipedia.org/wiki/Content_delivery_network">CDN</a> (Content Delivery Network) configuration are controlled manually. This means, that prior to the match BBC Sport staff in Salford co-ordinate with their Media Services and OTG (Online Technology Group) colleagues in London, who are responsible for the stream delivery, and the broadcast staff in Moscow responsible for the contribution feed coming into London to turn everything on in the right order and start the stream.</p><p>This needs to happen before the broadcast begins because as soon as the broadcast episode becomes ‘available’ on iPlayer, the TV app starts to offer the UHD version — and if the encoder isn’t turned on, that does not look pretty.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ThjFbL_WYICo15aC.jpg" /><figcaption>FIFA is very nice and puts their Central Equipment Room behind glass for some great photos (Picture: Quinn Cowper)</figcaption></figure><p>Once everything is running, our CDNs are warmed up and we see no issues, we are getting closer to the match. The ‘<a href="https://en.wikipedia.org/wiki/Call_to_action_(marketing)">Call to Action</a>’ (CTA) for users to switch to iPlayer for UHD actually comes from the Outside Broadcast truck in Russia.</p><p>When everything is prepared, we are ready to serve the live streams to the audience, and that is co-ordinated across the different iPlayer teams.</p><p>For the app on smart TVs and set-top boxes, the basic architecture is a pretty typical client-server one. We have a common client application which is used for all of the BBC’s Connected TV products and numerous backend services. These services often take the form of a <a href="https://en.wikipedia.org/wiki/Node.js">Node.js</a> application deployed to <a href="https://en.wikipedia.org/wiki/Amazon_Elastic_Compute_Cloud">Amazon EC2</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*agEa1TINLj-GOeiH.png" /><figcaption>Request rate for the iPlayer Homepage from TV devices for BRA-BEL on Jul 6 —The On-Screen CTA is clearly visible. This is where scheduled scaling comes in very handy.</figcaption></figure><p>Our usual scaling process is to scale in response to <a href="https://en.wikipedia.org/wiki/Amazon_Elastic_Compute_Cloud#Amazon_CloudWatch">AWS CloudWatch</a> alarms which are hooked up to appropriate metrics (e.g. memory utilisation, CPU utilisation, network throughput) for the service in question. This approach works well with our usual traffic patterns as they are reasonably gradual increases. When an on-air CTA is made, however, our traffic increases at a rate far too great for AWS AutoScaling to have an effect. We have seen the network traffic through our <a href="https://en.wikipedia.org/wiki/Network_address_translation">NAT</a> Gateways increase by 150% in the space of 60 seconds and traffic we send to the <a href="https://en.wikipedia.org/wiki/Application_programming_interface">API</a> that powers the iPlayer homepage tripled in less than 20 seconds.</p><h3>Nik Rahmel on Twitter</h3><p>I wonder if we can thank the @EveningStandard for a strong peak in requests to iPlayer at 7pm?</p><p>As soon as we find out which games are due to be broadcast on BBC One (and therefore in UHD) we start analysing the times to determine when the peak traffic points are expected to be. We liaise with our colleagues from across the whole of the BBC to determine if there are any on-air CTAs planned and if so, when they are likely to occur.</p><p>Once we’ve got all the necessary information we begin planning our scaling rules. Unfortunately this process is (for now) rather manual as we make use of <a href="https://docs.aws.amazon.com/autoscaling/ec2/userguide/schedule_time.html">AWS’ Scheduled Scaling</a>. Internally we’ve started looking at various other solutions that would allow automated scaling to be effective in these situations — hopefully we can write another blog post about this in the future.</p><p>To enforce a limit on the number of UHD streams, we couldn’t rely on previously used measurement systems, as the latency is too high. At kick off and on-screen CTAs, we have seen the number of viewers increase by a third within 60 seconds. We need to ensure that we can “close the gate” reliably when we reach the limit, as there is a real possibility to reach the limits of the UK internet infrastructure due to the bandwidth requirements of UHD — roughly 8x that of HD.</p><p>There is a Counting Service built by our Live Sport team, which uses AWS Kinesis Firehose to collect the viewer-count statistics, which was linked to the “gate”, and heavily used in the Live Pages — Mark Woosey has blogged about the details separately <a href="https://medium.com/bbc-design-engineering/the-live-summer-of-sport-2126b4c80e06">here</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*-BzuTHHxxt77SNo5.png" /><figcaption>Counting Service figures for ENG-SWE on July 7 — Kick Off and corresponding CTA clearly visible</figcaption></figure><h3>BBC Account</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*sxMgT5MNpEH5klcP.png" /><figcaption>Sign in to your BBC account</figcaption></figure><p>In order to allow users to see iPlayer or Sport content, we ask you to sign in to your BBC account. This is a collection of applications which open the gateway to user personalisation for the <strong>whole of the BBC</strong>, and not just for iPlayer and our live streams.</p><p>As with many of the modern BBC services, our applications are a set of Node.js microservices, which are deployed using AWS cloud services, and communicate with other BBC services via HTTP requests. We have an isomorphic React application which displays the sign in and registration forms and then securely handles your credentials to confirm your identity. We’ve spent a lot of time ensuring that the applications can be scaled effectively for large events and we do so using AWS autoscaling. Our amazing Quality Assurance (QA) team had already spent a lot of time performance testing our systems in the past in order to understand the expected traffic patterns and server load for our usual “big” events, when we authenticate users during the live online votes for Strictly Come Dancing and Sports Personality Of The Year. That said, we knew that our audience would be keen to watch both the World Cup and Wimbledon, so we performance tested some different traffic profiles, and then added contingency in the shape of additional virtual server instances to support potentially higher loads.</p><p>Needless to say you didn’t disappoint us!</p><h3>Marc Burrows on Twitter</h3><p>@MarcLittlemore Can you tell when england scored</p><p>As you can see from the tweet of one of our graphs during the England vs Sweden game, we had huge peaks of traffic as you signed into, or registered for, your BBC accounts at the start of the game. In fact, we had <strong>the most registrations we’ve ever had during a single day!</strong> You can also see where the two England goals occurred from the second and third peaks. People were quick to sign in and watch the goals again on our iPlayer/BBC Sport streams.</p><p>While you just want to watch the World Cup goals on our live streams, our goal is to verify that you have a valid BBC account which allows you to watch the BBC content that you love as quickly as possible. During the peak periods of the England vs Sweden game, we had around 24 times the amount of users signing in that we would normally expect during weekend usage, so our additional application resources help us to serve requests as quickly as we can. Our applications also help to support the BBC account mobile team, and their framework is used by all of the BBC mobile applications, such as iPlayer on your phone or tablet.</p><p>Once signed in, we authorise users to access various BBC services by means of an access token which, to ensure our and your security, refreshes on a regular basis. During the peak traffic of this game, we saw approximately 5 times the normal amount of traffic as people turned on their mobile devices, we refresh these access tokens and then you watch the World Cup goals quickly after they went in.</p><p>We try to be as unintrusive as possible so once we’ve signed you into your BBC account, we send you back to the page you clicked “sign in” from. For the World Cup and Wimbledon this was probably the BBC iPlayer or BBC Sport Live pages.</p><h3>Playback</h3><p>Every press of a play button triggers a call to our Media Selector API. This service uses a range of factors including the following to decide whether your device is authorised to play a stream, and if so, where it should play it from.</p><ul><li>Device type</li><li>Your location</li><li>The rights of the asset being requested</li><li>Current business rules for load balancing across CDNs based on capacity and performance</li></ul><p>You can find out more about how we built Media Selector 6, our current iteration in <a href="http://www.bbc.co.uk/blogs/internet/entries/02febc5a-a96b-410e-b896-47936e858b94">Henry’s post here</a>.</p><p>Media Selector generally serves between a few hundred and a few thousand requests every second, with regular but often unpredictable spikes in load. Before the World Cup, its traffic peak was when BBC News published a push notification after the <a href="https://www.bbc.co.uk/news/entertainment-arts-44097050">stage invasion at Eurovision</a>.</p><p>With the Media Selector response in hand, the device then selects a playback URL from the options provided to it by Media Selector, and starts streaming from a CDN.</p><p>In addition to our own CDN, <a href="http://www.bbc.co.uk/blogs/internet/entries/8c6c2414-df7a-4ad7-bd2e-dbe481da3633">BIDI</a>, the BBC engages with two commercial CDNs to be able to handle the users and bitrates our streams require. As well as providing huge amounts of network capacity and substantial cache offload, the CDNs also have a lot of intelligent logic that try to protect users from transient issues such as slow network paths to the content origin.</p><p>Each CDN has its own set of rules around retrying requests to our origin servers, timing them out, or trying to route around specific parts of the network. Tuning these rules is a very complex and nuanced process, where every timeout value changed needs to be considered in the form of what impact it will have on every other part of the distribution chain. In the event of a CDN having issues providing a stream to a user, our Media players Standard Media Player (SMP) and TAP will automatically attempt to use another CDN rather than showing an error to the user.</p><p>Each CDN has a quota of UK distribution capacity that they allocate to the BBC for use — how many bits they think they can push before performance will begin to degrade. Throughout the World Cup performance and capacity were reviewed across different platforms and services as we broke and re-broke our peak throughput records, regularly re-routing traffic whilst tweaking configurations as they hit new barriers that if not addressed would cause quality to degrade before we hit our total caps on the CDNs.</p><p>There is good news with capacity planning though: football fans are predictable. The number of users and amount of traffic delivered at kick-off for a football match is usually around a factor of <strong>√</strong>2 out from the amount of traffic we’ll be handling at full time. This essentially means that as soon as the timer starts, we know what, if any, traffic engineering will need to have taken place by half time to keep things running smoothly.</p><p>One exception to this rule has been UHD. We initially expected it to follow the same pattern but the traffic levels have been far flatter — it looks like the CTA which stated that there were a limited number of viewers allowed caused everyone to try and join early and stay on. Whilst for HD there’s a dip in concurrent viewers at half time whilst they pop the kettle on, UHD stays stable (perhaps because viewers know that if they close their UHD stream, they might not get back on after half time due to the limit on concurrent viewers).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*jr_vIkmT5-QXZxNI.jpg" /><figcaption>What goes on in the truck — not much different from normal HD production (Picture: Quinn Cowper)</figcaption></figure><h3>Digital 24/7 Operations</h3><p><em>What it’s like to be on shift — a personal insight</em></p><p>Operations, I like to think, is the glue that holds this whole process together. We are the eyes and ears of the BBC.</p><p>Prior to one of the matches starting, we prepare. And once, we’ve prepared, we prepare some more!</p><p>This includes checking the UHD encoders have started, Wimbledon &amp; World Cup promos have been scheduled ready to be displayed on iPlayer to chairing our tier 1 event stand-ups, ensuring our <a href="https://en.wikipedia.org/wiki/Traffic_light_rating_system">RAG status</a> is green across our broadcast, online &amp; network services &amp; sending out comms to all our stakeholders.</p><p>We constantly monitor.</p><p>Now, we love our monitoring in 24/7, as you can see from our monitoring wall image below!</p><p>We monitor everything from our content delivery network, live video &amp; audio output for iPlayer &amp; iPlayer Radio, live sport pages, RedButton and whole heap of other alarms, products &amp; services.</p><p>I work with very talented people in 24/7, some of whom have built bespoke monitoring tools to solve problems we have had here.</p><p>For example, for AWS Alarms we have LookOut. LookOut has a UI that utilises colours to help identify the state of alerts at a glance and exposes key elements to members of the Ops team, e.g. Wormhole links, SQS queue fetcher and it gives us the flexibility to customise visibility of alarms in the UI.</p><p>For monitoring our content delivery network we have Sawyer. A script that consumes the output of an API and performs calculations to determine the difference between the CDNs, compared against a customisable threshold. A UI that displays the desired metrics in a quick-to-digest dashboard view, with changing colours to draw attention to deviations between our third-party CDN providers — we like to avoid the buffer faces for our Audience Members!</p><p>Our monitoring tools ensure that we are able to identify &amp; resolve incidents fast!</p><p>And when the match is over?</p><p>Did I not mention we have another ~1,400 components that we support on top of the components being utilised for the World Cup and Wimbledon?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*6-HB4qPisFWwYyyD.jpg" /><figcaption>24/7 Operations Monitoring Wall (Picture: Sally Fletcher)</figcaption></figure><h3>Headline Stats</h3><p>Now that both World Cup and Wimbledon are over, and with that our special events UHD live trial, it’s probably good to reflect on the month. There were some initial hiccoughs — co-ordinating the CTAs with the Moscow team the impact of unprecedented traffic spikes on a new iPlayer homepage architecture and event based scaling of the underlying infrastructure.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/887/0*0vd-kl42eElqL0DJ.JPG" /><figcaption>A very calm central court before the storm of Wimbledon began (Picture: Nik Rahmel)</figcaption></figure><p>But by the end of it, we have broken several records multiple times:</p><ul><li>Over 5.5 Terabit/s in live video streams</li><li>3.8m concurrent streaming requests for the England vs Sweden match</li><li>15,000 requests for the VR version of the match stream</li><li>1.6 million requests for the UHD version over the course of the tournaments</li></ul><p>Of course there were some other problems — A large proportion of non-UHD viewers will have noticed the stream cutting out just before the end of the England vs Sweden, which was swiftly dealt with. Simon Thompson wrote another post <a href="http://www.bbc.co.uk/blogs/internet/entries/5c5b8f80-891d-4b51-babd-8814c1511b4e">summarising the teething problems we had and learned from with UHD</a>.</p><h3>Thanks</h3><p>Thanks to everyone involved in writing this blog post and to <a href="https://twitter.com/miss_jwo">Jenny Wong</a> on Twitter for suggesting that this would be an interesting topic. Thanks also to <a href="https://twitter.com/qwincowper">Quinn Cowper</a> the provide us with some of the picture</p><p>Ask the teams any questions and we’ll be happy to answer as many as we can.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=bc1bb95f450d" width="1" height="1" alt=""><hr><p><a href="https://medium.com/bbc-product-technology/bringing-the-bbcs-world-cup-wimbledon-online-coverage-to-you-bc1bb95f450d">Bringing the BBC’s World Cup &amp; Wimbledon online coverage to you</a> was originally published in <a href="https://medium.com/bbc-product-technology">BBC Product &amp; Technology</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Lambda Logs in ELK]]></title>
            <link>https://medium.com/bbc-product-technology/lambda-logs-in-elk-e4d924757249?source=rss-c3d7d944090c------2</link>
            <guid isPermaLink="false">https://medium.com/p/e4d924757249</guid>
            <category><![CDATA[lambda]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[elk-stack]]></category>
            <category><![CDATA[sre]]></category>
            <category><![CDATA[iplayer]]></category>
            <dc:creator><![CDATA[Nik Rahmel]]></dc:creator>
            <pubDate>Fri, 18 Jan 2019 16:26:47 GMT</pubDate>
            <atom:updated>2019-03-15T16:38:42.556Z</atom:updated>
            <content:encoded><![CDATA[<h4>How to ingest AWS Lambda Log Streams from CloudWatch into an ELK monitoring stack</h4><p>Anybody who uses AWS Lambda might have come across something like in the screenshot below when trying to access any kind of log, and felt emotions such as confusion, anger, and annoyance.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/928/1*6Vh7zerQSeEZctEwYsD1xw.png" /><figcaption>A Log Group in the AWS Console</figcaption></figure><p>Those emotions were probably even stronger if you have been spoilt by having an ELK stack for your all your centralised logging needs for all of your microservices — maybe even with some neat Kibana dashboards that allow you to dig into the data, filter it, and visualise it.</p><p>That’s how we felt anyway on the <a href="https://iplayer.engineering/the-tech-stack-that-powers-bbc-iplayer-471575318e2a">iBL team</a> (the iPlayer API team) — for the past 3.5 years all of our microservices sent their application and access logs into our Elasticsearch cluster, making monitoring and operational investigations a breeze. Since then we’ve started utilising AWS Lambda a lot more for various tasks — be it for serverless, decoupled integration with other AWS services, or to handle certain traffic requirements.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*EuA7J7ZphspS8r-rK8w3ig.png" /><figcaption>A lot of Elks — see <a href="https://www.elastic.co/elk-stack">elastic.co</a> for the whole story</figcaption></figure><p>But with serverless environments, there comes one issue: Not having access to the server environment. No journalbeat or filebeat for us to ship off logs to logstash, instead AWS just puts everything into CloudWatch logs, and by default not in the most accessible way. There are some teams in iPlayer who are fully utilising CloudWatch for their logs, and I’m sure that they enjoy just having Lambda logs delivered without much work, but after reviewing our requirements for iBL, we decided to stick with ELK for our main logging tool — among other things it is a lot cheaper to run our own ElasticSearch cluster.</p><p>In order to tie up Lambda logs into that, we have.. created another Lambda!</p><h3>The Pipeline</h3><p>CloudWatch Logs has a feature called <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/Subscriptions.html"><em>Subscriptions</em></a><em> </em>for “Real-time Processing of Log Data”, which is just what we want. It allows sending log events to a Kinesis Stream, Kinesis Firehose Stream, or a Lambda. It is created on a <em>log group</em>. Each log group contains a number of <em>log streams</em>. The screenshot above shows a number of <em>log streams</em> for a <em>log group — </em>and each Lambda gets its own <em>log group</em> created.</p><p>So what do we do now with this feature? For each of our existing Lambdas’ log groups, we create a subscription to send the Logs to the new Lambda, the <em>iBL CloudWatch Logs Processor</em>. As per the <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/SubscriptionFilters.html#LambdaFunctionExample">AWS Example Guide</a>, the event the Lambda gets invoked with looks something like this after parsing:</p><pre>{<br>  &quot;owner&quot;: &quot;123456789012&quot;,<br>  &quot;logGroup&quot;: &quot;/aws/lambda/ibl-example-lambda&quot;,<br>  &quot;logStream&quot;: &quot;2019/01/11/[28]cde6dd2161e44c61823b6b73d5e8471e&quot;,<br>  &quot;subscriptionFilters&quot;: [<br>    &quot;cloudwatch-logs-processor&quot;<br>  ],<br>  &quot;messageType&quot;: &quot;DATA_MESSAGE&quot;,<br>  &quot;logEvents&quot;: [<br>    {<br>      &quot;id&quot;: &quot;3195310660696698337880&quot;,<br>      &quot;timestamp&quot;: 1432826855000,<br>      &quot;message&quot;: &quot;{\&quot;eventVersion\&quot;:\&quot;1.03\&quot;,\&quot;userIdentity\&quot;:{\&quot;type\&quot;:\&quot;Root\&quot;}&quot;<br>    },<br>  ]<br>}</pre><p>From the <em>logGroup</em> key, we extract the function name, and create our payload:</p><pre>const { logGroup, logEvents } = logStream;</pre><pre>const payload = logEvents.map((event) =&gt; {<br>  return {<br>    function: getFunctionNameFromLogGroup(logGroup),<br>    timestamp: event.timestamp,<br>    message: event.message<br>  }<br>}</pre><p>On our logstash server, we have created a dedicated input on port 5090 to add a <em>lambda</em> type to the events. They get stored in our ElasticSearch cluster, alongside all other application logs, and are easily accessible through Kibana, so we end up with the following architecture:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*aEpvJdwt5IHgMKoMfdDpTg.png" /></figure><h3>Automating Subscriptions</h3><p>So far so good — but what when we create a new Lambda? We could add the Subscription to our CloudFormation templates that our <a href="https://yeoman.io/">yeoman</a> generator creates when we start a new lambda project.. or we can go a bit meta and have the CloudWatch Processor create those by itself!</p><p>Of course, we go meta.</p><p>We can use CloudWatch Events to invoke the Logs Processor every 30 minutes to check for any new Log Groups that need subscriptions created for. Using the AWS SDK, we get a list of all Log Groups and their existing subscriptions, filter out all those that already have subscriptions, and create subscriptions for the remaining ones. Our new architecture diagram does not look too different now:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*aHxKOuIsTbnheEW60wRACQ.png" /><figcaption>That font is BBC Reith Sans if anybody is wondering</figcaption></figure><p>One thing to be noted is, that we shall not create a subscription for the Log Group of the Logs Processor Lambda itself — every invocation would generate a log that it has been invoked, and in turn invoke it again. A neverending cataclysm of Lambda invocation would not make for a pleasant experience!</p><h3>The End Result</h3><p>Getting all log events pumped straight into ElasticSearch allows us to create dashboards in Kibana like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PonzgbCCgohYikyhtRT18A.png" /><figcaption>A Kibana visualisation of the number of all log entries of selected Lambdas</figcaption></figure><p>Or we can find all log messages from a select Lambda containing the name of a service we’re investigating:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jCrJqtYmGh0Gr7R9j-s47w.png" /><figcaption>Kibana Discovery of all log entries for the Lambda ‘ibl-tag-extractor-live’ containing ‘mediaselector’ in the past 24 hours</figcaption></figure><p>Of course, there are various alternatives to this — AWS CloudWatch now supports <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/AnalyzingLogData.html">Logs Insights</a> for more effective querying, and Grafana is about to release <a href="https://grafana.com/loki">Loki</a>, which is much simpler and does not require an ElasticSearch cluster.</p><p>Let us know if you have any thoughts about the above, or if you fancy using or improving it yourself, why not <a href="https://careerssearch.bbc.co.uk/jobs/job/Software-Engineer-iBL-AWS-node-graphQL-Restful-TDD-iPlayer/34164">join us</a>?</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e4d924757249" width="1" height="1" alt=""><hr><p><a href="https://medium.com/bbc-product-technology/lambda-logs-in-elk-e4d924757249">Lambda Logs in ELK</a> was originally published in <a href="https://medium.com/bbc-product-technology">BBC Product &amp; Technology</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How an iPlayer API engineer delivers platform features across platforms]]></title>
            <link>https://medium.com/bbc-product-technology/how-an-iplayer-api-engineer-delivers-platform-features-across-platforms-f0b508dcf7b3?source=rss-c3d7d944090c------2</link>
            <guid isPermaLink="false">https://medium.com/p/f0b508dcf7b3</guid>
            <category><![CDATA[iplayer]]></category>
            <category><![CDATA[collaboration]]></category>
            <category><![CDATA[product-management]]></category>
            <category><![CDATA[api]]></category>
            <category><![CDATA[cross-platform]]></category>
            <dc:creator><![CDATA[Nik Rahmel]]></dc:creator>
            <pubDate>Wed, 02 Jan 2019 15:13:34 GMT</pubDate>
            <atom:updated>2019-03-15T16:38:20.580Z</atom:updated>
            <content:encoded><![CDATA[<h3>How an iPlayer API engineer delivers features across platforms</h3><h4><strong>API Engineers as Consultants in collaboration with user-facing product teams</strong></h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bDnjB2AtIyIq8wM5LF3nIA.png" /></figure><p>iBL stands for <em>iPlayer Business Layer —</em> yes, we are ‘the API team’, but we are also the team that implements any shared business logic that affects iPlayer on any of the 3 key platforms (TV, Mobile and Web).</p><p>As an engineer in the iBL team, we’ve got great responsibility in maintaining a robust and resilient API. We are also very closely integrated with the audience facing teams to drive the product forward. We consult the teams on how the data works, and what impacts their decisions will have on the rest of the product.</p><p>Recently I have been consulting one of our audience facing teams on a new feature — from discovery to implementation, I wrote very little code and helped deliver a major milestone for iPlayer.</p><p>This is the story of how we go about embedding engineers as consultants in other teams.</p><h3>iPlayer Product Teams</h3><p>iPlayer runs on many platforms — we have the <a href="https://www.bbc.co.uk/iplayer">website</a>, the mobile App on the <a href="https://play.google.com/store/apps/details?id=bbc.iplayer.android&amp;hl=en_GB">Google Play Store</a>, <a href="https://itunes.apple.com/gb/app/bbc-iplayer/id416580485?mt=8">Apple AppStore</a> (which includes Apple TV) and <a href="https://www.amazon.co.uk/Media-Applications-Technologies-Ltd-iPlayer/dp/B009SPY7PW/ref=sr_1_1?s=mobile-apps&amp;ie=UTF8&amp;qid=1462983815&amp;sr=1-1&amp;keywords=bbc+iplayer">Amazon Store</a>, and of course connected TV devices, set-top boxes and other syndicated platforms such as Virgin and SkyQ.</p><p>We have many product teams across all the major platforms from connected TV to Mobile to Web. All the teams are cross-discipline teams with engineers, testers, UX Designers, Project Managers. Each product teams are all consumer/clients of the API, the team I work in, called iBL. Kit wrote a <a href="https://iplayer.engineering/the-tech-stack-that-powers-bbc-iplayer-471575318e2a">nice post</a> detailing the tech stack behind all of this.</p><h3>iBL — The product API</h3><p>A lot of user-experience defining features on iPlayer can be categorised as data-driven or UI-driven.</p><p>To give an example, guidance warnings on certain episodes. Some episodes have warnings about <em>strong language</em> or similar. The way this message is communicated to the user is primarily a UI feature, but the actual message is data feature — and data features like these are decided in iBL. Which episodes have guidance warnings, what the message is, all this comes from iBL, and we can change it. But any change we make to the data need to be understood by the clients.</p><p>In this post I will go into some detail about how iBL works with the platform teams, and other supporting engineering functions within the BBC.</p><h3>The product question</h3><p>The specific question we want to answer is: What impact does changing the promotions on the top have for a certain age range?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*AEw5l9QQqP9ekYrHdRQfbA.png" /><figcaption>The current non-personalised ‘editorial bundle’ in the top row of iPlayer</figcaption></figure><p>Specifically, we want to explore, deliver and measure the impact of a personalised experience for the ‘youth segment’ of 16–34 year old users who have opted-in to personalisation. This requires teamwork, not just from the product teams above, but also the editorial curation and strategy teams.</p><p>Why does iBL need to be involved in this to consult? Users sign on in the clients, not in iBL, so the clients can probably figure out the users&#39; profile without help from iBL. But this is where it stops — the clients would need to know what a different age group <em>means</em>. They need to know what episodes are promoted, where the promotion comes from, how the promotions are scheduled. All of this is <em>data</em>. <em>Data</em> that is in iBL, <em>data</em> that is part of the cross-platform business logic.</p><p>However, iBL can’t just make the decision to serve another feed to the youth audience — we do not have the resources to analyse the best ways this feature works with the rest of the user experience. Getting a product team to look after this means there are UX, product, and data analysis resources to prove the hypothesis.</p><h3>The Data and the product</h3><p>As first step to a more personalised experience in iPlayer, we have spent the past year revamping how the homepage is assembled. We now have one GraphQL API that returns a highly personalised view.</p><p>This really is a change to be implemented in iBL. However, iBL is a team consisting of four engineers — the product managers are embedded in and managing the product teams. This is a product feature, and needs input from disciplines other than engineering — product, UX, editorial and our data analysts.</p><p>This is why we decided to ‘open up’ iBL to contributions from client teams — they know the product, the audience and the user needs, and we can facilitate this collaboration. The ‘iBL Consultant’ is born!</p><h3>Consulting</h3><p>As a consultant, our responsibilities are beyond code, and sometimes do not even include any coding at all. There are three main pillars:</p><ul><li><strong>Supporting Discovery</strong><br>This includes advising on what’s possible — client teams, including engineers, are not usually aware of the capabilities of the platform built by iBL. Working with the client team in workshops to define the feature and elicit requirements.</li><li><strong>Technical Delivery</strong><br>Providing technical support to the client teams — if it is a feature for which iBL provides a ‘starter kit’, say a new bundle based on existing feeds, the client team may decide to have it implemented by their engineers. Alternatively, we may decide to run a developer exchange of either an iBL engineer into a client team, or a client team’s engineer into iBL to pair on aspects of the feature.</li><li><strong>Delivery/Project Management</strong><br>The iBL roadmap is rarely empty, and iPlayer project managers need to keep informed about what is happening. As consultant, we manage prioritisation of any work relating to the feature within iBL and ensure deliverables are aligned across all platforms with the lead project manager for iPlayer.</li></ul><p>It should be noted that, once delivered, ownership of the code lies with iBL.</p><h3>The Youth Bundle</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XqfTNj3BAFTAS9XhCJeR5w.jpeg" /><figcaption>The sunny day I went up to MediaCityUK. The iPlayer teams sit in the building off to the right.</figcaption></figure><p>At the beginning of this project, the objective was to create a personalised homepage experience for 16–34 year olds, which can be A/B tested. The scope of the ‘experience’ was not yet defined. That’s where the discovery session kicks in! The Editor of iPlayer, the Editorial Manager, and me as a consultant make their way up North to create that definition with the Mobile product team, and their ‘Ada’ crew of engineers.</p><p>Since this was going to be the first time we are segmenting the audience in iBL, we wanted to keep it fairly basic. Some of the main questions we tried to answer:</p><ul><li><strong>What is the experience going to be like?</strong><br>Change the whole page? Add a new bundle? Modify an existing bundle?</li><li><strong>How do we curate this new experience?<br></strong>Is it all manually selected? Can we use existing data (e.g. channel, genre, category) to select the content automatically? Can it be semi-automatic where the editorial team can define weightings or priorities? How much effort will this be, both upfront in engineering cost, and to keep it running editorially?</li><li><strong>What timeline are we operating on?<br></strong>Are there any big events coming up? Are there any big releases coming up?</li><li><strong>How do we build this thing?<br></strong>Are we re-using existing feeds? Do we need new feeds? Who else do we need to collaborate with in order to deliver this? Does iBL need to do any work, or can the client team deliver this themselves?</li><li><strong>How do we measure the impact?<br></strong>Whilst we have A/B testing on certain platforms as <a href="https://iplayer.engineering/@andymsuk">Andy</a> explained in a <a href="https://iplayer.engineering/optimising-the-iplayer-experience-294568dcf404">previous post</a>, extending this to measure <em>the same thing on all platforms </em>equally is a bit more difficult.</li></ul><p>There were introductions, hypotheses, post-its, a big whiteboard matrix, KPI definitions, edge cases and much more. The product team’s Business Analyst, Project Manager, Product Owner and Engineers, who are used to defining mobile app features and not data feeds, had to wrap their heads around a whole new world of workflows — and how it all works when you translate it to the website or TV devices. This is where I come in as a consultant from iBL, with experience of all that, and knowledge of the respective implications of the above questions on it.</p><p>Eventually, it was decided that we will modify the existing ‘Featured’ bundle with a specifically curated feed for the target audience segment. We also decided that we will run an A/B test on the new feature to assess any impact on key metrics such as consumption.</p><p>We also had to liaise with another team in the Online Production Systems group to create the new promotion destination in our editorial tools.</p><p>Since this was the first team we make use of segmentation in production, we also had to define an architecture for incorporating that in iBL — this was a discussion I had with the rest of iBL team.</p><p>Once all of that was ironed out, we could get started on modifying the existing editorial bundle. For that one of the mobile engineers, who works usually on Android in Java and Kotlin, joined us to implement this in our Typescript codebase.</p><p>We enabled the new feed earlier this year, and luckily I fall into the variant segment, so for me the Editorial bundle looks like this now:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Dnl6be3vuCK9yaMK_nf42w.png" /><figcaption>The personalised version of this bundle for someone in the 16–34 year old segment</figcaption></figure><h3>Results and next steps</h3><p>As for the feature, this was kept split for a couple of weeks, to observe the impact this has on audience behaviour.</p><p>Our data analysts then have some fun crunching numbers, and as outcome we have now rolled this out to the whole audience in the 16–24 age group only as this saw the highest positive impact.</p><p>We are also working on our technical capabilities to make testing a feature like this easier in the future. In the future, segments may not be as easily defined, or a lot more dynamic, so we are working to establish one common A/B (or even more variants) workflow for testing the impact of different bundles and promotions across all platforms which can be used as easily as the feature A/B tests we run on individual platforms already. This is a different challenge, and I’m sure there will be a different post about that at some point.</p><h3>Engineers as Consultants?</h3><h4><em>Does it work?</em></h4><p>For this feature, it worked. <em>Why?</em></p><p>First of all, the product team didn’t just ‘look after it’, they made it their priority for the quarter. The product manager, BA, and project manager wanted to get results, and understood the need for iBL consultation and cross-platform approach.</p><p>The engineers in the client team were keen to get involved and work with iBL, and I was also motivated by the impact of consulting on this. In terms of UX, the team could design and effectively conclude their hypotheses of how it should work and feel.</p><h4>Did anything not work?</h4><p>We had some issues with the data analysis — the client teams do not have their own embedded data analysts beyond the BAs, so it wasn’t clear who was responsible for this. The teams are also split between MediaCity near Manchester, and White City in London.</p><p>Once this was clear, we could analyse the data and had a conclusion. But to improve this in the future, we are starting an iPlayer-specific data team based in London, ‘Data Force’, which you’ll probably hear about in another blog post. They have a logo and latin motto and everything..</p><p>I also believe that team and people dynamics play a big role — it needs to be the right feature, and it needs to have buy-in from the product team and the consultant. In this role, the consultant is working with people they may not usually work with, so it’s even more important to have something interesting to work towards.</p><h3>Conclusion</h3><p>I hope this gives an insight into how we tackle cross-platform collaboration at iPlayer, and that the life of a “backend engineer” in iBL can be a lot more exciting than one might think — Educating and Informing Discovery is very much in the BBC spirit as well.</p><p>If this environment sounds like something you want to be a part of, maybe there is <a href="https://careerssearch.bbc.co.uk/jobs/job/Software-Engineer-iBL-AWS-node-graphQL-Restful-TDD-iPlayer/34164">a role open that suits you</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f0b508dcf7b3" width="1" height="1" alt=""><hr><p><a href="https://medium.com/bbc-product-technology/how-an-iplayer-api-engineer-delivers-platform-features-across-platforms-f0b508dcf7b3">How an iPlayer API engineer delivers platform features across platforms</a> was originally published in <a href="https://medium.com/bbc-product-technology">BBC Product &amp; Technology</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Optimising iPlayer Translations]]></title>
            <link>https://medium.com/@nik.rahmel/optimising-iplayer-translations-ec0e74b45118?source=rss-c3d7d944090c------2</link>
            <guid isPermaLink="false">https://medium.com/p/ec0e74b45118</guid>
            <category><![CDATA[iplaye]]></category>
            <category><![CDATA[ec2]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[optimisation]]></category>
            <category><![CDATA[profiling]]></category>
            <dc:creator><![CDATA[Nik Rahmel]]></dc:creator>
            <pubDate>Fri, 07 Dec 2018 16:05:54 GMT</pubDate>
            <atom:updated>2018-12-07T17:03:55.381Z</atom:updated>
            <content:encoded><![CDATA[<p><em>Written by Nick Spragg and Nik Rahmel</em></p><p>Over the past year, we have started personalising the iPlayer homepage a lot more than we did previously — with this came a re-architecture of the API.</p><p>All the data any of the iPlayer clients require to show the complete homepage, be it in the browser, on the TV or in the mobile app, can be requested with one request to the underlying GraphQL API.</p><p>Each response is personalized to the user — it includes their currently watched shows, their ‘added’ list and recommendations tailored to their watching behaviour.</p><figure><img alt="" src="https://cdn-images-1.medium.com/proxy/1*xhay4cqJGHNwmrsBO7msTQ.png" /><figcaption>The personalised homepage — the top rail is promoting content relevant to the user, and it shows the user’s progress on recently watched items. All of this comes from one API request</figcaption></figure><p>All this personalisation makes each response unique and difficult to cache on the edge caching layer.</p><p>Where previously we could utilise a simple cache layer built using Varnish for a few hundred different homepage variations, this has become impossible with the new architecture.</p><p>The computational overheads for each request have increased massively, which requires us to make our code as efficient as possible and optimise any CPU intensive tasks. During initial development, we didn’t meet our prototype performance expectation to handle 3,000 requests per second without breaking the bank, so we started to investigate where our bottlenecks lie.</p><figure><img alt="" src="https://cdn-images-1.medium.com/proxy/0*pHUlo9sCbpxrO5X_.png" /><figcaption>Profiling overview for assembling the homepage</figcaption></figure><p>Thanks to the excellent debug functionalities provided in current versions of Node.js (we are running a TypeScript project on Node.js 8), and the Javascript debug tools in Chrome, simple profiling identified the task that takes the most CPU time in our average requests: Translations.</p><p>Translations have been a part of iPlayer for a long time — the website is localised to English, Welsh, Scots Gaelic and Irish Gaelic, with hundreds of pre-set translation strings, and all of these get applied depending on the user in the translations module of this API.</p><p>Within the iPlayer API, we use a translation module to convert text (or parts of the text) into a specified language. This is achieved with templated strings. For example, a templated string like “#{available-for} 26 #{days}”, would become “Available for 26 days” in English or “Ar gael am 26 o ddyddiau” in Welsh.</p><figure><img alt="" src="https://cdn-images-1.medium.com/proxy/0*XzXeLEeiNBTCBR1i.png" /><figcaption>Profiling results: Timings per function</figcaption></figure><p>The module to do this is relatively straightforward. It exports a <em>translate</em> function which accepts a templated string and locale code. Translate parses the string for <em>translation keys</em> and looks up each key in the corresponding translation lookup. For example, a Welsh translation (cy), `available-for` maps to `Ar gael am`. There are approximately 100 translations per language.</p><p>Translation keys in a string are represented using the following syntax: ${example-translation-key}.</p><p>Here is an example of an English and Welsh :</p><pre>const templatedString = &#39;#{available-for} 26 #{days}&#39;;</pre><pre>const toEnglish = translate(template, &#39;en&#39;);<br>console.log(toEnglish) // &quot;Available for 26 days&quot;</pre><pre>const toWelsh = translate(template, &#39;cy&#39;);<br>console.log(toWelsh) // &quot;Ar gael am 26 o ddyddiau&quot;</pre><p>Given the levels of caching within the iPlayer architecture, modules like this have often been written to favour simplicity, testability and accuracy rather than performance.</p><p>Here is the original translate function:</p><pre>const TRANSLATIONS = loadTranslations();<br>const KEY_REGEX = /#{(.*?)}/g;</pre><pre>function translate(value, language = &#39;en&#39;) {<br> let translated = value;<br> Object.keys(TRANSLATIONS[language]).forEach((key) =&gt; {<br> const translation = TRANSLATIONS[language][key];<br> const re = new RegExp(`#{${key}}`, &#39;g&#39;);<br> translated = translated.replace(re, translation);<br> });</pre><pre>return translated;<br>}</pre><p>This function will translate zero or more translations templates in a given string. It works, and it’s very simple but it’s inefficient. Any ideas? Well, for the specified language it attempts to translate <em>every</em> <em>key </em>in the translation lookup. Based on the way this module is currently used within iPlayer, each call will translate between 0 and 3 translations. In a worst-case scenario, it will attempt to make approximately 100 template translations even if a string doesn’t have any.</p><p>A potential optimization is to only perform the required number of translations for a given string:</p><pre>const TRANSLATIONS = loadTranslations();<br>const KEY_REGEX = /#{(.*?)}/g;</pre><pre>function translate(value, language = &#39;en&#39;) {<br> if (!value) {<br> return value;<br> }</pre><pre>let translated = value;<br> let match;<br> while ((match = (KEY_REGEX.exec(value)))) {<br> if (match) {<br> const translation = TRANSLATIONS[language][match[1]];<br> if (translation === undefined) {<br> continue;<br> }<br> const key = &quot;#{&quot; + match[1] + &quot;}&quot;;<br> translated = translated.replace(key, translation);<br> }<br> }<br> return translated;<br>}</pre><p>From strings containing between 0 and 3 translations, benchmarks indicated that selectively replacing translations keys proved significantly quicker. Of course, it has a couple of drawbacks. First, although not rocket science it does add further complexity. Secondly, the performance plummets when a string has 4 or more keys due to the while loop.</p><p>Further inspection of the Javascript string replace method signature suggested that a replace function can be supplied as a second parameter. This replacement function will be invoked after each translation is found.</p><p>Here is a variant of the translation function using “string.replace” with a supplied replacement function:</p><pre>const TRANSLATIONS = loadTranslations();<br>const KEY_REGEX = /#{(.*?)}/g;</pre><pre>function translate(value, language = &#39;en&#39;) {<br> if (!value) {<br> return value;<br> }</pre><pre>return value.replace(KEY_REGEX, (a, b) =&gt; {<br> const translation = TRANSLATIONS[b];<br> if (translation === undefined) {<br> return a;<br> }</pre><pre>return translation;<br> });<br>}</pre><p>Arguably a more elegant solution. As a rule of thumb, it often pays dividends to reuse standard libraries or trusted third party solutions rather than rolling out your own solutions. Time has already been invested by talented engineers to optimize and make the code robust. Furthermore, standard libraries are officially supported and will be well tested.</p><p>This was evident is the benchmarks; this solution proved to be as fast (approximately) as the first optimisation for 0–3 translations but dramatically quicker for 4 or more.</p><p>One of the main objectives when it comes to optimisations like these is the user experience, and serving the majority of requests as quickly as possible.</p><p>In order to validate our changes, we run load tests on a controlled test environment. Alongside the profiling detailed above, this informs our prioritisation of potential optimisations.</p><p>For our initial validation, we had to find the current breaking point. We use <em>G</em><a href="https://gatling.io/"><em>atling</em></a> as a load testing tool, with a script that can make personalised requests for any number of user profiles and client configurations. We can pick to test with a representative split of the audience, majorly favouring the English language, or an even split of all our supported languages.</p><p>Since our objective was to improve the CPU cost of translations, and not the way we store or cache the results, this is largely irrelevant, and we tested with an even language split. We picked 2 instances of a relatively small machine type, and a relatively low number of users, 30; each making up to 1 request per second.</p><p>Looking at the response times over time, we get the following graph:</p><figure><img alt="" src="https://cdn-images-1.medium.com/proxy/0*iGjdzw7pdEhmP7sR.png" /></figure><p>Here we see that response times start out stable and with low variations in response times, but by the time we reach 20 or so users they reach over one second, which results in us not reaching our goal of 30rps as each user has to wait for their request to finish before they can make a new one. However, the API does not fall over completely — there is a much more noticeable slow down at around 12:00:20, but it recovers itself.</p><p>With our first iteration of algorithm improvements, we get a much more stable graph with the same test setup:</p><figure><img alt="" src="https://cdn-images-1.medium.com/proxy/0*_1UGdaX4hQ9lXLB_.png" /></figure><p>There are minutely spikes in response times for the upper percentiles, which are due to some cache refreshes, and irrelevant to this. We have made improvements! But what is our new limit?</p><p>For our second round of testing, we change the test setup: We’re only using one machine to run the API now, and we increase the number of users iteratively until we find the new breaking point. This is the graph for 50 users:</p><figure><img alt="" src="https://cdn-images-1.medium.com/proxy/0*3O6iHmfxQgL-Dbnz.png" /></figure><p>We can ignore the minute spikes again as before.</p><p>As before, when we reach around 40 users, the slow down becomes very pronounced, and looking at the profiling, it seems like we can make some more improvements — translate still takes up a majority of CPU time for our responses, and we’d like to squeeze every little bit of performance out of this.</p><p>After the second round of improvements, with the same test set up, our graph looks like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/proxy/0*4lG9zDMc_26uEPTI.png" /></figure><p>It looks like it starts to slow down again at 20 users, but if we look at the left y-axis, we can see that the range of slowdowns is much lower, and within the acceptable range for this load test.</p><p>Profiling now shows us that translate is not the biggest CPU expense now, and in combination with the graph above, it is time to look at where some of the patterns in spikes come from. But this is for another post!</p><p><em>Originally published at </em><a href="https://iplayer.engineering/optimising-iplayer-translations-604513fb8963"><em>iplayer.engineering</em></a><em> on December 7, 2018.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ec0e74b45118" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Scraping Wordpress — Step by Step]]></title>
            <link>https://medium.com/@nik.rahmel/scraping-wordpress-step-by-step-dbb605fc2101?source=rss-c3d7d944090c------2</link>
            <guid isPermaLink="false">https://medium.com/p/dbb605fc2101</guid>
            <category><![CDATA[wordpress]]></category>
            <category><![CDATA[web-scraping]]></category>
            <category><![CDATA[expose]]></category>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[Nik Rahmel]]></dc:creator>
            <pubDate>Sun, 30 Sep 2018 21:59:25 GMT</pubDate>
            <atom:updated>2018-09-30T22:27:56.009Z</atom:updated>
            <content:encoded><![CDATA[<h3>Scraping Wordpress — Archiving Journalism Step by Step</h3><h4>Using Node.JS to build a web scraper from scratch</h4><p>My time at university was defined by a very large part by my involvement in Student Media — the student newspaper, the student TV station, radio station. I still work with the Student Radio Association (see you at the awards in November?) and the National Student Television Association to this day.</p><p>Last week, my student paper announced that they will delete all posts from their website from 2015 and older, so I thought I’d see if I can create my own little backup. Obviously, having graduated some years ago, I do not have a valid login anymore, so I had to figure out my own way to scrape the data.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rihiZRUg9dckYVrxMZy94w.jpeg" /></figure><h3>The Headlines</h3><p>Before I come to the technicalities, here is what some basic data analysis of the scraping shows:</p><p><strong><em>How many posts will be deleted?</em></strong><em><br></em>Analysing the posts accessible, we’re looking at around <strong>58% of all posts to be deleted</strong>.<br>This is the number of posts published per year:<br>2014: 1883<br>2015: 1903<br>2016: 1388<br>2017: 900<br>2018: 471</p><p>Interestingly, the website was set up around 2011/2012. There was a provider migration from wordpress.com to a 3rd party around 2014. This analysis and backup is only based on the current website.</p><p><em>Note that during my time it was practice to publish all posts from the print edition online, as well. I do not know if this is still current practice, but it would explain the higher numbers for 2014/2015.</em></p><p><strong><em>How much data will be freed?<br></em></strong>For a lot of the older posts, the related images are already removed or not accessible in one way or another, so the majority of images is from more recent years.<br>Removing all from 2015 and earlier will result in around <strong>13% less space</strong> taken up by locally hosted media:<br>2014: 7.08 MB<br>2015: 740.75 MB<br>2016: 3.39 GB<br>2017: 1.2 GB<br>2018: 542.67 MB<br>Total Size: 5.84 GB</p><p>So all in all, this will <strong>remove &lt;1 GB of publicly accessible data</strong> — obviously, I do not know how much data there is, that isn’t exposed in the post content or part of other pages in the system.</p><p><strong><em>The cost<br></em></strong>All my analysed data is taking up in zipped form around <strong>4.8 GB</strong>. I have uploaded a copy to an S3 bucket which is going to cost me around <strong>£1.02/year</strong> at current exchange rates.</p><p>I am pretty sure that the storage class they have to host the site allows for 20 GB, so I am a bit surprised that there seem to be less than 5 GB of content accessible whilst reaching the limit. I don’t know if the actual posts count towards the allowance, but my raw JSON file containing the post content and basic metadata weighs in at around 50 MB for the whole site.</p><p>I know Student Unions don’t have much money, and also not the resources to support custom-built solutions, but I am still disappointed that it has come to this — I am very interested to see what impact tomorrow’s planned deletion actually has, but the communication of the actual issue at heart has not been very thorough.</p><h3>The Technicals</h3><p>I am just interested in the actual post data and media content related to the posts, not the whole rendered output. I am also not a fan of HTML scraping, it always seems to end up messy even if you have a well-structured site — and with all the changes the new editorial teams have made throughout the years I have no idea how structured the site was anymore.</p><p>Searching for Wordpress Backup or Wordpress Scraping didn’t really yield any valuable results without having access to the Admin interface or database, so I took it as a bit of a challenge to write some code.</p><h3>RSS Lives</h3><p>I had a hunch as to how I can get standardised post data — good ol’ trusty RSS. I still use RSS daily; <a href="https://feedly.com/">Feedly</a> is my second most used website, and trying out /feed on the site gave me a nice bit of XML. It contained 10 posts. Great. At least they seemed to be full versions and not abridged. Yay!</p><p>I was expecting there to be thousands though, and there were no pagination indicators as per <a href="https://www.ietf.org/rfc/rfc5005.txt">RFC 5005</a>. Back to the drawing board? No!/feed/?page=2 showed me the second page. Thanks to <em>user2611 </em>on the Wordpress StackExchange for that tip!</p><h4>The feed</h4><p>Through some trial and error, I found out that there are 655 pages of RSS to paginate through, resulting in around 6,500 posts.</p><p>With this knowledge it’s fairly trivial to get the content of the complete feed:</p><pre>const request = require(&#39;request-promise&#39;);<br>const bluebird = require(&#39;bluebird&#39;);<br>const fs = require(&#39;fs&#39;);</pre><pre>const PAGES = 655<br>const HOST = &#39;http://example.com&#39;</pre><pre>async function getPage(i) {<br>  return request(`${HOST}/feed/?paged=${i}`);<br>};</pre><pre>const pages = bluebird.map(Array(PAGES), (v, i) =&gt; {<br>  console.log(`Getting Page ${i}`);<br>  return getPage(i);<br>}, {concurrency: 5});</pre><pre>pages.then((res) =&gt; {<br>  fs.writeFileSync(&#39;rss.json&#39;, JSON.stringify(res));<br>});</pre><p>This gives me a JSON file containing an array of strings which represent the pages of the RSS feed</p><h4>The posts</h4><p>In the RSS pages there is a lot of data I don’t need — XML is quite verbose, and a lot of the metadata is not related to the posts. I used a nice little library called <a href="https://www.npmjs.com/package/rss-parser">rss-parser</a> to extract the actual posts from the pages:</p><pre>const _ = require(&#39;lodash&#39;);<br>const fs = require(&#39;fs&#39;);<br>const Parser = require(&#39;rss-parser&#39;);<br>const parser = new Parser();</pre><pre>const rsss = require(&#39;./rss.json&#39;);</pre><pre>const itemsP = rsss.map(async (rss) =&gt; {<br>    const feed = await parser.parseString(rss);<br>    return feed.items;<br>});</pre><pre>Promise.all(itemsP).then((itemsA) =&gt; {<br>    const items = _.flatten(itemsA); // without this, we&#39;d have an array of pages containg an array of posts<br>    console.log(`Saving ${items.length} posts`);<br>    fs.writeFileSync(&#39;items.json&#39;, JSON.stringify(items));<br>})</pre><p>This gives me a JSON file containing an array of post objects — including relevant metadata for content , creator , title , link ,date , categories and guid.</p><p>To make them more easily browsable I’ve split them up into their own files each; using the guid , padded to a common length, as filenames:</p><pre>const fs = require(&#39;fs&#39;);<br>const items = require(&#39;./items&#39;);</pre><pre>function pad(num, size) {<br>    let s = num.toString());<br>    while (s.length &lt; size) s = `0${s}`;<br>    return s;<br>}</pre><pre>items.forEach(item =&gt; {<br>    const id = item.guid.split(&#39;=&#39;)[1]; // extract ID from full URL<br>    const idNumber = parseInt(id);<br>    const idFixed = pad(idNumber, 8);<br>    fs.writeFileSync(`./items/${idFixed}.json`, JSON.stringify(item, null, 2));<br>});</pre><p>Let’s analyse some of this — how many posts are there per year?<br>I used the infamous <a href="https://momentjs.com/">moment.js</a> to find the year for each item with the following code:</p><pre>const posts = require(&#39;./items&#39;);<br>const moment = require(&#39;moment&#39;);</pre><pre>const years = {}</pre><pre>function getYear(post) {<br>    if (!post.isoDate) throw new Error(&#39;No Date found&#39;);<br>    const postDate = moment(post.isoDate);<br>    return postDate.year();<br>}</pre><pre>posts.forEach(post =&gt; {<br>    const year = getYear(post);<br>    if (!years[year]) years[year] = 0;<br>    years[year]++;<br>});</pre><pre>Object.keys(years).map(year =&gt; {<br>    console.log(`${year}: ${years[year]}`);<br>});</pre><p>And these are the stats we find if you skipped the intro:</p><pre>2014: 1883<br>2015: 1903<br>2016: 1388<br>2017: 900<br>2018: 471</pre><h4>The media</h4><p>Photos and other imagery make out a large part of the posts, so I wanted to find all media links the posts mentioned.</p><p>Let’s start with all URL’s mentioned in the post content. I used another library that can comb through any string and identifies URLs, <a href="https://www.npmjs.com/package/get-urls">get-urls</a>:</p><pre>const posts = require(&#39;./items.json&#39;);<br>const fs = require(&#39;fs&#39;);<br>const _ = require(&#39;lodash&#39;);<br>const getUrls = require(&#39;get-urls&#39;);</pre><pre>const allUrls = new Set();<br>posts.forEach((post) =&gt; {<br>    const values = [post[&#39;content:encoded&#39;], post.content, post.contentSnippet];<br>    const strings = _.compact(values).join(&#39; &#39;);<br>    const urls = getUrls(strings);<br>    urls.forEach(url =&gt; {<br>        allUrls.add(url);<br>    });<br>});</pre><pre>fs.writeFileSync(&#39;allUrls.json&#39;, JSON.stringify([...allUrls], null, 2));</pre><p>I also noticed that quite of them didn’t resolve to valid files, and a status code 404 was returned from the host, which I also wanted to filter out.<br>Since I have to do requests for this anyway, I thought it might be nice to figure out how much storage these files actually take up, so for each valid URL I also recorded the reported file size:</p><pre>const allUrls = require(&#39;./allUrls&#39;);<br>const _ = require(&#39;lodash&#39;);<br>const bluebird = require(&#39;bluebird&#39;);<br>const fs = require(&#39;fs&#39;);<br>const request = require(&#39;request-promise&#39;);</pre><pre>const storedUrls = allUrls.filter((url) =&gt; {<br>    const isCorrectHost = url.includes(&#39;example1.com&#39;) || url.includes(&#39;cdnbucket.com&#39;);<br>    const isUpload = url.includes(&#39;/wp-content/uploads&#39;)<br>    return isCorrectHost &amp;&amp; isUpload;<br>});</pre><pre>let counter = 0; // To store and display progress<br>const urlsAndSizes = bluebird.map(storedUrls, async (url) =&gt; {<br>    let res;<br>    try {<br>        res = await request(url, {<br>            method: &#39;HEAD&#39;,<br>            resolveWithFullResponse: true<br>        });<br>    } catch (error) {<br>        if (error.statusCode === 404) return;<br>    };<br>    counter++;<br>    const percentage = ((counter/storedUrls.length) * 100).toFixed(2)<br>    console.log(`Finished ${percentage}%`);<br>    return {<br>        url,<br>        size: res.headers[&#39;content-length&#39;]<br>    };<br>}, {concurrency: 5});</pre><pre>urlsAndSizes.then((urls) =&gt; {<br>    const validurlsAndSizes = _.compact(urls); // remove 404s<br>    const fileContent = JSON.stringify(validurlsAndSizes, null, 2);<br>    fs.writeFileSync(&#39;validUrlsAndSizes.json&#39;, fileContent);<br>});</pre><p>This gives me a JSON file containing an array of objects with urls and their reported file size in bytes. Let’s analyse the data!</p><p>Again, I am interested in a storage cost breakdown by year — does removing content from 2015 and before actually result in substantial savings?<br>Similar to the post analyses above, we can make use of the previously extracted media URLs:</p><pre>const filesize = require(&#39;filesize&#39;);<br>const objects = require(&#39;./validUrlsAndSizes&#39;);</pre><pre>const years = {}<br>let totalSize = 0;</pre><pre>function getYear(url) {<br>    const split = url.split(&#39;uploads/&#39;);<br>    if (split.length != 2) throw new Error(&#39;Invalid URL&#39;);<br>    return split[1].slice(0, 4); //the year in the upload path<br>}</pre><pre>objects.forEach(medium =&gt; {<br>    const year = getYear(medium.url);<br>    if (!years[year]) years[year] = 0;<br>    totalSize += parseInt(medium.size);<br>    years[year] += parseInt(medium.size);<br>});</pre><pre>Object.keys(years).map(year =&gt; {<br>    console.log(`${year}: ${filesize(years[year])}`);<br>});</pre><pre>console.log(`Total Size: ${filesize(totalSize)}`)</pre><p>This is what we get for this:</p><pre>2014: 7.08 MB<br>2015: 740.75 MB<br>2016: 3.39 GB<br>2017: 1.2 GB<br>2018: 542.67 MB</pre><pre>Total Size: 5.84 GB</pre><h3>Archiving</h3><p>I have downloaded all the media files from my validUrlsAndSizes.json file, preserving their relative paths using wget:</p><pre>wget --no-host-directories --force-directories --input-file=urls.txt</pre><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=dbb605fc2101" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Evaluating TensorFlow Models in AWS Lambda]]></title>
            <link>https://medium.com/bbc-product-technology/evaluating-tensorflow-models-in-aws-lambda-c0e06cf23d87?source=rss-c3d7d944090c------2</link>
            <guid isPermaLink="false">https://medium.com/p/c0e06cf23d87</guid>
            <category><![CDATA[iplayer]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[lambda]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[tensorflow]]></category>
            <dc:creator><![CDATA[Nik Rahmel]]></dc:creator>
            <pubDate>Thu, 24 Aug 2017 13:59:17 GMT</pubDate>
            <atom:updated>2019-03-15T16:25:00.958Z</atom:updated>
            <content:encoded><![CDATA[<h4>Our journey to classify video frames as quickly as possible — with serverless parallelisation</h4><p>In the BBC, Artificial Intelligence and Machine Learning are currently being explored heavily by pretty much all product teams — Why? For the answer to this, I am going to point at <a href="http://www.bbc.co.uk/blogs/internet/entries/a7bb9d84-f330-4ad4-ac9d-c7d031c2af6a">this blog post</a> in which Ali, our Head of Emerging Technology, goes into a bit more detail about the <em>why </em>and covering not just BBC iPlayer.</p><p>While exploration is very important, in iPlayer we’ve already gone a step further: A couple of months ago we’ve trained our first machine learning model using Google’s <a href="https://www.tensorflow.org/">TensorFlow</a>. Its job is to classify video frames by certain characteristics and give us a score back. This is has been running in production for a while now. And we’re iPlayer — we have a lot of videos, and therefore a lot of video frames to be processed and classified.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/426/1*b-SbbzmWw1l868IVVAyXkA.gif" /><figcaption>Visualisation of the output of the classification</figcaption></figure><p>How many you ask? Well, most of our catalogue is available for 30 days. Every time we broadcast an episode that is not currently available, it becomes ‘available’, and thousands of episodes of iPlayer content become available each month.</p><p>Every time an episode becomes ‘available’, we need between 80–200 video frames classified. Ideally as quickly as possible.</p><p>How? The obvious step would be to look at Google, who make TensorFlow, and see that they have a service called the <a href="https://cloud.google.com/ml-engine/">Cloud Machine Learning Engine</a>. And whilst we’re experimenting with Google Cloud Platform (and yes, some BBC teams are looking at Azure), AWS is our production platform, so we had to work with that as a constraint.</p><p>I did spoiler it in the title for this blog post what our solution is: <a href="https://aws.amazon.com/lambda/">AWS Lambda</a>. In theory, we should be able to invoke a classification function 150 times in parallel, pay for that compute time, and get results back pretty quickly.</p><p>In practice — it works! However, it was a bit of a journey:</p><h3>The Problem</h3><p>AWS does place <a href="http://docs.aws.amazon.com/lambda/latest/dg/limits.html">certain limitations</a> on Lambda functions. Of interest here is the <em>Lambda function deployment package size</em> at a neat 50 MB (N.B. when using the console to upload the function this is 10 MB).</p><p>Let’s have a look at what we need to provide to classify our video frames:</p><ul><li>A trained model necessary for classification takes up space — in our case around 85 MB.</li><li>The TensorFlow library itself to run a video frame against the model takes up space — uncompressed we’re looking at 280 MB for that and the Python dependencies it requires that don’t come with Amazon Linux (the environment in which Lambda functions get executed).</li><li>Some code to process the data, and return something — this weighs in at around 15 kB in this case.</li></ul><p>Doing some maths this adds up to 365 MB. Just a bit more than the 50 MB AWS allows us to upload.</p><p>How could we get around that? <strong><em>Compression</em></strong> I hear you shout. We use TensorFlow 0.11 — the download size for that alone in a Lambda-like environment is 39.8MB. Add all the dependencies to that and we’re way over our limit.</p><p>Now you could download and install the dependencies and TensorFlow at runtime — but for several 10s of thousands of invocations a day that’ll add up in computation time and bandwidth. It’s probably not a secret that iPlayer doesn’t quite have the $500M R&amp;D budget of a certain competitor to even attempt justifying that. Thankfully there are some work arounds for that to which we’ll get to in due time.</p><p>How about the model? Can we compress that? Well, there’s <a href="https://arxiv.org/pdf/1510.00149v5.pdf">some research</a>, Caffe models can <a href="https://github.com/yuanyuanli85/CaffeModelCompression">do it</a>. TensorFlow? Nothing production-ready. And regardless of that, when the model gets trained further or becomes more complex, its size will increase, so this will not help us much in the long term.</p><h3>The Solution</h3><h4>The Hack: --exclude</h4><p>Let’s begin with the hackiest part: <strong>--exclude</strong>. A TensorFlow 0.11 installation gives us a lot of tools included for visualisation: <a href="https://www.tensorflow.org/get_started/summaries_and_tensorboard">TensorBoard</a>, <a href="http://eigen.tuxfamily.org/">Eigen</a>, and libraries for making use of other tools such as <a href="http://www.numpy.org/">Numpy</a> and <a href="https://ffmpeg.org/ffmpeg.html">ffmpeg</a>. Additionally, when you run Python, the interpreter will save the compiled byte code with the source files — we do not need to include all of when packaging up our zip. So we’re not including any of that when packaging it up. To be precise, we exclude *.DS_Store *.pyc /external/* /tensorflow/contrib/* /tensorflow/include/unsupported/*. This saves us 83 MB or so. We can probably save a lot more on that front by excluding more unnecessary libraries or using a different version of TensorFlow to evaluate models, but it turns out this is enough for us for — alongside the other tweaks.</p><p>There is only one more thing to be removed before our zipped package weighs in at under 50 MB and is ready to be deployed: The model.</p><h4>The storage: S3 and /tmp</h4><p>We store the model in <a href="https://aws.amazon.com/s3/">S3</a>. Sounds simple, right? The benefits are clear: We can decouple that, train and update the model separately and we don’t need to include some extra 85 MB of data in the lambda.<br>On the other hand, we have to pay for S3: storage of the model and requests from the lambda. Also, it takes extra compute time we’d have wait for and pay for, which isn’t ideal. If we go back to the <a href="http://docs.aws.amazon.com/lambda/latest/dg/limits.html">Lambda limits</a> I mentioned earlier, it states that there are 512 MB of <em>Ephemeral disk capacity (“/tmp” space) </em>available. And that’s where we are going to keep our model:</p><p>Because we run 200 invocations or so in parallel we’ll only need to download the model once and save it there — the other invocations should theoretically have access to it if it’s already saved. So one of the first things we do when invoking the function is to check whether the model exists in that location and download it using <a href="https://aws.amazon.com/sdk-for-python/">Boto3</a> (the AWS SDK for Python) if it doesn’t:</p><pre>import boto3<br>import os</pre><pre>if os.path.isfile(&#39;/tmp/graph.pb&#39;) != True:<br>    s3 = boto3.client(&#39;s3&#39;)<br>    s3.download_file(&#39;modelbucket&#39;, &#39;graph.pb&#39;, &#39;/tmp/graph.pb&#39;)</pre><p>Dandy right? Except there’s one more item on my solutions list above:</p><h4><strong>The warm-up: </strong><a href="http://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html"><strong>CloudWatch Scheduled Events</strong></a></h4><p>Some of you might have got some questions in regard to the above — if we’re kicking off 200 invocations in parallel, wouldn’t they all have to download the model as at that point it won’t exist? How long does /tmp persist anyway? We’ve done some digging.</p><p>Exhibit 1: <a href="https://aws.amazon.com/blogs/compute/container-reuse-in-lambda/"><em>Understanding Container Reuse in AWS Lambda</em></a>. This blog post from 2014 when Lambda was still in preview mentions the following:</p><blockquote>Files that you wrote to /tmp last time around will still be there if the sandbox gets reused.</blockquote><p>Exhibit 2: <a href="http://docs.aws.amazon.com/lambda/latest/dg/lambda-introduction.html"><em>AWS Lambda: How It Works</em></a><em>:</em></p><blockquote>Each container provides some disk space in the /tmp directory. The directory content remains when the container is frozen, providing transient cache that can be used for multiple invocations. You can add extra code to check if the cache has the data that you stored. For disk space size, see <a href="http://docs.aws.amazon.com/lambda/latest/dg/limits.html">AWS Lambda Limits</a>.</blockquote><blockquote>[…] Do not assume that AWS Lambda always reuses the container because AWS Lambda may choose not to reuse the container. Depending on various other factors, AWS Lambda may simply create a new container instead of reusing an existing container.</blockquote><p>So in theory, it should just stay there, except it might not. So we have set up a scheduled event to keep the container ‘warm’. We invoke the function every couple of minutes not to do any processing, but to make sure we’ve got the model ready. This accounts for a very small percentage of invocations but helps us mitigate race conditions in the model download.(We also limit artificially how many lambdas we invoke in parallel as an additional measure).</p><p>So how big is our function now? The zip measures in at 40MB at the moment — turns out python source files compress quite nicely.<br>This should remain at this level for this specific one as we don’t need to update the lambda to train the model further.</p><p>This brings us to the final question: How do we invoke the function? — <a href="https://aws.amazon.com/api-gateway/">API Gateway</a> is the answer to that one.</p><h4>The access: API Gateway</h4><p>We have an API Gateway POST endpoint that takes the <a href="https://en.wikipedia.org/wiki/Base64">base64</a>-encoded jpeg of the video frame, and returns JSON 10 seconds later. This also allows quick and effective testing of changes to the model without having to write any code to invoke the function — curl and base64 is all that’s needed to evaluate a frame.</p><p>API Gateway does actually <a href="https://aws.amazon.com/about-aws/whats-new/2016/11/binary-data-now-supported-by-api-gateway/">support binary data</a> now, so we could scrap the need for base64 as it does the base64 encoding for you, but when we first deployed this, that feature was not available using CloudFormation.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*U1ADd5cL-I7vAZiLtk9DzA.png" /><figcaption>The solution we come up with to evaluate TensorFlow Models in AWS Lambda</figcaption></figure><p>All in all we are quite happy with how this is running in production at the moment , in terms of reliability and efficiency, but we are looking forward to improving on this pipeline, and putting it to use for many more applications to come.</p><p>We are gathering usage data of this classifier and with help from the editorial staff we are analysing the accuracy of the model, but the training of the model is currently a manual process which we are hoping to automate in the future.</p><p>We are also looking at other metadata which might be available to us elsewhere in the broadcast chain to enrich our content, and how we might use it to improve the audience experience — stay tuned for more on <em>what</em> we are actually classifying if the gif above doesn’t give away too much.</p><h3>Join us</h3><p><em>If you’re interested and excited by the technical challenges we’re facing, we’re always looking for driven engineers to join us.</em></p><p><em>You can see all our job listings for BBC iPlayer </em><a href="http://www.bbc.co.uk/careers/home"><em>here</em></a><em> by searching for iPlayer.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c0e06cf23d87" width="1" height="1" alt=""><hr><p><a href="https://medium.com/bbc-product-technology/evaluating-tensorflow-models-in-aws-lambda-c0e06cf23d87">Evaluating TensorFlow Models in AWS Lambda</a> was originally published in <a href="https://medium.com/bbc-product-technology">BBC Product &amp; Technology</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Blurring, Banding and Browsers]]></title>
            <link>https://medium.com/@nik.rahmel/blurring-banding-and-browsers-de94315c6077?source=rss-c3d7d944090c------2</link>
            <guid isPermaLink="false">https://medium.com/p/de94315c6077</guid>
            <category><![CDATA[svg]]></category>
            <category><![CDATA[blur]]></category>
            <category><![CDATA[css]]></category>
            <category><![CDATA[ux]]></category>
            <category><![CDATA[html]]></category>
            <dc:creator><![CDATA[Nik Rahmel]]></dc:creator>
            <pubDate>Wed, 13 Jul 2016 00:00:00 GMT</pubDate>
            <atom:updated>2018-09-18T18:33:05.521Z</atom:updated>
            <content:encoded><![CDATA[<p>This week we spent spiking a new feature! Always very exciting. Would love to tell you what it is, but this is not the time nor the place. If you want to know, apparently I get more talkative after a beer.</p><p>Anyway, for this feature our UX people really wanted to blur images, because that’s up and coming these days. Now that doesn’t exactly sound like a challenge, what was CSS3 invented for anyway? Well, sadly we still have to give some consideration Browsers from the pre-CSS3 era, and whilst the funky old Microsoft DXImageTransform filters do have a nice blur, it’s not really a route we wanted to settle on (and also would only be supported in IE 5.5–8 and 10+ I believe, because who likes logic anyway)</p><p>So we looked at alternatives. Client-side we looked at drawing in a HTML canvas and putting our image inside of an SVG with a filter on. The drawback of the HTML canvas is that there’s no simple way to blur without much code — we’d have to package our own blur algorithm (<a href="http://www.quasimondo.com/StackBlurForCanvas/StackBlur.js">this one </a>seems to pretty popular, coming in at 6kB minified), we don’t really want to do that for reasons.</p><p>So SVG blur is the other client-side solution, which is also used by other teams within the organisation, but they have quite a different set of requirements. And it’s pretty simple, look at that:</p><pre>&lt;svg&gt; &lt;defs&gt; &lt;filter id=&quot;svgBlur&quot;&gt; &lt;feGaussianBlur in=&quot;SourceGraphic&quot; stdDeviation=&quot;25&quot;/&gt; &lt;/filter&gt; &lt;/defs&gt; &lt;image xlink:href=&quot;http://7nr.de/somepicjpg&quot; width=&quot;100%&quot; height=&quot;100%&quot; filter=&quot;url(#svgBlur)&quot; /&gt; &lt;/svg&gt;</pre><p>Pretty straightforward, hm? It also looks beautiful, in Safari at least. It’s a very different situation in Chrome or Firefox, however:</p><ul><li>There is some serious banding on the graphic. No idea where it comes from or why, but it just looks a bit ugly.</li><li>Due to the nature of the Gaussian Blurs and SVGs, the edges of the image get blurred with the surroundings, in this case, nothing — i.e. it just fades out and becomes semi-transparent towards the edges. This is something that can probably be fixed easily by setting the width/height of the filter, or just zooming in the image or.. there are probably a couple other methods.</li><li>There was a third point, but I don’t remember it now.</li></ul><p>This made us think about alternatives, and our infrastructure — and our infrastructure is quite clever! For any raw source image, our image server can generate different resolutions and caches them and whatnot. Not only can you specify a resolution, but these ‘recipes’ that we use can contain a whole range of commands to be applied (it uses GraphicsMagick under the hood).</p><p>One of those commands is ‘blur’, and it looks beautiful. And because there’s no client-side operation, it’s supported in any browser that can display JPEGs — which is definitely a superset of the browsers we care about so yay :)</p><p>To just illustrate the differences between a GraphicsMagick blur and an SVG blur in our case and in this case in my specific configuration on my machine, here two screenshots (you might want to open them in a new tab/view them full sized):</p><p><strong>SVG Gaussian Blur</strong>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*0q1-X70OCQJ6WZdI.png" /></figure><p><strong>GraphicsMagick Blur</strong>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*NxxBn5xq5oOjaM6S.png" /></figure><p><em>Originally published at </em><a href="http://niklasr.github.io/blog/2016/07/13/blur-banding-and-browsers"><em>niklasr.github.io</em></a><em> on July 13, 2016.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=de94315c6077" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Synology Backups and a Power Cut]]></title>
            <link>https://medium.com/@nik.rahmel/synology-backups-and-a-power-cut-c701abec1631?source=rss-c3d7d944090c------2</link>
            <guid isPermaLink="false">https://medium.com/p/c701abec1631</guid>
            <category><![CDATA[synology]]></category>
            <category><![CDATA[raspberry-pi]]></category>
            <category><![CDATA[backup]]></category>
            <dc:creator><![CDATA[Nik Rahmel]]></dc:creator>
            <pubDate>Mon, 18 Apr 2016 00:00:00 GMT</pubDate>
            <atom:updated>2018-09-18T18:31:49.065Z</atom:updated>
            <content:encoded><![CDATA[<p>I ordered a Raspberry Pi 3 the other day and it arrived today yay! When I got home I obviously was very keen to plug it in and give it a go, so I unplugged a plug and thought.. <em>Oh crap</em></p><p>A couple of weeks ago I started a Backup task for all my photos on my NAS. So my DS410 has eagerly been uploading hundreds of gigabytes of DNGs to S3 24/7, and was about 25% finished (yay under 2 months left to be done!). And I just pulled the plug. Smart move, Nik, smart move.</p><p>Plugged it back in, logged in and luckily saw no drive errors, which is a wonder seeing that the oldest drive in the bunch has 41620 hours “Power-On Time” (the youngest has 5862 hours).</p><p>Restarted the Backup (apparently it was ‘Never backed up’ before), and it started uploading.. 10MB.. 20MB.. 0%. I logged into my AWS account to check it hadn’t deleted the 250GB it had already uploaded.. it didn’t.. Mysterious, what was it doing? We got to ~ 60MB uploaded, and suddenly.. 26% done! Thank the lord it picked it back up, and I feel a bit safer about this back up now.</p><p>Next step will be downloading it onto an RS815+ at home from the S3 backup. Redundancy, accessibility and cost-efficiency. Yada-yada..</p><p>Oh, and the Raspberry Pi? Well, I realised I don’t actually have a USB keyboard at home, so that’ll have to wait until some other time, too.</p><p><em>Originally published at </em><a href="http://niklasr.github.io/blog/2016/04/18/synology-dsm-backup-and-powercut"><em>niklasr.github.io</em></a><em> on April 18, 2016.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c701abec1631" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>