<?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 Chas DeVeas on Medium]]></title>
        <description><![CDATA[Stories by Chas DeVeas on Medium]]></description>
        <link>https://medium.com/@chasdevs?source=rss-249d9a458e11------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/0*SRHhlW8QGJfWqBkz.</url>
            <title>Stories by Chas DeVeas on Medium</title>
            <link>https://medium.com/@chasdevs?source=rss-249d9a458e11------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 14 Nov 2026 10:45:16 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@chasdevs/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[Blue-Green ETLs with Airflow Task Groups]]></title>
            <link>https://medium.com/storyblocks-engineering/blue-green-etls-with-airflow-task-groups-71c36d120c2e?source=rss-249d9a458e11------2</link>
            <guid isPermaLink="false">https://medium.com/p/71c36d120c2e</guid>
            <category><![CDATA[redshift]]></category>
            <category><![CDATA[airflow]]></category>
            <category><![CDATA[data-engineering]]></category>
            <category><![CDATA[software-development]]></category>
            <dc:creator><![CDATA[Chas DeVeas]]></dc:creator>
            <pubDate>Mon, 27 Sep 2021 20:57:50 GMT</pubDate>
            <atom:updated>2021-09-27T20:57:50.983Z</atom:updated>
            <content:encoded><![CDATA[<p><em>How Storyblocks achieved zero-downtime updates of application data in our data warehouse using Task Groups in Apache Airflow.</em></p><h3>The Problem: Data Downtime</h3><p>Like most tech companies, Storyblocks has application databases and ETLs importing them into a data warehouse. Bulk-copying production MySQL databases hosted on Amazon RDS to a data warehouse in Amazon Redshift overnight is easy enough, but three issues with that naive approach are <strong>Data Latency</strong>, <strong>Data Reliability</strong>, and <strong>Interrupted Workloads</strong>.</p><p>Those three problems can arise from bulk load operations which <em>drop the target data</em> in the data lake or warehouse. Some ETL tools such as <a href="https://aws.amazon.com/dms/">Amazon DMS</a> make it very easy to setup a routine data migration, but by default they will drop or clear the target tables.</p><p><strong>Data Reliability</strong> can suffer if an ETL process drops target tables before bulk-loading the new data. Any unhandled error in the load process can leave the data lake/warehouse in an incomplete state when people come to work the next day. When more and more data processes are added to the ETL or different workloads are added to the data lake/warehouse, some bulk load operations can lead to <strong>Interrupted Workloads</strong>. For example, a long-running SELECT operation for an analyst’s derived table or a data scientist’s recommendation engine job could be clobbered by the ETL. As a result of those limitations, the ETL is often only scheduled once per day overnight and <strong>Data Latency</strong> can forever be a day behind.</p><h3>A Solution: “Blue-Green” Data Deploys</h3><p>To avoid the issues above, one solution is to maintain two sets of target data for each data source — a “blue” dataset and “green” dataset — and then a simple view layer on top which all consumers actually use. For example, if we have an <em>accounts</em> database in production, we would keep an <em>accounts_blue</em> and <em>accounts_green</em> database in the data warehouse with populated tables, and an <em>accounts</em> database which just holds views of each table pointing to the “active” database.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/521/1*6hroIljuRvvmpcax0qM1lw.png" /></figure><p>The “blue-green” name comes from the software engineering concepts of <a href="https://martinfowler.com/bliki/BlueGreenDeployment.html">Continuous Integration/Continuous Delivery</a> and refers to deploying new code to a set of servers and slowly cutting traffic over to the new servers. The analogy doesn’t exactly fit, but I figured it is close enough (also there’s likely a more appropriate data term I don’t know about).</p><p>A lot of other ETLs will load data into a temporary table, then change the table name at the end of the process. This is a perfectly reasonable solution, but in some systems it can still lead to interrupted workflows, and at least for Amazon DMS it is not possible to do this out of the box.</p><h3>Airflow and Task Groups</h3><p>At Storyblocks, we use Apache Airflow to schedule most data processes. Airflow 2.0 introduced <a href="https://airflow.apache.org/blog/airflow-two-point-oh-is-here/#task-groups-aip-34">Task Groups</a>, a much-easier-to-use feature compared to SubDags for reusable/composable groupings of tasks. In this case, we had a group of three distinct tasks which could logically be grouped into a repeatable pattern for a given database: 1. Query the warehouse to see which color is inactive, 2. Run the ETL process for the inactive color, 3. Switch the active color in the warehouse (point the view layer to the newest color).</p><h3>The Storyblocks Nightly ETL</h3><p>Our main ETL process for loading application databases into our data warehouse consists of Amazon DMS jobs running in parallel, followed by a series of Apache Spark jobs to generate derived tables, and finally a massive collection of SQL scripts maintained by our Analytics department (called “etl_runner” below).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/774/1*BYOGnn9rXAX7Tc92W7elsQ.png" /><figcaption>The Storyblocks “Nightly ETL” (the blue tasks are Task Groups)</figcaption></figure><p>Originally, the DMS jobs would drop and bulk load the target tables. That often led to the issues outlined above. Our machine learning team would attempt to read data in their own overnight data jobs and would often get incomplete results from the still-in-progress DMS jobs or the ETL would clobber their process and delay their team by a day.</p><p>The white tasks on the left indicate standard DMS tasks, and the blue ones are the Task Groups. If you click on a Task Group in the Airflow UI it will expand:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xNGeFe5W8N8qZD7MPlSF4w.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/543/1*TmwcUMrtlar4avPXJqDw7A.png" /><figcaption>Clicking on a Task Group will expand it in Airflow</figcaption></figure><p>A simplified version of the DAG code with Task Groups is here:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/384651aa916e8d43c275ab24f1175c38/href">https://medium.com/media/384651aa916e8d43c275ab24f1175c38/href</a></iframe><p>The code for selecting the active color in the warehouse is here:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/807382e9d32d7825a6480f8c7f68a497/href">https://medium.com/media/807382e9d32d7825a6480f8c7f68a497/href</a></iframe><p>And the code (Redshift-specific) for switching all of the views in a database to point to the new active color is here:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/caf81df9c7918dce28acd63048913ebb/href">https://medium.com/media/caf81df9c7918dce28acd63048913ebb/href</a></iframe><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=71c36d120c2e" width="1" height="1" alt=""><hr><p><a href="https://medium.com/storyblocks-engineering/blue-green-etls-with-airflow-task-groups-71c36d120c2e">Blue-Green ETLs with Airflow Task Groups</a> was originally published in <a href="https://medium.com/storyblocks-engineering">The Storyblocks Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Foolproof Schema Management with GitHub Actions and Avro]]></title>
            <link>https://medium.com/storyblocks-engineering/foolproof-schema-management-with-github-actions-and-avro-afb0ebb00dfe?source=rss-249d9a458e11------2</link>
            <guid isPermaLink="false">https://medium.com/p/afb0ebb00dfe</guid>
            <category><![CDATA[continuous-integration]]></category>
            <category><![CDATA[schema]]></category>
            <category><![CDATA[github-actions]]></category>
            <category><![CDATA[kafka]]></category>
            <category><![CDATA[avro]]></category>
            <dc:creator><![CDATA[Chas DeVeas]]></dc:creator>
            <pubDate>Tue, 26 Nov 2019 15:03:52 GMT</pubDate>
            <atom:updated>2019-11-27T19:23:20.634Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/650/1*L9fjOb771HwpEuh-FXBQjQ.png" /></figure><p>The Storyblocks platform produces millions of events each day. These events allow our growing number of microservices to communicate with each other, let us incorporate user behavior into our search algorithms, and allow product managers and analysts to evaluate new features. These structured event payloads are produced and consumed by frontend code, backend services, and data-intensive scripts and jobs.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/934/0*MZe37__Nuq5s-yZb" /><figcaption>The Storyblocks data pipeline</figcaption></figure><p><a href="https://kafka.apache.org/">Apache Kafka</a> is a wonderful tool that enables asynchronous communication and data transportation at scale, and <a href="https://avro.apache.org/">Apache Avro</a> is a schema management and encoding technology which ensures structured and valid data. Using the new <a href="https://github.com/features/actions">GitHub Actions</a> platform, we created an extremely developer-friendly experience for managing schemas across our entire engineering organization.</p><h3>Problem</h3><p>Until recently, our method for gathering frontend and backend event data has been to produce JSON-encoded events onto a Kinesis stream, which then had a custom application validating the events with <a href="https://json-schema.org/">json-schema</a> and an EMR job storing them in S3. Event schemas were stored in a forked version of <a href="https://github.com/snowplow/iglu">Iglu</a> deployed to an EC2 server.</p><p>While simple in theory, we faced many challenges with this system: Schemas were not stored in a central location, registering a schema was an error-prone process that required manually running a custom python script, versioning was unenforced and unclear, custom infrastructure and code led to bugs and outages, schema validation was incomplete, backwards-incompatible schemas changes broke downstream scripts, schema files were large and human-unfriendly, common fields were copied and repeated in every file, and invalid events were not discoverable until code was deployed to staging.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/496/0*nY-mpgsEZ7BZnztv" /></figure><p>Developers found the system difficult to use and fragile, which created friction when creating new events or updating existing ones. Also, the system did not allow easy asynchronous communication between services. As a result, we were slow to track new data, slow to enhance existing data, and stuck in synchronous REST calls between our services. Not a good situation for a startup that “<a href="https://www.wearestoryblocks.com/">hearts data</a>”.</p><h3>Solution</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/216/1*MFL2XXpaaNfIWjiGNV3ndw.png" /></figure><p>Enter <a href="https://avro.apache.org/">Avro</a>. Avro is a widely-used technology for structuring and encoding data, and is a first-class citizen in the awesome Kafka-based <a href="https://www.confluent.io/">Confluent</a> ecosystem. It has schema evolution built-in, and its <a href="https://avro.apache.org/docs/current/idl.html">Avro IDL</a> language makes it easy to write human-readable schemas that can import and reuse common fields.</p><p>Given our developers’ lack of enthusiasm for creating events, we wanted to take things a step further and create a pain-free developer experience for managing schemas. Something that Just Works™.</p><p>Here are the components:</p><h4>Central Events Repository</h4><p>Every developer and product manager on our team knows how to use GitHub, so having a simple “events” repository to hold our schemas made the most sense. It’s easy to browse the schemas using the GitHub interface, and to update or create schemas you follow the same workflow as any other codebase: Branch, code, pull request, and merge.</p><h4>Avro IDL</h4><p>Avro IDL is extremely easy to read and understand (much more than JSON, in my opinion). More importantly, it allows <a href="https://avro.apache.org/docs/current/idl.html#imports">importing schemas</a>. This important feature lets us share common schemas between common events, vastly simplifying and DRY-ing up our schema code. For example, all of our backend events have a “meta” field that contains uuid, timestamp, and origin (the service or site that produced it).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/513/1*jg3zjX3OD79MATnz_Tqojw.png" /><figcaption>Common schemas may be imported and shared</figcaption></figure><h4>Event Libraries</h4><p>Typed code generally creates less bugs and speeds up the development process. We wanted our developers to be able to import typed event classes and populate them with all of the benefits of types and typeahead. That is much preferable to sending arbitrary JSON or arrays-of-arrays onto the wire hoping they conform to a schema. It also makes it easy to “click into” event classes to see the schemas and available fields.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9d0V6KTsPmGrhvNS42zmaw.png" /><figcaption>Typeahead with our Typescript Event Library</figcaption></figure><p>Lucky for us, there are open-source libraries which compile Avro schemas into <a href="https://github.com/davidmc24/gradle-avro-plugin">Java</a>, <a href="https://github.com/bornfight/avro-to-typescript">Typescript</a>, and <a href="https://github.com/rbystrit/avro_gen">Python</a> files. There wasn’t a PHP version, so we <a href="https://github.com/chasdevs/avro-to-php">built that ourselves</a>. We just needed to figure out how to link all these things together in a user-friendly fashion…</p><h4>Continuous Integration with GitHub Actions</h4><p>Continuous Integration offloads the work of testing and deploying to machines so that people can just do their jobs. GitHub recently released their own system for executing code automatically on their platform in the form of GitHub Actions. We configured custom actions to run when engineers pushed code or merged to master in the “events” repository.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/576/1*pNKWO8IG0BycHxvrJkru_A.png" /><figcaption>GitHub Actions interface</figcaption></figure><p>On every push to a branch, a <em>validate schemas</em> action checks all schemas against <a href="https://docs.confluent.io/current/schema-registry/index.html">Confluent Schema Registry</a>, hosted on <a href="https://aiven.io/">Aiven.io</a>. If a schema change is not forwards- or backwards-compatible, the action fails and GitHub shows a red “x” on that branch. This is also a required check before a pull request can be merged to master. As a result, <strong>only valid, non-breaking changes can be made to the master branch of our event repository</strong>.</p><p>On every merge to master, a “<em>sync schemas and tag” </em>action puts the changes into the schema registry, followed by a “<em>publish event libraries” </em>action. The <em>publish</em> action auto-generates files and a versioned artifact for each language, and publishes the artifact. For example, the “compile typescript” action generates Typescript files for each event and publishes a private npm package to npmjs.org. A developer can install the library by running npm install @videoblocks/events-ts.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ain9XVkhfFIR2bcSnNPzWQ.png" /><figcaption>Example of a merged Pull Request publishing the PHP library</figcaption></figure><h3>Simple Workflows == Happy Developers</h3><p>Now, when a product manager wants to track a new feature (beyond simple telemetry data) or a developer needs to communicate a message to another system, the workflow looks like this:</p><ol><li>Design the schema.</li><li>Create a pull request in the events repository.</li><li>Merge to master.</li><li>Update the client library.</li><li>Code.</li></ol><p>Developers can write tests for their events, produce and consume them locally, and can rest assured knowing that they won’t anger coworkers by breaking their downstream scripts. Our latest high-impact microservice for billing customers successfully used the new system to communicate with the other services in our platform, and the engineers found the system easy to use. <strong>Events are a tool that enables them rather than a requirement they must implement</strong>.</p><h3>Next Steps</h3><p>We hope to open-source a version of our events repository in the near future, including our GitHub Actions and example client libraries. Combined with the powers of Kafka and Kafka Connect, these tools can be a huge asset to any development team dealing with structured event data.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=afb0ebb00dfe" width="1" height="1" alt=""><hr><p><a href="https://medium.com/storyblocks-engineering/foolproof-schema-management-with-github-actions-and-avro-afb0ebb00dfe">Foolproof Schema Management with GitHub Actions and Avro</a> was originally published in <a href="https://medium.com/storyblocks-engineering">The Storyblocks Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Fighting Meetings with Technology and Psychology]]></title>
            <link>https://medium.com/storyblocks-engineering/fighting-meetings-with-technology-and-psychology-ed361ec04e9?source=rss-249d9a458e11------2</link>
            <guid isPermaLink="false">https://medium.com/p/ed361ec04e9</guid>
            <category><![CDATA[innovation]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[hackathons]]></category>
            <category><![CDATA[internet-of-things]]></category>
            <dc:creator><![CDATA[Chas DeVeas]]></dc:creator>
            <pubDate>Wed, 26 Jun 2019 10:01:04 GMT</pubDate>
            <atom:updated>2021-06-04T03:34:45.924Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3jLBTImMMixtQn8W_bWwPg.jpeg" /></figure><h3>Fighting Meetings with Technology and Psychology… and Light Bulbs</h3><p><em>Repository: </em><a href="https://github.com/Footage-Firm/meeting-room-traffic-lights"><em>https://github.com/Footage-Firm/meeting-room-traffic-lights</em></a></p><p>With two-dozen Oscars to present during the Academy Awards, there are a great many acceptance speeches. To keep speakers within a reasonable time limit, play-off music is famously used to tell folks to wrap it up — and it nearly always does the trick!</p><p>All too often, I have been stuck in a dragging meeting at work wishing that someone would start the play-off music. For our third annual “Innovation Day” at Storyblocks, my coworker and sometimes-boss <a href="https://medium.com/u/bd57bd4038cc">Aaron Silverman</a> had the idea to create traffic lights for our meeting rooms to tell people the meeting is ending. Having previously written a <a href="https://engineering.videoblocks.com/analyzing-meeting-metrics-using-the-google-calendar-api-3c76c9f8ffea">blog post</a> on gathering meeting metrics using the Google Calendar API, this idea fit very well with my general displeasure at meetings and my desire to stomp them out at Storyblocks — or at least make them more tolerable.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hYOSuZO1kEu1HRRMns-INg.png" /><figcaption>Storyblocks stats from the “meetrics” project</figcaption></figure><p>The idea is simple: Put smart RGB light bulbs in each of our meeting rooms, then connect each bulb up to Google Calendar and change the color based on meeting time. If there is no meeting, the bulb is off. If there is currently a meeting in the room, the bulb is green. If the meeting is almost over, the bulb turns orange. When the meeting is over, the bulb turns red.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RrNp6SXGkcy7GCR_NA6YNg.jpeg" /><figcaption>Our hastily-drawn demonstrative. Note the crude timeline to the right.</figcaption></figure><h3>Materials</h3><ul><li><a href="https://www.lifx.com/collections/featured-products/products/lifx-mini-color-e26">LIFX Mini Color</a> bulbs</li><li><a href="https://www.amazon.com/gp/product/B01FZKKTU0">INJUICY</a> bulb stands</li><li>Meeting Rooms defined as bookable resources in Google Calendar</li><li>2.4 GHz ONLY network (mixed networks don’t work well).</li><li>AWS</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*sNt0iiWMPv1NIL3pLshtEw.jpeg" /><figcaption>Coding up the app</figcaption></figure><h3>Process</h3><p>We wanted a system that would be simple, inexpensive, flexible, and reliable. To achieve that, we knew that we wanted little-to-no infrastructure and bulbs that could be controlled remotely over the internet. Cursory internet research showed that others had success controlling LIFX bulbs via <a href="https://api.developer.lifx.com/">their API</a> and their bulbs were relatively cheap at about $30 per bulb (even cheaper on Ebay), so we ordered some along with simple stands that fit our office aesthetic. After that, our game-plan was to write up a simple Typescript application to be deployed on AWS Lambda using the dead-simple <a href="https://serverless.com/">serverless</a> module.</p><h4>Code</h4><p>The application was coded to allow automatic configuration of rooms and bulbs. If our company changed any room names or added any rooms, we wanted it to be as simple as changing the name of a light bulb or purchasing a new one. Also, if any other groups wanted to use our system, they shouldn’t have to touch the code other than deploying it.</p><pre><em>// Handler.ts (syncBulbs function)</em></pre><pre><em>const </em>lights = <em>new </em>MeetingTrafficLights()<br><br><em>const </em>lifxNetwork = new LifxBulbNetwork()<br>lights.addNetwork(lifxNetwork)<br><br><em>const </em>calendarService = new CalendarService()<br>lights.setCalendar(calendarService)<br><br><em>return await </em>lights.syncBulbs()</pre><p>The code works by polling Google Calendar to get the list of all meeting rooms in an organization, querying the LIFX API to get the list of bulbs, then mapping rooms to bulbs before setting bulb colors. It will attempt to match bulb labels to room name automatically (so you don’t have to touch the code), but you can optionally override the pairings. For each mapped room/bulb pair, it then checks if a meeting is currently in-progress, almost finished, or finished, and sets the corresponding color.</p><pre>/// MeetingTrafficLights.ts</pre><pre><em>if </em>(currentMeeting) {<br>    logger.debug(&#39;Meeting in progress. Setting color to Green.&#39;, {room, bulb})<br>    <em>await </em>bulb.setColor(Color.<em>GREEN</em>)<br>} <em>else </em>{<br>    logger.debug(&#39;No meeting. Turning bulb off.&#39;, {room, bulb})<br>    <em>await </em>bulb.powerOn(<em>false</em>)<br>}</pre><p>The app runs every minute during normal business hours Monday through Friday (to save energy and cut down on Lambda executions).</p><pre>// serverless.yml</pre><pre>functions:<br>  syncBulbs:<br>    handler: bin/Handler.syncBulbs<br>    name: sync-bulbs<br>    description: Sets light bulb colors for meeting rooms during weekday business hours (12-24 GMT).<br>    events:<br>      - schedule:<br>          rate: cron(* 12-23 ? * 2-6 *)</pre><h4>Technical Difficulties</h4><p>Our biggest issue by far was WiFi. The bulbs that we used can only communicate on a 2.4 GHz network, and our office WiFi had the same SSID for each band. It was quite common for a bulb to initially connect successfully and then be completely unable to reconnect after turning off. For our demo, it was easy enough to coast on the initial connection, but we knew that we’d need a dedicated 2.4 GHz network before staking our reputations on a real implementation.</p><p>Additionally, connecting to the Google Calendar API is not the most straightforward process. We used a <a href="https://cloud.google.com/iam/docs/service-accounts">service account</a> that was setup for the Meetrics project (see instructions in that <a href="https://engineering.videoblocks.com/analyzing-meeting-metrics-using-the-google-calendar-api-3c76c9f8ffea">blog post</a> or in this <a href="https://github.com/Footage-Firm/meeting-room-traffic-lights">repo</a>), but it still took a good couple hours of experimenting with the <a href="https://github.com/googleapis/googleapis">googleapis</a> module to get it right.</p><h4>Live Demo</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kigH_N0rEfss-QJOa-felg.jpeg" /><figcaption>The live demo</figcaption></figure><p>Our demo went well! The Innovation Day judges were able to move meetings around on Google Calendar and see the bulbs change color. We also showed how easy it was to add a bulb without making any code changes. People were excited to see the bulbs “rolled out to production” in the coming weeks.</p><p>After Innovation Day, we were able to do live tests of a bulb in one of our meeting rooms called “The Studio” and see what effects, if any, it had. The first runs were definitely biased by the novelty of the addition to the meeting room, but there did seem to be a natural increased awareness of meeting schedule. Once the bulb turned red, people picked up their hustle to get out.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*X0PDKhSED3tu5BVDZvRZnA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cbLG1_6cJCh1rcBIfrhw8g.jpeg" /><figcaption>Live testing of one bulb in “The Studio”</figcaption></figure><h3>Results</h3><p>At the very least, we won the “Cultural Award” at Innovation Day and got our working prototype out the door for live experimentation in our company’s meeting rooms. We will see if the bulbs continue to work in spite of WiFi issues and live users who don’t like being told when to end their meetings.</p><p>Feel free to implement your own traffic lights using our open-sourced code at <a href="https://github.com/Footage-Firm/meeting-room-traffic-lights">Footage-Firm/meeting-room-traffic-lights</a>! The links to necessary materials and instructions are included in the README.</p><p>Happy hacking, and DOWN WITH ALL THE MEETINGS (some are ok).</p><p><em>Interested in monitoring your own organization’s meeting health? Check out Meetrics: </em><a href="https://meetrics.io/"><em>https://meetrics.io</em></a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ed361ec04e9" width="1" height="1" alt=""><hr><p><a href="https://medium.com/storyblocks-engineering/fighting-meetings-with-technology-and-psychology-ed361ec04e9">Fighting Meetings with Technology and Psychology</a> was originally published in <a href="https://medium.com/storyblocks-engineering">The Storyblocks Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The LAN Stack: Going 100% Serverless]]></title>
            <link>https://medium.com/storyblocks-engineering/the-lan-stack-going-100-serverless-dfba9c26a570?source=rss-249d9a458e11------2</link>
            <guid isPermaLink="false">https://medium.com/p/dfba9c26a570</guid>
            <category><![CDATA[microservices]]></category>
            <category><![CDATA[serverless]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[software-development]]></category>
            <dc:creator><![CDATA[Chas DeVeas]]></dc:creator>
            <pubDate>Thu, 09 Aug 2018 20:57:14 GMT</pubDate>
            <atom:updated>2018-08-09T21:24:55.046Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3MZDz5z3r8gYw-VuvGf0vQ.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/photos/TevqnfbI0Zc?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">ian dooley</a> on <a href="https://unsplash.com/search/photos/balloons?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><p>With the release of <a href="https://aws.amazon.com/rds/aurora/serverless/">Amazon Aurora Serverless</a>, which recently went into public beta, you can now build a standard web app which is 100% serverless. This post walks through a basic serverless implementation using Lambda, Aurora, and NodeJs.</p><blockquote><strong>L</strong>ambda<strong> A</strong>urora<strong> N</strong>ode… <strong>LAN</strong> Stack.</blockquote><h4>Overview</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/541/1*UgopcoIpyWFTzLpzavNIyQ.png" /></figure><p>The high-level view of our LAN application is an REST API written in NodeJs and deployed to Amazon API Gateway + Lambda using the wonderful <a href="https://serverless.com/">serverless</a> framework with a database hosted using Aurora Serverless. Although not necessary for all web apps, you may easily add a frontend to this setup with a single page app hosted on S3 and served with CloudFront, putting a nice decoupled cherry on top of this delicious serverless cake.</p><h4>Serverless Framework</h4><p>The <a href="https://github.com/serverless/serverless">serverless.js</a> library is a great tool which makes it dead simple to deploy Node.js code to Amazon Lambda. The only steps you need to get up and running are to npm install serverless and then configure <em>serverless.yml</em> in the project root.</p><p>A bare-bones <em>serverless.yml</em> configuration may look like this:</p><pre># serverless.yml<br># @see docs.serverless.com</pre><pre>service: my-serverless-api</pre><pre>provider:<br>  name: aws<br>  runtime: nodejs8.10</pre><pre>functions:<br>  helloWorld:<br>    handler: src/helloWorld<br>    events:<br>      - http: ANY /hello</pre><p>This deploys a function exported in <em>src/helloWorld.js</em> to Lambda and configures API Gateway to launch that function when someone makes an HTTP request (using any method) to <em>https://&lt;generated endpoint&gt;/hello</em>. The endpoint is returned after deploying serverless via serverless deploy.</p><p>That setup works well enough if you want to just launch a simple javascript function when you hit an endpoint, but what if you want to create a fully-fledged web app? When I’m developing my API or web app, I don’t want to think about functions, gateways, or infrastructure at all; I just want to install the web framework of my choice and get down to writing routes.</p><h4>The Proxy Function</h4><p>Most NodeJs web frameworks support “inject” functionality for their server classes, which is the key to wrapping a normal web app in a Lambda function. Lambda functions launched by API Gateway are invoked with an <em>event </em>parameter which contains the relevant HTTP request information. We just map that event to a fake <em>request </em>object and inject into the server, then translate the response<em>.</em></p><pre>// proxy.js<br><strong>const </strong>url = require(&#39;url&#39;)<br><br><strong>const </strong>server = require(&#39;../server&#39;)<br><br>module.exports = <strong>async </strong>(event, context, callback) =&gt; {<br>  <strong>const </strong>request = {<br>    method: event.httpMethod,<br>    url: url.format({<br>      protocol: event.protocol,<br>      pathname: event.path,<br>      query: event.queryStringParameters<br>    }),<br>    payload: event.body,<br>    headers: event.headers<br>  }<br><br>  <strong>const </strong>response = <strong>await </strong>server.inject(request)<br><br>  <strong>return </strong>{<br>    headers: response.headers,<br>    statusCode: response.statusCode,<br>    body: response.payload<br>  }<br>}</pre><p>We now just need to map every incoming API Gateway request to this function, which is easy using <em>serverless.yml</em>:</p><pre><strong>functions</strong>:<br>  <strong>proxy</strong>:<br>    <strong>handler</strong>: src/functions/proxy<br>    <strong>description</strong>: Proxies an API Gateway request to the web server.<br>    <strong>events</strong>:<br>      - <strong>http</strong>: ANY /<br>      - <strong>http</strong>: ANY {proxy+}</pre><p>There are some other important tweaks to <em>serverless.yml</em>, such as installing serverless-offline for local development, serverless-api-cloudfront to setup custom domain names, adding VPC configurations for the function, and adding environment variables.</p><p>The full configuration I used is here (on a side project that is in-the-works):</p><p><a href="https://github.com/openrecord/turntable/blob/master/serverless.yml">openrecord/turntable</a></p><h4>The Database</h4><p><a href="https://aws.amazon.com/rds/aurora/serverless/">Aurora Serverless</a> isn’t <em>really “</em>serverless”, it’s just Amazon’s way of abstracting server management away from us developers. To date, this has been a popular trend in cloud servers and storage, but not many (if any?) cloud providers have released a stateful database solution. Their documentation sums up the service well:</p><blockquote>With Aurora Serverless, you simply create a database endpoint, optionally specify the desired database capacity range, and connect your applications. You pay on a per-second basis for the database capacity you use when the database is active.</blockquote><p>For something like side projects, Aurora Serverless can be a convenient way to deploy a production-ready environment while not worrying about shutting down servers when you go on vacation. As of August 2018, one catch is that you can’t connect directly to the endpoint from outside the VPC. To connect from my computer, I just configured my <em>~/.ssh/config</em> file to do port-forwarding to an EC2 jump-box in the same VPC:</p><pre>Host aurora-serverless-tunnel<br>    IdentityFile ~/.ssh/my-pem.pem<br>    User ubuntu<br>    Hostname &lt;EC2 IP Address&gt;<br>    StrictHostKeyChecking no<br>    LocalForward 3306 &lt;aurora cluster endpoint&gt;:3306</pre><p>When connecting from an application deployed in the same VPC, it is functionally the same as a normal RDS/Aurora setup.</p><h4>Conclusion</h4><p>The best parts of this setup are that it can be written and configured almost exactly as you would write a normal web app or API and that it costs pennies on the dollar for a side project. I haven’t used Aurora Serverless at scale, so I can’t recommend it for a production application since I’m not sure how well it performs at scale, but the proxy function approach can easily be used in production. In fact, at <a href="https://engineering.videoblocks.com/">Storyblocks Engineering</a> we have started using this approach for our production API. I look forward to seeing the proliferation of technologies like this in the coming years, as it makes web development extremely simple to deploy and scale.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=dfba9c26a570" width="1" height="1" alt=""><hr><p><a href="https://medium.com/storyblocks-engineering/the-lan-stack-going-100-serverless-dfba9c26a570">The LAN Stack: Going 100% Serverless</a> was originally published in <a href="https://medium.com/storyblocks-engineering">The Storyblocks Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[NodeJs Clustering: Don’t Spare the CPU Horses]]></title>
            <link>https://medium.com/storyblocks-engineering/nodejs-clustering-dont-spare-the-cpu-horses-52c9edc3c130?source=rss-249d9a458e11------2</link>
            <guid isPermaLink="false">https://medium.com/p/52c9edc3c130</guid>
            <category><![CDATA[open-source]]></category>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[Chas DeVeas]]></dc:creator>
            <pubDate>Sat, 04 Aug 2018 23:04:24 GMT</pubDate>
            <atom:updated>2018-08-09T20:58:02.651Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2RwV9M-R0aWC1DIc862LdA.jpeg" /><figcaption>Photo by James Wainscoat on Unsplash</figcaption></figure><p>If you’re running a Nodejs process on dedicated servers and not using all of the available CPUs, adding less than 20 lines of code could get you more bang for your buck.</p><p>A normal server function might look like this:</p><pre>// startServer.js<br><br><strong>const </strong>server = require(&#39;../src/app/server&#39;);<br><strong>const </strong>logger = require(&#39;../src/app/util/logger&#39;);<br><br>server.start();</pre><p>Using the builtin NodeJs <a href="https://nodejs.org/api/cluster.html">node-cluster</a> library, you can launch multiple instances of your server process. The best part? The instances will automatically <strong><em>share the original server port</em></strong>, meaning you can implement this with zero infrastructure or deployment changes:</p><pre>// startClusteredServer.js<br><br><strong>const </strong>cluster = require(&#39;cluster&#39;);<br><strong>const </strong>os = require(&#39;os&#39;);<br><br><strong>const </strong>server = require(&#39;../src/app/server&#39;);<br><strong>const </strong>logger = require(&#39;../src/app/util/logger&#39;);<br><br><strong>if </strong>(cluster.<em>isMaster</em>) {<br><br>  logger.info(&#39;Cluster master is running. Forking workers.&#39;);<br><br>  <strong>const </strong>NUM_CPUS = os.cpus().length;<br>  <strong>for </strong>(<strong>let </strong>i = 0; i &lt; NUM_CPUS; i++) {<br>    cluster.fork();<br>  }<br><br>  cluster.on(&#39;exit&#39;, (worker, code, signal) =&gt; {<br>    logger.error(&#39;Cluster worker died.&#39;, {code, signal, pid: worker.process.pid});<br>  });<br><br>} <strong>else </strong>{<br>  logger.info(&#39;Worker starting server.&#39;);<br>  server.start();<br>}</pre><p>At Storyblocks, we have been using this approach for over a year on our NodeJs services which are still on servers, and it has appreciably cut down the hardware required. While we are moving more towards Elastic Container Service to more efficiently use our hardware, this is definitely a quick win which requires no infrastructure changes.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=52c9edc3c130" width="1" height="1" alt=""><hr><p><a href="https://medium.com/storyblocks-engineering/nodejs-clustering-dont-spare-the-cpu-horses-52c9edc3c130">NodeJs Clustering: Don’t Spare the CPU Horses</a> was originally published in <a href="https://medium.com/storyblocks-engineering">The Storyblocks Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A Tutorial on AWS Fargate]]></title>
            <link>https://medium.com/@chasdevs/a-tutorial-on-aws-fargate-bd63828b6d6c?source=rss-249d9a458e11------2</link>
            <guid isPermaLink="false">https://medium.com/p/bd63828b6d6c</guid>
            <category><![CDATA[fargate]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[containers]]></category>
            <category><![CDATA[aws-ecs]]></category>
            <category><![CDATA[docker]]></category>
            <dc:creator><![CDATA[Chas DeVeas]]></dc:creator>
            <pubDate>Mon, 04 Jun 2018 00:58:25 GMT</pubDate>
            <atom:updated>2018-06-04T00:58:25.626Z</atom:updated>
            <content:encoded><![CDATA[<p>Exploring the latest useful advances in containerization deployment on Amazon’s cloud platform.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*VZf19QtcEYsMaA912V0pkQ.jpeg" /><figcaption>Containers — Photo by <a href="https://unsplash.com/photos/RJjY5Hpnifk?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Erwan Hesry</a> on <a href="https://unsplash.com/search/photos/containers?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><p>This past month at Storyblocks engineering we held a team-wide interactive <a href="https://aws.amazon.com/fargate">AWS Fargate</a> workshop. Since we are moving our core products from docker containers on EC2 over to ECS/Fargate, we figured it would be fun to get everyone up to speed using a competitive team-based workshop. The tutorial is open-source and hosted here: <a href="https://github.com/Footage-Firm/learn-fargate">https://github.com/Footage-Firm/learn-fargate</a>.</p><h3>Why Fargate?</h3><p>We are migrating our core site and services to Fargate in order to simplify our DevOps. Fargate allows users of Amazon’s Elastic Container Service to deploy a collection of containerized services <em>without managing the servers on which they run</em>. This is a very powerful distinction: before Fargate, one had to manage both the deployment and scaling of containers via ECS in addition to the auto-scaling of the EC2 boxes running the containers. This added simplicity (subtracted complexity?) had just the activation energy we needed to make the switch for our DevOps processes.</p><h3>ECS Fargate Basics</h3><h4>Terminology</h4><ul><li><strong>Task Definition: </strong>The blueprint for a task, which is used by ECS to launch containers.</li><li><strong>Task: </strong>A group of one or more containers launched and maintained by ECS based on a preset Task Definition. This is the smallest unit.</li><li><strong>Service: </strong>A service encapsulates a specific group of like tasks and is most commonly used to scale tasks. A service is not required to run a task.</li><li><strong>Cluster: </strong>A physical grouping of underlying servers that holds all of your services, tasks, etc. This is the largest unit.</li><li><strong>ECR: </strong>Elastic Container Registry hosts images similar to Docker Hub.</li></ul><h4>Overview</h4><p>Any process running on Fargate starts with a Task Definition, which you can set up via the AWS Console or via Cloudformation. It both defines the limits and capabilities of the task, and defines the container parameters to tell the task which docker images to run and how to map ports, etc. The CPU and Memory properties of the Task Definition will ultimately determine how much horsepower your Task gets from Fargate and how much the Task will cost to run.</p><p>With a Task Definition in place, you can either launch individual Tasks or encapsulate multiple Tasks within a Service, allowing you to connect them to a load balancer and scaled just like EC2 instances. Tasks can be configured to run multiple containers, such as a side-car logging service for a web container. Multiple logically-related Services and/or Tasks can be launched on a Cluster.</p><h3>The Tutorial</h3><p>To teach our team how to use ECS Fargate, we created a simple open-source <a href="https://laravel.com/">Laravel</a> app which lets teams compete in a race to watermark 10,000 images located in AWS S3.</p><p><a href="https://github.com/Footage-Firm/learn-fargate">https://github.com/Footage-Firm/learn-fargate</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*G95feHGkOVfL2rCJgJjN2A.png" /></figure><p>The application works by letting teams of two or three people register a team name via a simple form. When the team name is registered, a worker on the default queue immediately creates a folder for their team name in the <strong>teams </strong>folder of the S3 bucket and then creates 10,000 image-watermark jobs on a queue with the given team name. The non-watermarked source images were located in the <strong>source-photos </strong>directory of the S3 bucket.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WasQwnxQxEXJjUZbsUb5WA.png" /><figcaption>Team Registration Page</figcaption></figure><p>In order to start watermarking images, a team would have to check out the codebase, build a worker image with the correct queue name, and then create Fargate Tasks to start executing jobs on their queue. They could make progress easily by running the worker process locally, but the idea was to get everyone using hyper-parallelization on Fargate with the stroke of a few keys.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Y9j9qd-IOfr_XIcOkig-6w.png" /><figcaption>Team Progress Page</figcaption></figure><h3>Results</h3><p>After a couple hours and a few pizzas, all of the teams had made good progress on their watermarking queues, with the victorious team having figured out how to launch beefy Task Definitions with dozens of worker containers for each Task (extra-hyper parallelization). Everyone walked away with an appreciation of how easy it is to get production-ready applications deployed and parallelized with minimal infrastructure configuration.</p><p>If you want an interactive way to bring Fargate to your team or just try it out yourself, check out the tutorial!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=bd63828b6d6c" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Phasing into a Microservice with Zero Downtime: Part I (Refactor)]]></title>
            <link>https://medium.com/storyblocks-engineering/phasing-into-a-microservice-with-zero-downtime-part-i-refactor-c989f0899e50?source=rss-249d9a458e11------2</link>
            <guid isPermaLink="false">https://medium.com/p/c989f0899e50</guid>
            <category><![CDATA[refactoring]]></category>
            <category><![CDATA[microservices]]></category>
            <category><![CDATA[software-development]]></category>
            <dc:creator><![CDATA[Chas DeVeas]]></dc:creator>
            <pubDate>Sun, 03 Jun 2018 22:27:35 GMT</pubDate>
            <atom:updated>2018-06-04T01:00:20.453Z</atom:updated>
            <content:encoded><![CDATA[<h4>Refactoring consumers of the service to have clean service boundaries.</h4><p><em>For an introduction to this series, see </em><a href="https://medium.com/p/af2333875a33"><em>Phasing into a Microservice with Zero Downtime: Intro</em></a></p><p>Most microservice projects should begin with the API definition, which means figuring out every way that the consumers of the service interact with it. For a legacy monolithic codebase, this can be very complicated due to either missing or leaking service boundaries in the code. In our case, we had a PHP ORM for our user entities which had escaped and run rampant through every conceivable part of the code (See our VP of Engineering Jonathan Fulton’s post on how a <a href="https://engineering.videoblocks.com/the-new-three-letter-design-pattern-for-the-web-that-will-take-you-beyond-mvc-a65b1236bea4">leaky ORM can get you into trouble</a>).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/640/1*nbpcOqFCmjFFyIAtkbsSiw.png" /><figcaption>Leaky service boundaries in legacy code</figcaption></figure><p>Creates, Reads, and Updates were happening everywhere, which made it difficult to conceive what the service boundary API should look like. Unfortunately, this means the first step on the microservice journey (and really most of the work) was to refactor that legacy code.</p><p>Our high-level approach for refactoring the legacy code:</p><ol><li>Create a UserService class to encapsulate all user CRUD operations.</li><li>Create a MySQL and a REST driver to be used by the service.</li><li>Identify all existing ORM Create operations in the codebase.</li><li>Implement all Create operations inside of the MySQL driver and connect the driver to the UserService API.</li><li>Write unit tests for the new driver methods and service API.</li><li>Convert all original Create operations to use the UserService API.</li><li>(Repeat for Updates and Reads).</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/361/1*OSVdnBsQ8_0s8XXTbE9RVQ.png" /><figcaption>UserService UML Diagram</figcaption></figure><p>That took a long time. A <em>very</em> long time. Before the refactor, a cursory search in the PHPStorm IDE for the ORM user objects would prompt you to cancel the search process after 1000 references were found.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/403/1*NRE299-d2PSCotp1U05uUg.png" /><figcaption>The total was between 3000 to 4000</figcaption></figure><p>Technical debt of this scale leads to challenges in project planning, project management, and even team morale. How can you know how long it will take to finish? How many engineers should work on it? Is it even worth refactoring or should a new system be created from the ground up? How can engineers work for months without losing interest and productivity? WHY DO UNIT TESTS KEEP FAILING ON MY BRANCH?! (For this project, it took 3 engineers about 6 months and by the end everyone needed a break).</p><p>Some general guidelines:</p><ul><li>If your codebase has unit tests which actually call the old source of record, try to mock the responses from the service (your other option is actually spinning up an instance of the service when running unit tests… not ideal). In our case, we had over two thousand tests with the majority relying on user queries (due to the unclean boundaries), and mocking would add too much time. It worked, but mocking the service would have been much more ideal.</li><li>“Services” in your codebase (or whatever you call the code that encapsulates interactions with the data) should only interact with their own drivers. The UserService may not use the PaymentService MySQL driver, for example.</li><li>When you are identifying the common CRUD operations used in the code, look for opportunities to minimize the final number of operations required by the service API. Try to converge and simplify the actions and API, reusing functionality as much as possible. For example, if your admin section of the code searches for users created in the last week while a part of your billing code fetches users older than a month, create a generic service method which takes optional beginDate and endDate parameters.</li><li>TDD is the name of the game with major refactors. If you do not have decent test coverage already in place, you will need to add time to the project to create test coverage. It may be worth investing in your CI/CD pipeline to get fast-running and non-flakey tests to run on every commit.</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c989f0899e50" width="1" height="1" alt=""><hr><p><a href="https://medium.com/storyblocks-engineering/phasing-into-a-microservice-with-zero-downtime-part-i-refactor-c989f0899e50">Phasing into a Microservice with Zero Downtime: Part I (Refactor)</a> was originally published in <a href="https://medium.com/storyblocks-engineering">The Storyblocks Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Analyzing Meeting Metrics Using the Google Calendar API]]></title>
            <link>https://medium.com/storyblocks-engineering/analyzing-meeting-metrics-using-the-google-calendar-api-3c76c9f8ffea?source=rss-249d9a458e11------2</link>
            <guid isPermaLink="false">https://medium.com/p/3c76c9f8ffea</guid>
            <category><![CDATA[startup]]></category>
            <category><![CDATA[google-cloud-platform]]></category>
            <category><![CDATA[meetings]]></category>
            <category><![CDATA[data-visualization]]></category>
            <category><![CDATA[golang]]></category>
            <dc:creator><![CDATA[Chas DeVeas]]></dc:creator>
            <pubDate>Wed, 27 Dec 2017 21:13:27 GMT</pubDate>
            <atom:updated>2021-06-04T03:36:59.512Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*K1mHm1dBwsQGCvs9A1xs6A.png" /></figure><blockquote><strong>*Update</strong>: Meetrics is now a real product! Check out <a href="https://meetrics.io/">meetrics.io</a> for an easy way to monitor your own organization’s meeting health.</blockquote><p>A group of us at Storyblocks recently decided to take a look at how much time the company was spending in meetings. We created meeting “rules of etiquette”, started polling employees to generate an NPS score for our meetings, and experimented with <em>No Meeting Wednesday</em> as a way to give people some dedicated heads-down time. As an engineer, I wanted a way to get some hard data from Google Calendar to track the impact of these efforts.</p><h4>The “Meetrics” Dashboard</h4><p>The ultimate outcome of my project was a dashboard (created using <a href="https://www.periscopedata.com/">Periscope</a>) showing compiled metrics for meetings by employees in our company’s Google “G Suite” domain. The metrics currently include average time spent in meetings and one-on-ones over time, a breakdown by department, and highest-impact meetings.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mQSfXCoxPa54UswcdRuWKQ.png" /><figcaption>A beta version of the Meetrics dashboard.</figcaption></figure><h4>How It Works</h4><p>The workflow is straightforward: A dockerized Go app (made extra-small using some docker multistage build <a href="https://blog.codeship.com/building-minimal-docker-containers-for-go-applications/">tips</a>) runs once per night via Jenkins and hits the Google Calendar API to calculate the previous day’s meeting statistics for each employee in our company.</p><p>The list of employee emails is manually-curated to sort each employee into a department and to prevent email aliases and miscellaneous email addresses in the organization from polluting the data. Alternatively, one could use the Google Admin API to list users in a given organization.</p><p>For each email address, all events are retrieved from the previous day and classified as having 0 guests, 1 guest, or 2+ guests. The number of minutes for each of these categories is then saved in a database entry. Additionally, large recurring meetings have some metadata stored so the dashboard can flag the most “impactful” (or expensive) events in the organization.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/638/1*9bpcdDrUWkmH36BW3dlDHg.png" /><figcaption>User minutes spent in meetings with 0 guests, 1 guest, and 2 or more guests during one day.</figcaption></figure><p>With the number of minutes each person spends in each of those three buckets of meetings during a day, we can try to make a reasonable guess at how much time on average they spend in large meetings, in one-on-ones or quick catch-ups, and on personal “crank” time (as many of our employees book their own time to get work done in a normal day).</p><p>One of the more challenging parts of the application was getting Google’s complicated authentication system configured correctly.</p><h4>Authentication</h4><p>Accessing all of a “G Suite Domain’s” user information from the calendar API requires some finagling of their OAuth 2.0 authentication system. I ended up creating a</p><p>Google’s service account <a href="https://developers.google.com/identity/protocols/OAuth2ServiceAccount">documentation</a> outlines how to do this. The basic steps I took were the following:</p><ol><li>Create a project in the Google APIs <a href="https://console.developers.google.com/apis/dashboard">dashboard</a> to hold the service account.</li><li>Create the service account in the <a href="https://console.developers.google.com/iam-admin/serviceaccounts">IAM &amp; admin</a><em> </em>section of your project.</li><li>Delegate domain-wide authority to the service account.</li><li>Log into the G Suite <a href="http://admin.google.com/">Admin</a> section (as an admin user) to authorize the service account for API scopes. For listing calendar information, you need the <strong>calendar.readonly</strong> scope: <em>https://www.googleapis.com/auth/calendar.readonly</em>. For listing users in the domain, you need the <strong>admin.directory.user.readonly</strong> scope: <a href="https://www.googleapis.com/auth/admin.directory.user.readonly."><em>https://www.googleapis.com/auth/admin.directory.user.readonly.</em></a></li><li>Go to the <a href="https://console.developers.google.com/apis/credentials">credentials page</a> in the Google APIs dashboard for your project, click <strong>Create credentials</strong> &gt; <strong>Service account key</strong>, select your service account and JSON as the key type, and click <strong>Create</strong>. This will download your credentials JSON file containing your private key which will be used in Google API SDKs.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*pls4dNYZfIEzGIzLvAESfg.png" /><figcaption>Identify the Client ID for the service account. Also, note the “Create credentials” button, which will be used to generate the private key for the service account</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7zHQFiUfMcegMdOXgf7hIw.png" /><figcaption>Grant scopes to the service account. For reading calendar events and basic user profile information, you need the <strong>calendar.readonly</strong> scope and <strong>admin.directory.user.readonly</strong> scope.</figcaption></figure><p>Note: This service account will allow your application to query various Google APIs <em>as users in your organization</em>. This means you will want to restrict the scopes as much as possible (stick to readonly):</p><blockquote>Your application now has the authority to make API calls as users in your domain (to “impersonate” users). When you prepare to make authorized API calls, you specify the user to impersonate.</blockquote><h4>Conclusion</h4><p>We have not revealed the dashboard to the company at-large yet, so I’m not sure if this will have a positive, negative, or null impact. It has proved an interesting conversation-starter: we’ve had fun speculating about peaks/troughs in the data during particular weeks and wondering if some of the larger meetings really need to be that large.</p><p>In the future I’d like to add meeting-related NPS data from a Slack poll to the dashboard to try and track employee satisfaction with meetings over time. The event data also needs to be filtered and analyzed more-accurately, as it currently suffers from duplicate events, miscounted all-day (vacation) events, misinterpreted invitation information (when a list or group is invited to an event in addition to individual members), etc. Assumptions also need to be validated, such as the one that meetings with 0 people are actually crank time as opposed to personal chores.</p><p><em>And of course I’ll be adding unit tests… at some point… probably.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3c76c9f8ffea" width="1" height="1" alt=""><hr><p><a href="https://medium.com/storyblocks-engineering/analyzing-meeting-metrics-using-the-google-calendar-api-3c76c9f8ffea">Analyzing Meeting Metrics Using the Google Calendar API</a> was originally published in <a href="https://medium.com/storyblocks-engineering">The Storyblocks Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Phasing into a Microservice with Zero Downtime: Intro]]></title>
            <link>https://medium.com/storyblocks-engineering/phasing-into-a-microservice-with-zero-downtime-intro-af2333875a33?source=rss-249d9a458e11------2</link>
            <guid isPermaLink="false">https://medium.com/p/af2333875a33</guid>
            <category><![CDATA[microservices]]></category>
            <category><![CDATA[software-development]]></category>
            <dc:creator><![CDATA[Chas DeVeas]]></dc:creator>
            <pubDate>Thu, 12 Oct 2017 01:33:19 GMT</pubDate>
            <atom:updated>2017-10-12T13:43:35.401Z</atom:updated>
            <content:encoded><![CDATA[<h4><em>How Storyblocks used a phased approach to migrate user accounts from three separate websites to a single microservice with zero downtime.</em></h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/541/1*me9BXMQfBJyaQf4OHtgvhw.jpeg" /><figcaption>Positive Exchange of Controls. Like checklists, it prevents death.</figcaption></figure><p>When a pilot hands the controls over to a copilot (or vice-versa), they both need to execute what is called the <a href="https://ntl.bts.gov/lib/1000/1100/1125/ac61-115.pdf">Positive Exchange of Controls</a>: The person with their hands on the yoke tells new pilot “you have the flight controls,” to which the new pilot replies “I have the controls,” which is followed by a final “you have the flight controls” from the pilot. This method ensures that the important thing — control of the airplane — is maintained without any confusion or lapses. Here at Storyblocks, we used a similar concept to hand-off control of our user accounts from their original MySQL databases to a centralized microservice.</p><p>Until recently, all user account data for our three core websites (<a href="http://audioblocks.com/">audioblocks.com</a>, <a href="http://videoblocks.com/">videoblocks.com</a>, and <a href="http://storyblocks.com/">storyblocks.com</a>) existed in separate databases. This means that if you signed up with your@email.com on VideoBlocks and then wanted to get access to AudioBlocks, you would have to sign up again on the new site. If you had accounts on both sites and updated your password on one of them, you would now have two different passwords for two of our sites. Not a great user experience. The solution? Put our user accounts in a single place.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/592/1*Yg_WnHmuVtlLoZb9-5n1FA.png" /><figcaption>From separate databases to a single microservice</figcaption></figure><p>In late 2016, we decided to carve user accounts into a single microservice. This would help us do the following: Pay down technical debt caused by old code and hacked-together features, unlock in-product capabilities like more targeted upgrade offers, simplify the overall user experience, scale our infrastructure, and shift our company mindset regarding users to be more holistic.</p><p>There are a few approaches you can take to a microservice migration like this, the simplest being to just build the service, temporarily shut-down the production sites, import the data from the old databases to the microservice, then turn the sites back on (what we called the “Big Bang” approach). In our case, because A) this refactor touched extremely high-risk business logic and B) this was our first stateful microservice, we opted to ease into it with a phased approach that also enabled zero downtime. The phases were as follows:</p><ol><li><strong>Phase I / </strong><em>Refactor</em>: Rewrite the code in the monolith so that all reads and writes to the data “domain” of the service have clean boundaries. This encapsulates all read/write logic in a single place and makes it easier to manage the hand-off between original database and microservice.</li><li><strong>Phase II / </strong><em>Write-Only</em>: Write user data to the original database and also send to the new service, but the original database remains the source of record for reading user data.<em> (“You have control of the airplane”)</em></li><li><strong>Phase III / </strong><em>Backfill</em>: Import the data from the old service to the new microservice. While users created since Phase II will have their information in the new service, pre-existing users will not. We’ll need their data before switching the source of record to the new service, and thus need to retroactively backfill older users.</li><li><strong>Phase IV / </strong><em>Attempt-Read</em>: In the monolith, start attempting to read from the service but allow minor data inconsistencies or even a total service failure. Log any data inconsistencies to help troubleshoot any bugs that pop up. Writes are still going to the monolith and it is STILL the source of record at this point.<em> (“I have control of the airplane”)</em></li><li><strong>Phase V / </strong><em>Read &amp; Write</em>: Turn off writes and reads to the old database, completing the transition to the new service. The microservice is finally the source of record. <em>(“You have control of the airplane”)</em></li></ol><p><strong>Conclusion</strong></p><p>By activating writes and backfilling old records in the background, we achieve zero downtime for our production applications. Also, by creating an attempt-read phase, we get a live preview for how the production sites are reading from the service. Response times, failed writes or reads, data integrity issues, etc., can all be troubleshot on-the-fly and in a low-risk environment. This Positive Exchange of Controls approach to a microservice is useful when refactoring a risky core domain of the monolith, and was invaluable to our migration of user accounts at Storyblocks.</p><p><em>This upcoming series of posts outlines those phases and some of our learnings from this process.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=af2333875a33" width="1" height="1" alt=""><hr><p><a href="https://medium.com/storyblocks-engineering/phasing-into-a-microservice-with-zero-downtime-intro-af2333875a33">Phasing into a Microservice with Zero Downtime: Intro</a> was originally published in <a href="https://medium.com/storyblocks-engineering">The Storyblocks Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Goal Yodeler]]></title>
            <link>https://medium.com/storyblocks-engineering/the-goal-yodeler-ce59d448b578?source=rss-249d9a458e11------2</link>
            <guid isPermaLink="false">https://medium.com/p/ce59d448b578</guid>
            <category><![CDATA[iot]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[blocks-video]]></category>
            <category><![CDATA[hackathons]]></category>
            <category><![CDATA[arduino]]></category>
            <dc:creator><![CDATA[Chas DeVeas]]></dc:creator>
            <pubDate>Sat, 10 Sep 2016 02:24:52 GMT</pubDate>
            <atom:updated>2017-06-28T12:50:15.473Z</atom:updated>
            <content:encoded><![CDATA[<p>What’s a fun way to measure your company’s monthly goals? How about a giant replica of the <a href="https://www.youtube.com/watch?v=YHv5jgXz9I8">Cliffhangers</a> game from The Price is Right?</p><iframe src="https://cdn.embedly.com/widgets/media.html?url=http%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DP6fcL4_TJ5M&amp;src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FP6fcL4_TJ5M%3Ffeature%3Doembed&amp;type=text%2Fhtml&amp;key=d04bfffea46d4aeda930ec88cc64b87c&amp;schema=youtube" width="640" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/a88549a86df680edf626a4a3e95758ab/href">https://medium.com/media/a88549a86df680edf626a4a3e95758ab/href</a></iframe><p>In late 2015, the VideoBlocks engineers decided to start a semi-annual VideoBlocks hackathon (which has since turned into our first company-wide “Innovation Day” this month). We sent out a spreadsheet to collect project ideas from the company, and among the great ideas (#-of-downloads LED ticker, a 24/7 CS 1.5 office server, a Slack doorbell for the office, a <a href="https://www.npmjs.com/package/peapod">peapod API</a> for Slack), there was this gem added by our now-CEO TJ Leonard:</p><blockquote>In the style of the Cliffhanger Game on the Price is Right, we should have a Swiss Hiker marching towards our revenue goals each month, showing daily (or even real time) progress. When we reach our goal, the Swiss Hiker should break out in a Yodeling Song that the entire office can hear.</blockquote><p>Since it was our first hackathon, we took it as the joke that it was and moved on to more approachable projects. When the time came for hackathon #2, though, our very own <a href="https://twitter.com/MathiasHansen">@MathiasHansen</a> dared to ask “why not?”</p><h3>Design &amp; Execution</h3><p><em>Home Depot, Kinkos, Arduino, 3D Printing, and APIs.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_2HrtcNmz1NZ4XC-eVgRLQ.jpeg" /><figcaption>It had to big, beautiful, loud, and functional.</figcaption></figure><h4>Step 1: How will he climb?</h4><p>This was the biggest unknown for us, so we took to the internet. We found an open source project that looked pretty similar to our requirements, and had some schematics and how-to’s already in place. We would just need to <a href="https://learn.adafruit.com/bluetooth-motorized-camera-slider/3d-printing">3D print some parts</a> and buy a servo, belt, and rails. Once we were confident in our motor/rail design and knew its limitations, we could get the dimensions for the final product.</p><p>The motor is a <a href="https://www.sparkfun.com/products/9238">Sparkfun Stepper Motor</a>, which lets you give the driver a number of “steps” you want for it to rotate. We picked a rotation speed that we liked, and then had the motor run a full circuit in order to get a measurement for our total number of steps.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/400/1*ISTYs5v0QtqbTUcDvLzXAg.jpeg" /><figcaption>3D Printing the Motor Housing</figcaption></figure><h4>Step 2: How will he yodel?</h4><p>I happened to have an old assembled <a href="https://www.adafruit.com/product/94">Adafruit Wave Shield</a> from a previous project, so it was a simple matter of finding an mp3 of the Cliffhangers song and loading it onto an SD card. The shield has a built-in 3.5mm headphones jack, so any speakers would do.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/400/1*v8hlaJ52zzYXITV_884HAg.png" /><figcaption>Arduino Wave Shield + Yodeling MP3</figcaption></figure><p>We knew the wave shield would not be driving the motor or running the yodeler logic (its only job is to make sound), so we created a very simple program that would loop the desired MP3 when an input pin went to 5 volts. The main microcontroller would activate sound this way.</p><h4><strong>Step 3: Making the Mountain</strong></h4><p>We knew how big it <em>could</em> be from Step 1, so the next step was heading to Home Depot with some spitballed dimensions and little idea of what we actually wanted to buy. We ended up using 2x4 boards for the frame (and the mount for the motor/rail unit), and sheets of MDF for the mountain and background. After a little woodworking and hammering, we were good to go!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/400/1*dUmzVPM-q12Oa1RjfcuQCQ.jpeg" /><figcaption>Mounting MDF to the Frame</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/400/1*xFs8ekrnjBQy0zFpbvEvsA.png" /><figcaption>Rail, Belt, Yodeler, Microcontroller, Driver, and Motor</figcaption></figure><h4>Step 4: Make it Look Good</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/280/1*MYgXLfO3SO5OVFTNi_dYaQ.jpeg" /><figcaption>Attaching the Vinyl</figcaption></figure><p>A vector image of the mountain and background was clearly the way to go. Fortunately, we had access to VideoBlocks world-class graphic designer (Nick), who took some time off of beautifying our websites to draw up the vector. Using the FedEx online portal, we ordered a giant print on white vinyl. A couple layers of adhesive, and the vinyl was easy to attach.</p><h4>Step 5: Give it Data</h4><p>The last step was giving the yodeler some company data so it knew when to yodel. We aimed for simplicity, so a very simple endpoint running on our hubot server (something like /yodeler-info) that responded with a minimal JSON object would work just fine:</p><blockquote>{min: 0, max: 100, value: 63, percent: 0.63}</blockquote><p>The server computes that information using our analytics database living in Amazon Redshift, and is (currently) based on a monthly cash goal.</p><p>For the client, we went super simple and used a <a href="https://www.sparkfun.com/products/13711">SparkFun ESP8266 Thing</a> board (Mathias had previous experience with it), which has a wifi chip on board and costs less than $15. The client simply establishes a wifi connection and then queries our endpoint in a loop. The code is not public, but is very similar to the public code I wrote for our “build status light bulbs”, <a href="https://github.com/cdeveas/arduino-build-status">here</a>.</p><p>The Thing board computes the correct number of steps using the “percent” field from the endpoint, and then drives the yodeler to that point. When it reaches 100%, the unit runs a full circuit while activating the yodeling wave shield by setting an output pin to 5 volts. Then, it goes back to the bottom.</p><h4>Step 6: ???</h4><h4>Step 7: Profit</h4><p>And that’s that. We hope to continue the VideoBlocks tradition of quirky hackathon (now Innovation Day) products, and continue to grow this blog!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ce59d448b578" width="1" height="1" alt=""><hr><p><a href="https://medium.com/storyblocks-engineering/the-goal-yodeler-ce59d448b578">The Goal Yodeler</a> was originally published in <a href="https://medium.com/storyblocks-engineering">The Storyblocks Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>