If you are thinking of modernizing any of your legacy applications and want to focus only on your applications and not the infrastructure, this article is for you. Consider serverless architecture if you’re interested in reducing the cost of development and operations for your next project.

Serverless architecture is based on a cloud computing execution model, it is driven by events, and its resources are dynamically managed and automatically scaled by the cloud providers. A serverless framework could potentially free up your time, because you won’t need to maintain, debug, and monitor your own infrastructure.

Prerequisites

This article assumes that you have working experience with JavaScript and have some familiarity with cloud terminology.

Estimated time

Take 5-10 minutes to read this article and consider some of the tools we used, and invest more time if you want to try to work with serverless architecture yourself.

Our challenge at Weather Underground

For any company, there will always be technical debts that you have to deal with as technology evolves. Whether it’s revamping a site, refactoring or optimizing an application, or adding new features, you always want to find new and better ways to make improvements. The Weather Company is no exception. We spent a better part of this year on modernizing our Weather Underground website.

Wunderground.com is a site that caters to a unique set of our audience: users who are into data visualization and the science of weather. Our user base ranges from developers, students, education institutes, DIY hobbyists, scientists, government, the general public, and other knowledgeable users. One of the legacy products we needed to migrate to the new stack was the radar maps feature of Intellicast. The Intellicast product suite has been around since 1996, providing expert weather information to smart, weather-savvy users. With the most technically advanced site-specific weather forecasting system in the world today, we deliver site-specific forecasts for 60,000 sites in the U.S. and around the globe – from detailed local forecasts to hurricane tracks, and severe weather warnings to international conditions. Intellicast is the home of active weather tools that business professionals and outdoor enthusiasts have come to rely on, consistently making us one of the top five weather sites on the internet.

Intellicast products for the web was powered by some middleware that was scheduled to be decommissioned by 2018. To replace the existing system with the new, we had the following few options for how and where we should build and deploy:

  • Option 1: Traditional hosting
  • Option 2: Cloud hosting
  • Option 3: Serverless computing

Because the imagery for different map products come in periodically and at different time intervals (depending on the product type), the middleware has the following main responsibilities:

  • Provide endpoints for the web client to consume the different map products based on geo locations such as current radar and windstream.
  • Based on the request and location preferences, fetch and package the proper assets from Amazon S3 (soon to be moved to IBM Cloud Object Storage) for that request. Generate thumbnails and animated gifs, and then then return the server-side rendering HTML to the web client.

Both options 1 and 2 require some DevOps work, and instance servers must be run 24×7. Because we only needed to package each map product periodically, option 3 was the ideal choice for this particular requirement. Going with a serverless approach, we cut out the time needed in setting up the server and deploying. Not needing instance servers up and running 24 hours a day is another advantage. We created a composition of serverless functions that consists of one composer (intellicast-composer.js) and two actions (intellicast-action.js and intellicast-animate-action.js) to perform the equivalent processes. Later sections explain the difference between the two, when to use an action, and when to use both.

Our solution for Intellicast radar maps

We structured our serverless components in the following way:

  • We restructured how assets are delivered to our S3 bucket and grouped each product by type, region, and date. You can think of it in terms of a directory such as radar/usa/20181222/. As mentioned earlier, assets do come in at different time intervals for different product types. Grouping assets by date helps processing and maintaining more efficiently.
  • Instead of creating traditional server/endpoints, we created a composer and actions. An action is triggered periodically to traverse the S3 bucket and to generate a JSON file that has references to the most recent asset files for each region of a product type. Each JSON file is then uploaded to the corresponding path (a directory, in a sense) in the S3 bucket. The JSON file is used by the browser to pull in the proper assets to assemble the page view on the client side. In the same process, this action also generates a thumbnail.gif file to pair with each generated JSON file. A total of more than 1200 files (both JSON and GIF) are generated and uploaded to S3 for each invocation of this action.

In our case, we set up a trigger, which is a cron job that is scheduled to run and invoke the action every ten minutes. Each invocation of this action takes approximately 15 seconds to complete, which is pretty amazing. What is left are the animated GIF files. About half of the map products offers animation, which means about 300 animated GIF files (ranging from 7 frames to 24 frames) must be generated.

At first I was trying to pack everything into this same action, and I quickly ran into memory limits. By default, an action is set up with 256MB, which increased the memory to 2048MB for it to stop complaining. Then I hit another limit: the exceeds allowed threshold of 600000 milliseconds roadblock.

Now the Composer programming model comes in. You can break your action into smaller sub tasks or actions and use the Composer as a way to orchestrate when particular actions should run. Composer comes with a set of APIs that you can use to stitch up your actions into a comprehensive application.

Therefore, instead of packing the animation process into the same action, I created a second action: intellicast-animate-action. The main action continues to perform its tasks, and when they are completed, it returns an array of objects that contains references to images to produce the animation for each product. My first attempt was to run the intellicast-animate-action in a composer.while loop. I also created a while action to set the parameters before each invocation of intellicast-animate-action, as shown in the following code example:

In while-action.js:

        function main(params) {
            params.start++;
           if (params.start < params.items.length) {
                params.value = [params.items[params.start]];
                return { value: true };
           }
           return { value: false };
}

In composer index.js:

        module.exports = composer.sequence(
            composer.let(
              {items: [], thumbnails: [], start: -1, params: {}},
              composer.try(
                    // generate and upload json files and thumbnails by walking directories
            'tools/intellicast-action',
                  args => ({result: 'something went wrong'})
              ),
              x => {items = x.body.result.animate, thumbnails = x.body.result.thumbnail,
params = x.body.params},
              composer.while(tools/while-action,
                    'tools/intellicast-animate-action'
              )
          )  
         )

The following visual flow diagram helps visualize the entire process of the composition:

serverless intellicast architecture flow diagram

Because each invocation of the intellicast-animate-action is synchronous, the composition exited before it had a chance to complete all of its work. Each action is limited to 10 minutes. What I needed was a way to process each invocation in parallel. With a little research and a few questions posted to the IBM Cloud Functions team, I learned that there is a composer.async, which gave me what I needed. By applying composer.async to the previous code – and with a little refactoring – it looks like the following code example:

module.exports = composer.sequence(
  composer.let(
    {items: [], thumbnails: [], start: -1, params: {}, batch: 1},
    // generate and upload json files and thumbnails by walking directories
    composer.try(
      'tools/intellicast-action',
      args => ({result: 'something went wrong'})
    ),
    x => {items = x.body.result.animate, thumbnails = x.body.result.thumbnail, params = x.body.params},
    composer.while(() => ++start < items.length,
      composer.async(
        x => (Object.assign(params, {value: items.slice(start, start+batch)})),
        'tools/intellicast-animate-action'
      )
    )
  )
)

By executing the intellicast-animate-action (one animation per invocation) through composer.async and composer.while, I was able to complete the entire process (including generating all the necessary JSON files, thumbnails, and animations) in less than one minute.

The following visual flow diagram helps visualize the entire process of the composition, using composer.async with the composer.while loop:

serverless visual flow diagram

serverless visual flow diagram

You can see the finished product at www.wunderground.com/maps.

I have tried running intellicast-animate-action by the batch for each invocation by dialing up the batch number and still staying within the maximum allowable time limits. At 30 animations per invocation, the process takes approximately 8 minutes to run.

I’m still trying determine which is more cost effective to run: having more invocations (one animation per invocation) or having fewer invocations (such as 30 animations per invocation). If anyone knows the answer to this, please contact me.

Summary

Overall, there are three main benefits we saw from taking the serverless route:

  • Reduced development cost
  • Reduced operational cost
  • Auto horizontal scaling

The next phase of our migration is to move from S3 to IBM Cloud Object Storage.