<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0"><channel><title>Pulumi Blog: Data science</title><link>https://www.pulumi.com/blog/tag/data-science/</link><description>Pulumi blog posts: Data science.</description><language>en-us</language><pubDate>Thu, 18 Mar 2021 00:00:00 +0000</pubDate><item><title>Data Science in the Cloud</title><link>https://www.pulumi.com/blog/data-science-in-the-cloud/</link><pubDate>Thu, 18 Mar 2021 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/data-science-in-the-cloud/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/data-science-in-the-cloud/index.png" /&gt;
&lt;p&gt;Data science has advanced because tools like Jupyter Notebook hide complexity by running high level code for the specific problem they are trying to solve. Increasing the level of abstraction lets a data scientist be more productive by reducing the effort to try multiple approaches to near zero, which encourages experimentation and better results.&lt;/p&gt;
&lt;p&gt;Data scientists typically work locally, but they often store data for analyses and models in the cloud. There are clear advantages to using cloud resources for these tasks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Data scientists generally don’t want to manage their storage and databases.&lt;/li&gt;
&lt;li&gt;They need to be able to store large data sets cheaply.&lt;/li&gt;
&lt;li&gt;They need large capacity swings available on-demand.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;SDKs like AWS’ Python library, &lt;code&gt;boto3&lt;/code&gt;, can create resources, but they still require domain expertise to manage and properly architect a solution. The Pulumi Automation API improves on raw SDKs by providing high-level abstractions for creating and managing cloud services, letting data scientists concentrate on analyses and models without being well-versed in cloud APIs.&lt;/p&gt;
&lt;h2 id="how-pulumi-automation-api-improves-on-cloud-sdks"&gt;How Pulumi Automation API Improves on Cloud SDKs&lt;/h2&gt;
&lt;p&gt;Pick any cloud provider, and you will find a cloud SDK in various languages, for example &lt;a href="https://aws.amazon.com/tools/"&gt;AWS SDKs&lt;/a&gt;, &lt;a href="https://azure.microsoft.com/en-us/downloads/"&gt;Azure SDKs&lt;/a&gt;, and &lt;a href="https://cloud.google.com/sdk"&gt;Google Cloud SDKs&lt;/a&gt;. These SDKs directly access cloud resources, letting data scientists provision resources as required. The drawback to using these libraries is that the data scientist is also responsible for providing resources reliably and securely and performing modeling and analysis.&lt;/p&gt;
&lt;p&gt;Pulumi reduces the burden of creating and maintaining repeatable and robust deployments by providing the latest language runtimes, concurrency management, support for diffing updates, and maintaining state with robust checkpoints. Your infrastructure will always be in a well-defined and recoverable state.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/pulumi/automation-api-examples"&gt;Automation API&lt;/a&gt; takes this one step further by providing an API overlay to Pulumi&amp;rsquo;s Infrastructure as Code engine. A data scientist can build infrastructure programmatically within Jupyter to automate building and creating infrastructure as functions and call them as needed. Let&amp;rsquo;s take a look at an example using a Jupyter notebook.&lt;/p&gt;
&lt;h2 id="infrastructure-as-a-function"&gt;Infrastructure as a function&lt;/h2&gt;
&lt;p&gt;As a data scientist, you might want to share data, charts, or graphs with remote team members and make them available through a shared website. You want to keep iterating on your model but don&amp;rsquo;t want to update the site manually, or worse, let it get out of date.&lt;/p&gt;
&lt;p&gt;This &lt;a href="https://github.com/pulumi/automation-api-examples/blob/main/python/pulumi_via_jupyter/automation_api.ipynb"&gt;example&lt;/a&gt; is a simple overview of Automation API that demonstrates a basic deployment of a static website from within a Jupyter notebook. This example uses an inline program to define infrastructure within a function alongside your other code. The function called &lt;code&gt;s3_static_site&lt;/code&gt; creates an s3 bucket, sets it up as a basic static website, and exports the URL. This is the equivalent of creating a Pulumi &lt;a href="https://www.pulumi.com/docs/concepts/stack/"&gt;stack&lt;/a&gt; using the CLI.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ve added code to the example to upload files written to the &lt;code&gt;www&lt;/code&gt; directory. Every time you want to update the website, you can write a file to the directory and call the function to upload it to the s3 bucket. Pulumi maintains state, so it will only upload new files when the function is called.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pulumi_aws&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;s3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;s3_static_site&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# Create a bucket and expose a website index document&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;site_bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;s3-website-bucket&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;website&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BucketWebsiteArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index_document&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;index.html&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;index_content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &amp;lt;html&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &amp;lt;head&amp;gt;&amp;lt;title&amp;gt;Hello S3&amp;lt;/title&amp;gt;&amp;lt;meta charset=&amp;#34;UTF-8&amp;#34;&amp;gt;&amp;lt;/head&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &amp;lt;body&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &amp;lt;p&amp;gt;Hello, world!&amp;lt;/p&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &amp;lt;p&amp;gt;Made with ❤️ with &amp;lt;a href=&amp;#34;https://pulumi.com&amp;#34;&amp;gt;Pulumi&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &amp;lt;/body&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &amp;lt;/html&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# Write our index.html into the site bucket&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BucketObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;index&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;site_bucket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# reference to the s3.Bucket object&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;index_content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;index.html&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# set the key of the object&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;content_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;text/html; charset=utf-8&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# set the MIME type of the file&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# Set the access policy for the bucket so all objects are readable&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BucketPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;bucket-policy&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;site_bucket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Version&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;2012-10-17&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Statement&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Effect&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Allow&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Principal&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;*&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Action&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;s3:GetObject&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# Policy refers to bucket explicitly&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Resource&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Output&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;arn:aws:s3:::&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;site_bucket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/*&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# Export the website URL&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;export&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;website_url&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;site_bucket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;website_endpoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can write additional functions to &lt;a href="https://nbviewer.jupyter.org/github/pulumi/automation-api-examples/blob/main/python/pulumi_via_jupyter/automation_api.ipynb#Automating-your-deployment"&gt;deploy and destroy&lt;/a&gt; the S3 static website. We deploy the stack by calling the &lt;code&gt;deploy_project&lt;/code&gt; function and pass it the &lt;code&gt;s3_static_site&lt;/code&gt; function, the Pulumi AWS plugin, and the AWS region.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;s3_site&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;deploy_project&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;my_first_project&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;s3_static_site&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;plugins&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;aws&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;v3.23.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;aws:region&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;auto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConfigValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;us-west-2&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can view the resources that the function created in two ways. The standard method is to open the AWS console to see your resources.&lt;/p&gt;
&lt;p&gt;&lt;img src="aws_console.png" alt="AWS Console"&gt;&lt;/p&gt;
&lt;p&gt;You can also view them with the &lt;a href="https://app.pulumi.com/signin"&gt;Pulumi Service&lt;/a&gt; as a resource graph.&lt;/p&gt;
&lt;p&gt;&lt;img src="pulumi_resource_graph.png" alt="Pulumi resource graph"&gt;&lt;/p&gt;
&lt;p&gt;The Pulumi Service also displays updates to the static site and the changes made.&lt;/p&gt;
&lt;p&gt;&lt;img src="pulumi_updates.png" alt="Pulumi updates"&gt;&lt;/p&gt;
&lt;p&gt;Now that the static site has been deployed, we want to get the site URL. The &lt;code&gt;s3_static_site&lt;/code&gt; function exports the website url. Pulumi returns the s3_site as an object with &lt;a href="https://www.pulumi.com/docs/concepts/inputs-outputs/"&gt;outputs&lt;/a&gt; that we use to retrieve the URL. There are several ways to check if the site is working, but a simple way is to open the URL in a web browser.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;webbrowser&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;outputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3_site&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outputs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;webbrowser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src="browser.png" alt="Browser"&gt;&lt;/p&gt;
&lt;h2 id="data-science-your-way"&gt;Data Science Your Way&lt;/h2&gt;
&lt;p&gt;In this simple example, we demonstrated how to create a static website from an S3 bucket, write content into the bucket, and set the access policy as a Python function. We called the function, which created a stack for the website. The stack is returned as a Python object with the site URL as an output. You can manage the site and view updates using the Pulumi Service.&lt;/p&gt;
&lt;p&gt;Because it&amp;rsquo;s code, you can create a separate module containing all the functions to create your infrastructure, import it into your notebooks, and reuse it. Pulumi lets you perform analysis and build models within a familiar environment while reducing the burden of managing infrastructure. You can find the complete example on &lt;a href="https://github.com/pulumi/automation-api-examples/tree/main/python/pulumi_via_jupyter"&gt;GitHub&lt;/a&gt; including a Jupyter notebook for &lt;a href="https://github.com/pulumi/automation-api-examples/blob/main/python/pulumi_via_jupyter/database_migration.ipynb"&gt;database migration&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Be sure to watch our video that explains the code in greater detail.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/8XFjqzX9ZK4?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
&lt;/div&gt;</description><author>Sophia Parafina</author><category>data-and-analytics</category><category>automation-api</category><category>python</category><category>jupyter</category><category>data-science</category></item><item><title>Data science on demand: spinning up a Wallaroo cluster</title><link>https://www.pulumi.com/blog/data-science-on-demand-spinning-up-a-wallaroo-cluster-is-easy-with-pulumi/</link><pubDate>Fri, 02 Nov 2018 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/data-science-on-demand-spinning-up-a-wallaroo-cluster-is-easy-with-pulumi/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/data-science-on-demand-spinning-up-a-wallaroo-cluster-is-easy-with-pulumi/index.png" /&gt;
&lt;p&gt;&lt;em&gt;This guest post is from Simon Zelazny of
&lt;a href="https://www.wallaroo.ai/"&gt;Wallaroo Labs&lt;/a&gt;.
Find out how Wallaroo powered their cluster provisioning with Pulumi,
for data science on demand.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Last month, we took a
&lt;a href="https://github.com/WallarooLabs/wallaroo_blog_examples/tree/master/provisioned-classifier/classifier"&gt;long-running pandas classifier&lt;/a&gt;
and made it run faster by leveraging Wallaroo&amp;rsquo;s parallelization
capabilities. This time around, we&amp;rsquo;d like to kick it up a notch and see
if we can keep scaling out to meet higher demand. We&amp;rsquo;d also like to be
as economical as possible: provision infrastructure as needed and
de-provision it when we&amp;rsquo;re done processing.&lt;/p&gt;
&lt;p&gt;If you don&amp;rsquo;t feel like reading the post linked above, here&amp;rsquo;s a short
summary of the situation: there&amp;rsquo;s a batch job that you&amp;rsquo;re running every
hour, on the hour. This job receives a CSV file and classifies each row
of the file, using a Pandas-based algorithm. The run-time of the job is
starting to near the one-hour mark, and there&amp;rsquo;s concern that the
pipeline will break down once the input data grows past a particular
point.&lt;/p&gt;
&lt;p&gt;In the blog post, we show how to split up the input data into smaller
dataframes, and distribute them among workers in an ad-hoc Wallaroo
cluster, running on one physical machine. Parallelizing the work in this
manner buys us a lot of time, and the batch job can continue processing
increasing amounts of data.&lt;/p&gt;
&lt;p&gt;Sure, we can handle a million rows in reasonable time, but what if the
data set grows by orders of magnitude? By running our classifier on a
local Wallaroo cluster, we were able to cut the processing time of a
million rows to ~16 minutes, thus fitting within our allotted time slot
of one hour. But if the input data is, say, 10x more, we&amp;rsquo;re going to
have a hard time processing it all locally.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s see how we can keep up with the data growth by launching a
cloud-based Wallaroo cluster on-demand, running the job, collecting the
data, and shutting down the cluster, all in a fully automated fashion.&lt;/p&gt;
&lt;h2 id="tools-of-the-trade"&gt;Tools of the trade&lt;/h2&gt;
&lt;p&gt;Wallaroo&amp;rsquo;s big idea is that your application doesn&amp;rsquo;t have to know
whether it&amp;rsquo;s running on one process, several local processes, or a
distributed system comprising many physical machines. In this sense,
there&amp;rsquo;s no extra work involved in &amp;lsquo;migrating&amp;rsquo; our classifier application
from the previous blog post.&lt;/p&gt;
&lt;p&gt;We will need some tools to help us set up and manage our cluster in the
cloud. Wallaroo can work with a lot of different tools. Our friends at
Pulumi provide an excellent tool that removes the
headaches involved in provisioning infrastructure. We&amp;rsquo;ll use Pulumi to
define, set up, and finally tear down our processing cluster in this
example.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ll also need &lt;a href="https://www.ansible.com/"&gt;Ansible&lt;/a&gt; to start, stop, and
inspect the state of our cluster, and, last but not least, we&amp;rsquo;ll need
an &lt;a href="https://aws.amazon.com/"&gt;AWS&lt;/a&gt; account where our machines will live.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s jump into it!&lt;/p&gt;
&lt;h2 id="a-sample-run"&gt;A sample run&lt;/h2&gt;
&lt;p&gt;First of all, if you&amp;rsquo;d like to follow along (and spend some money
provisioning EC2 servers), please
&lt;a href="https://www.pulumi.com/docs/install/"&gt;download and set up Pulumi&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Next, &lt;a href="https://github.com/WallarooLabs/wallaroo_blog_examples"&gt;clone the wallaroo blog examples repo&lt;/a&gt; and
navigate to &lt;code&gt;provisioned-classifier&lt;/code&gt;. If you followed along with the
previous Pandas blog post, you&amp;rsquo;ll find our old application nested away
here, under&lt;code&gt;classifier&lt;/code&gt;. What&amp;rsquo;s more interesting are the two new
directories: &lt;code&gt;pulumi&lt;/code&gt; and &lt;code&gt;ansible&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Without delving into details, let&amp;rsquo;s see how to run our application on a
freshly-provisioned cluster in the EC2 cloud:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;make up run-cluster get-results down &lt;span class="nv"&gt;INPUT_LINES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1000000&lt;/span&gt; &lt;span class="nv"&gt;CLUSTER_SIZE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src="https://www.pulumi.com/uploads/content/blog/data-science-on-demand-spinning-up-a-wallaroo-cluster-is-easy-with-pulumi/tty-fast.gif" alt="tty-fast"&gt;&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s break that down and see what&amp;rsquo;s really going on here.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;make up CLUSTER_SIZE=3&lt;/code&gt; configures the cluster to consist of 3
machines, and delegates to &lt;code&gt;pulumi up&lt;/code&gt; the actual business of spinning
up the infrastructure. Our physical cluster will contain 3 nodes for
processing, and one extra metrics_host node for hosting our Metrics
UI, and collecting results.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Once provisioning is complete, the next make
task: &lt;code&gt;run-cluster INPUT_LINES=1000000&lt;/code&gt; uses our Ansible playbooks to
upload application code from &lt;code&gt;classifier/*&lt;/code&gt; to all 3 machines
provisioned above, and then start up a Wallaroo cluster with 7 worker
processes per machine.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Next, Ansible starts sending 1 million lines of our
&lt;a href="https://github.com/WallarooLabs/wallaroo_blog_examples/blob/master/provisioned-classifier/bin/send.py"&gt;synthetic CSV data&lt;/a&gt;,
and waits for 1 million lines to arrive at the &lt;code&gt;data_receiver&lt;/code&gt; process.
When those lines arrive, they are compressed, and the cluster is shut
down.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;make get-results&lt;/code&gt; pulls the compressed result file
to &lt;code&gt;output/results.tgz&lt;/code&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;And finally, &lt;code&gt;make down&lt;/code&gt; destroys the cloud infrastructure that was
used to power our computation.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="the-pulumi-cluster-definition"&gt;The Pulumi cluster definition&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s take a look at how our infrastructure is defined. This is the core
of the
&lt;a href="https://github.com/WallarooLabs/wallaroo_blog_examples/blob/master/provisioned-classifier/pulumi/index.js"&gt;definition&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Instance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;associatePublicIpAddress&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;instanceType&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;instanceType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;securityGroups&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;secGrp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;ami&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ami&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Name&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;keyName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;keyPair&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keyName&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;metrics_host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;classifier-metrics_host&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;initializer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;classifier-initializer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;workers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;clusterSize&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;workers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;classifier-&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As you can see from the above, our little &lt;code&gt;instance()&lt;/code&gt; function
encapsulates the common settings for every machine that we want to
provision.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;metrics_host&lt;/code&gt; and &lt;code&gt;initializer&lt;/code&gt; are &lt;code&gt;ec2.Instance&lt;/code&gt; objects with
descriptive names, while the &lt;code&gt;workers&lt;/code&gt; are &lt;code&gt;ec2.Instance&lt;/code&gt;s that are
distinguished solely by their ordinal number. Pulumi lets us define &amp;ndash;
in code &amp;ndash; things like Security Groups, SSH keypairs, and practically
every other aspect of cloud infrastructure.&lt;/p&gt;
&lt;p&gt;An example of this capability is the &lt;code&gt;keyPair&lt;/code&gt; object that&amp;rsquo;s used to
access the instances via SSH. Our Makefile ensures that an ssh key is
generated on-the-fly for our cluster, and Pulumi knows how to use it to
set up SSH access for newly-provisioned nodes:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;let pubKey = fs.readFileSync(&amp;#34;../ssh_pubkey_in_ec2_format.pub&amp;#34;).toString();
let keyPair = new aws.ec2.KeyPair(&amp;#34;ClassifierKey&amp;#34;, {publicKey: pubKey});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With the relevant bits of our computing infrastructure thus defined, we
can tell Pulumi to take action in the real world and make it conform to
our definition:&lt;/p&gt;
&lt;p&gt;When we run &lt;code&gt;make up CLUSTER_SIZE=3&lt;/code&gt;, we&amp;rsquo;ll see Pulumi output something
like the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Performing changes:
+ pulumi:pulumi:Stack classifier-classifier-demo creating
+ aws:ec2:KeyPair ClassifierKey creating
(...)
+ aws:ec2:Instance classifier-2 created
---outputs:---
metrics_host: [
[0]: {
name : &amp;quot;classifier-metrics_host&amp;quot;
private_ip: &amp;quot;172.31.47.236&amp;quot;
public_dns: &amp;quot;ec2-54-245-53-87.us-west-2.compute.amazonaws.com&amp;quot;
}
(...)
]
info: 7 changes performed:
+ 7 resources created
Update duration: 1m59.524017637s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can use Pulumi&amp;rsquo;s output to stitch together an Ansible inventory,
which will let us interact programmatically with our provisioned
instances.&lt;/p&gt;
&lt;p&gt;If we want to modify our cluster, we can edit &lt;code&gt;pulumi/index.js&lt;/code&gt;, and
then rerun &lt;code&gt;make up CLUSTER_SIZE=N&lt;/code&gt;. If the changes don&amp;rsquo;t require
restarting the instances (for example if they only concern the Security
Group), Pulumi will do the right thing and not disturb the rest of the
infrastructure.&lt;/p&gt;
&lt;p&gt;If you examine the Pulumi definition file in depth, you&amp;rsquo;ll see that it
relies on a mystery AMI: &lt;code&gt;ami-058d2ca16567a23f7&lt;/code&gt;. This is an
experimental ubuntu-based image, with Wallaroo binaries
(&lt;code&gt;machida&lt;/code&gt;, &lt;code&gt;cluster_shutdown&lt;/code&gt;,&lt;code&gt;cluster_shrinker&lt;/code&gt;, and &lt;code&gt;data_receiver&lt;/code&gt;)
pre-loaded. For now, it only exists in the &lt;code&gt;us-west-2&lt;/code&gt; AWS region, but
we hope to make Wallaroo AMIs available for experimentation in all
regions starting with the next Wallaroo release.&lt;/p&gt;
&lt;h2 id="running-the-computation"&gt;Running the computation&lt;/h2&gt;
&lt;p&gt;Now that we know what magic powers conjured up our AWS infrastructure,
let&amp;rsquo;s take a look at how we use it to run our task. Fundamentally, the
components of our cluster can be described as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The data source &amp;ndash; In our case it&amp;rsquo;s the
file &lt;a href="https://github.com/WallarooLabs/wallaroo_blog_examples/blob/master/provisioned-classifier/bin/send.py"&gt;send.py&lt;/a&gt;,
which can generate and transmit randomly-generated CSV data for our
computation to consume.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The machida processes: one Initializer and a bunch of Workers. The
distinction between the two is only relevant at cluster startup.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The data receiver &amp;ndash; A process that listens on a TCP port for the
output of our computation. This is the &lt;code&gt;data_receiver&lt;/code&gt;, provided as part
of a Wallaroo installation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The metrics UI &amp;ndash; Our Elixir-powered realtime dashboard.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Our Ansible playbook takes care of coordinating the launch of the
various components and making sure that their input, output and control
ports match up. In particular, that the cluster initializer starts up
knowing the total number of workers in the cluster, and every other
worker connects to the initializer&amp;rsquo;s internal IP and control port.&lt;/p&gt;
&lt;p&gt;This is what ends up running on the servers when we launch our Ansible
playbooks:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.pulumi.com/blog/data-science-on-demand-spinning-up-a-wallaroo-cluster-is-easy-with-pulumi/wallaroo.png" alt="wallaroo"&gt;&lt;/p&gt;
&lt;p&gt;Once the cluster is up and running, and the initializer node&amp;rsquo;s
&lt;a href="https://github.com/WallarooLabs/wallaroo_blog_examples/blob/master/provisioned-classifier/classifier/classifier.py#L17"&gt;tcp source&lt;/a&gt;
is listening for connections, we start up the &lt;code&gt;sender&lt;/code&gt; and instruct it to
send a stream of data to the TCP Source. In a realistic batch scenario,
this sender could be implemented as a Connector that
reads a particular file from a remote filesystem or S3. For our
purposes, we&amp;rsquo;ll simulate this by generating a set number of CSV lines
on-demand, and then shutting down.&lt;/p&gt;
&lt;p&gt;While the work is being performed, we can take a look at the metrics URL
printed out on the screen to find out how the work is being distributed.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;To see the cluster&amp;rsquo;s real-time metrics, please visit
&lt;code&gt;http://ec2-54-200-198-6.us-west-2.compute.amazonaws.com:4000&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src="https://www.pulumi.com/blog/data-science-on-demand-spinning-up-a-wallaroo-cluster-is-easy-with-pulumi/wallaroo-2.png" alt="wallaroo-2"&gt;&lt;/p&gt;
&lt;p&gt;In the screenshot above, you can see that
the &lt;code&gt;Initializer&lt;/code&gt; and &lt;code&gt;B03a909b23&lt;/code&gt;nodes are processing about 4k messages
per second each, and all the other workers have the classification work
split evenly among them. Don&amp;rsquo;t be surprised that two of the workers are
processing orders of magnitude more messages! Remember our
&lt;a href="https://github.com/WallarooLabs/wallaroo_blog_examples/blob/master/provisioned-classifier/classifier/classifier.py#L11-L21"&gt;pandas application pipeline&lt;/a&gt;?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; ab.new_pipeline(&amp;quot;Classifier&amp;quot;,
wallaroo.TCPSourceConfig(in_host, in_port, decode))
ab.to_stateful(batch_rows, RowBuffer, &amp;quot;CSV rows + global header state&amp;quot;)
ab.to_parallel(classify)
ab.to_sink(wallaroo.TCPSinkConfig(out_host, out_port, encode))
return ab.build()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The CSV rows come in one-by-one, but we batch them and convert them to
dataframes of a hundred, so that our classification algorithm can tackle
more than one row at a time. Worker &lt;code&gt;B03a909b23&lt;/code&gt; just happens to be the
worker where our state named &amp;ldquo;CSV rows + global header state&amp;rdquo; lives.
Let&amp;rsquo;s take a look at its metrics:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.pulumi.com/blog/data-science-on-demand-spinning-up-a-wallaroo-cluster-is-easy-with-pulumi/wallaroo-3.png" alt="wallaroo3"&gt;&lt;/p&gt;
&lt;p&gt;Indeed, we can see that this worker is processing about 4k/sec messages
in the &amp;ldquo;Batch Rows Of Csv, Emit Dataframes&amp;rdquo; step. Every other worker is
busy classifying! Let&amp;rsquo;s see the breakdown for a different, random
worker:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.pulumi.com/blog/data-science-on-demand-spinning-up-a-wallaroo-cluster-is-easy-with-pulumi/wallaroo-4.png" alt="wallaroo-4"&gt;&lt;/p&gt;
&lt;p&gt;Looking good. All that&amp;rsquo;s left for us to do is wait until the job
completes and we receive our zipped data onto our disk.&lt;/p&gt;
&lt;h2 id="the-numbers"&gt;The numbers!&lt;/h2&gt;
&lt;p&gt;As a reminder, let&amp;rsquo;s take a look at the numbers we obtained by running
our classifier on a single &lt;code&gt;c5.4xlarge&lt;/code&gt; instance in AWS:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# SINGLE-MACHINE RUNNING TIMES (NO PROVISIONING)
CSV rows 1 worker 4 workers 8 workers
----------- ---------- ----------- -----------
10 000 39s 20s 11s
100 000 6m28s 3m16s 1m41s
1 000 000 1h03m46s 32m12s 16m33s
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, let&amp;rsquo;s see how much speedup we can achieve from scaling out with our
provisioned-on-demand infrastructure.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# MULTI-MACHINE RUNNING TIMES (PROVISIONING + COMPUTATION)
----------------------------------------------------------
CSV rows 4 machines/ 8 machines/ 16 machines/
28 workers 56 workers 112 workers
------------ -------------- -------------- ---------------
10 000 2m13s 2m36s 2m43s
100 000 3m38s 3m42s 3m48s
1 000 000 7m38s 6m41s 5m56s
10 000 000 40m56s 33m10s 23m24s
30 000 000 &amp;gt; 2h 1h45m 1h12m
----------------------------------------------------------
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Even though there is some constant overhead involved in spinning up the
required infrastructure (clearly too much overhead to justify spinning
up clusters for less than 1 million rows), we&amp;rsquo;re now able to classify a
hefty 10 million rows of CSV data in under half an hour, and 30 million
in a little over an hour. This gives us some perspective on when our
application will need extra resources, or perhaps some performance
optimizations.&lt;/p&gt;
&lt;p&gt;As long as the data fits in the 1 million : 10 million range, it seems
that a cluster of 4 machines represents a sweet-spot between price and
performance &amp;ndash; we can process the data and still fit in the hour-long
window allotted for our batch process, but not have to incur unnecessary
infrastructure costs if we don&amp;rsquo;t need the extra speed.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The above figures illustrate how Wallaroo can be used as an ad-hoc
compute cloud, using Pulumi and Ansible to provision, run workloads
remotely, and shut down the infrastructure once the results are in.&lt;/p&gt;
&lt;p&gt;In the case or our batch job, we can leverage this pattern to scale
horizontally on-demand, even when the incoming workloads exceed the
capacity of one physical machine &amp;ndash; all while running the exact same
Wallaroo application that we run locally as part of our regular
development. Wallaroo handles the scale-aware layer of our program, so
we can focus on the business logic and flow of our data.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re hitting limits when running your hourly, daily or nightly
batch jobs and are looking into scaling out horizontally, don&amp;rsquo;t hesitate
to reach out or drop in to our IRC channel. We&amp;rsquo;d love to chat!&lt;/p&gt;</description><author>Marc Holmes</author><author>Simon Zelazny</author><category>guest-post</category><category>data-science</category></item></channel></rss>