<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0"><channel><title>Pulumi Blog: Robbie McKinstry</title><link>https://www.pulumi.com/blog/author/robbie-mckinstry/</link><description>Pulumi blog posts: Robbie McKinstry.</description><language>en-us</language><pubDate>Thu, 28 Sep 2023 00:00:00 +0000</pubDate><item><title>Benchmarking Python Performance</title><link>https://www.pulumi.com/blog/benchmarking-python-performance/</link><pubDate>Thu, 28 Sep 2023 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/benchmarking-python-performance/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/benchmarking-python-performance/index.png" /&gt;
&lt;p&gt;This is the second post in a series about performance optimizations we&amp;rsquo;ve made
to the Pulumi CLI and SDKs. In this post, we&amp;rsquo;ll go deep on a performance
improvement we made for Pulumi Python programs. You can read more
about Amazing Performance in
&lt;a href="https://www.pulumi.com/blog/amazing-performance/"&gt;the first post in the series&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Late last year, we took a hard look at the performance of Python programs when we
realized they weren&amp;rsquo;t performing up to our expectations. We uncovered a major
bug limiting Python performance, and we ran a number of rigorous experiments
to evaluate just how performant Pulumi Python programs are after the bug had
been repaired. The results indicate Pulumi Python programs are significantly
faster than they were, and now Pulumi Python has reached performance parity
with Pulumi Node.js!&lt;/p&gt;
&lt;h2 id="the-bug"&gt;The Bug&lt;/h2&gt;
&lt;p&gt;When you execute a Pulumi program, Pulumi internally builds a dependency graph
between the resources in your program. In every Pulumi program, some resources
have all their input arguments available at the time of their construction.
In contrast, other resources may depend on &lt;code&gt;Outputs&lt;/code&gt; from other resources.&lt;/p&gt;
&lt;p&gt;For example, consider a sample program where we create two AWS S3 buckets, where
one bucket is used to store logs for the other bucket:&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;pulumi&lt;/span&gt;
&lt;/span&gt;&lt;/span&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;pulumi_aws&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;aws&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;log_bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aws&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;logBucket&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;acl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;log-delivery-write&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&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;aws&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;bucket&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;acl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;private&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;loggings&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;aws&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;BucketLoggingArgs&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;target_bucket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;log_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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;target_prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;log/&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Because &lt;code&gt;bucket&lt;/code&gt; takes an &lt;code&gt;Output&lt;/code&gt; from &lt;code&gt;log_bucket&lt;/code&gt; as an input,
we can&amp;rsquo;t create the &lt;code&gt;bucket&lt;/code&gt; until after the &lt;code&gt;log_bucket&lt;/code&gt;
is created. We have to create the &lt;code&gt;log_bucket&lt;/code&gt; first to compute its ID,
which we can pass to &lt;code&gt;bucket&lt;/code&gt;. This idea extends inductively for
arbitrary programs – before any resource can be run, we must resolve the
&lt;code&gt;Outputs&lt;/code&gt; of all of its arguments. To do this, Pulumi builds a dependency graph
between all resources in your program. Then, it walks the graph topologically
to schedule provisioning operations.&lt;/p&gt;
&lt;p&gt;Provisioning operations that are not dependent on each other can be executed
in parallel, and Pulumi defaults to unbounded parallelism, but users can
ratchet this down if they so desire. Consider this embarrassingly parallel
Python program:&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;pulumi&lt;/span&gt;
&lt;/span&gt;&lt;/span&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;pulumi_aws&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;aws&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;# SQS&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="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&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;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pulumi-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rjust&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sqs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# SNS&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="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&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;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pulumi-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rjust&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;aws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sns&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Topic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&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;In this program, we can create 200 resources in parallel because none of them
take inputs from other resources. This program should be entirely
network-bound because Pulumi can issue all 200 API calls in parallel and wait
for AWS to provision the instances.
&lt;a href="https://github.com/pulumi/pulumi/issues/11116"&gt;We discovered&lt;/a&gt;, however,
that it did not! Strangely, API calls were issued in an initial batch of 20;
as one completed, another would start.&lt;/p&gt;
&lt;h2 id="the-fix"&gt;The Fix&lt;/h2&gt;
&lt;p&gt;The culprit was the Python default future executor,
&lt;a href="https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.ThreadPoolExecutor"&gt;ThreadPoolExecutor&lt;/a&gt;.
We observed that benchmark was run on a four-core computer, and in Python 3.5
to Python 3.7, the number of max workers is five times the number of cores, or 20
(in Python 3.8, this number was changed to &lt;code&gt;min(32, os.cpu_count() + 4)&lt;/code&gt;). We
realized we shouldn&amp;rsquo;t be using the default &lt;code&gt;ThreadExecutor&lt;/code&gt;, and instead we
should provide a &lt;code&gt;ThreadExecutor&lt;/code&gt; with an adjusted number of &lt;code&gt;max_workers&lt;/code&gt;
based on the configured parallelism value. That way, when users run
&lt;code&gt;pulumi up --parallel&lt;/code&gt;, which issues an upper bound on parallel resource
construction, the &lt;code&gt;ThreadExecutor&lt;/code&gt; will respect that bound. We
&lt;a href="https://github.com/pulumi/pulumi/pull/11122"&gt;merged a fix&lt;/a&gt;
that would plumb the value of &lt;code&gt;--parallel&lt;/code&gt; through to a custom &lt;code&gt;ThreadExecutor&lt;/code&gt;
and measured the impact this change had on the performance of our benchmark.&lt;/p&gt;
&lt;h2 id="experimental-setup"&gt;Experimental Setup&lt;/h2&gt;
&lt;p&gt;We designed and implemented two independent experiments to evaluate this change.
The first experiment measures how well the patched Python runtime stacks up
against the control group, Pulumi Python without the patch. The second experiment
compares Pulumi Python to Pulumi TypeScript using the same benchmark ported
to TypeScript. We used the awesome benchmarking tool
&lt;a href="https://github.com/sharkdp/hyperfine"&gt;hyperfine&lt;/a&gt; to record wall clock time as
our indicator of performance.&lt;/p&gt;
&lt;p&gt;The experiments ran overnight on a 2021 MacBook Pro with 32GB RAM, the M1 chip,
and 10 cores. Experimental code is
&lt;a href="https://github.com/pulumi/python-concurrency-experiments/tags"&gt;available on GitHub&lt;/a&gt;,
and release tags pin the version of the code used for each experiment.
We also made an effort to run the experiments on a quiet machine connected
to power. For all experiment groups, &lt;code&gt;--parallel&lt;/code&gt; was unset, translating to
unbounded parallelism.&lt;/p&gt;
&lt;p&gt;Before between samples, we ran &lt;code&gt;pulumi destroy –yes&lt;/code&gt; to ensure a fresh
environment. Hyperfine measures shell startup time and subtracts the value
before final measurements are recorded to more precisely represent the true
cost of execution. All groups collected 20 samples each. We also discard
&lt;code&gt;stderr&lt;/code&gt; and &lt;code&gt;stdout&lt;/code&gt; to reduce noise associated with logging to a tty, but
we do record the status code of each command so can show they executed successfully.&lt;/p&gt;
&lt;h2 id="python-pre--and-post-patch"&gt;Python: Pre- and Post-patch&lt;/h2&gt;
&lt;p&gt;This experiment compares the performance of Pulumi Python before and after
the patch was applied. The control group used Pulumi v3.43.1, while the
experimental group used Pulumi v3.44.3. The primary difference between these
two groups is that a fix was introduced for a Python runtime concurrency bug
as part of v3.44.0. Both groups use the same benchmark program, which created
100 AWS SNS and 100 AWS SQS resources in parallel, as described earlier. Only
the version of the Pulumi CLI is different between groups.&lt;/p&gt;
&lt;h3 id="control-vs-fix"&gt;&lt;a href="https://app.warp.dev/block/rk7fFf2jn2iKXYcIXwhZ8F"&gt;Control vs. Fix&lt;/a&gt;&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Group&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Mean&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Standard Deviation&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Control&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;222.232 s&lt;/td&gt;
&lt;td&gt;0.908 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Experimental&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;70.189 s&lt;/td&gt;
&lt;td&gt;1.497 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Summary:&lt;/strong&gt; The &lt;strong&gt;Experimental Group&lt;/strong&gt; ran 3.17 ± 0.07 times faster than the &lt;strong&gt;Control Group&lt;/strong&gt;, accounting for a +300% speedup in performance. Running Welch T-Test indicated statical significance (p = 2.93e-59, α=0.05).&lt;/p&gt;
&lt;h2 id="python-vs-typescript"&gt;Python vs. TypeScript&lt;/h2&gt;
&lt;p&gt;After seeing very promising results from the first experiment, we wanted to
determine just how promising these results were. We decided to compare Pulumi
Python to Pulumi TypeScript to see if this fix had narrowed the gap in
performance between the two runtimes. We ported the Python program to TypeScript:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;pulumi&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;@pulumi/pulumi&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="kr"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;@pulumi/aws&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// SQS
&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="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sb"&gt;`pulumi-&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&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;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;sqs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Queue&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="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;// SQS
&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="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sb"&gt;`pulumi-&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&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;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;sqs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Queue&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="p"&gt;})];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For this experiment, we fixed the version of the CLI to v3.44.3, which included
the patch to the Python runtime. Here are the result.&lt;/p&gt;
&lt;h3 id="typescript-vs-python"&gt;&lt;a href="https://app.warp.dev/block/rk7fFf2jn2iKXYcIXwhZ8F"&gt;TypeScript vs. Python&lt;/a&gt;&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Group&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Mean&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Standard Deviation&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Python&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;70.975 s&lt;/td&gt;
&lt;td&gt;0.909 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TypeScript&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;73.741 s&lt;/td&gt;
&lt;td&gt;1.574 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Summary:&lt;/strong&gt; The &lt;strong&gt;Python Group&lt;/strong&gt; performed the best and ran 1.04 ± 0.03 times
faster than the &lt;strong&gt;TypeScript Group&lt;/strong&gt;. This accounts for a 4% difference in
performance. A second T-Test indicated statical significance
(p = 1.4e-07, α=0.05). Not only did Python close the gap with TypeScript,
but it is also now marginally faster than its Node.js competitor.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s rare to have a small PR result in such a massive performance increase,
but when it happens, we want to shout it from the rooftops. This change, which
shipped last year in v3.44.3, does not require Python users to opt-in; their programs
are now faster. This patch has closed the gap with the Node.js runtime.
Users can now expect highly parallel Pulumi programs to run in a similar
amount of time between either language.&lt;/p&gt;
&lt;h2 id="artifacts"&gt;Artifacts&lt;/h2&gt;
&lt;p&gt;You can check out the artifacts of the experiments
&lt;a href="https://github.com/pulumi/python-concurrency-experiments/tags"&gt;on GitHub&lt;/a&gt;,
including the source code.&lt;/p&gt;
&lt;p&gt;Here are some useful links:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/pulumi/python-concurrency-experiments/tags"&gt;The GitHub repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pulumi/python-concurrency-experiments/releases/tag/parallelism"&gt;Artifacts from the first experiment&lt;/a&gt;, &amp;ldquo;Control vs. Fix&amp;rdquo; or &amp;ldquo;Pre- and Post-patch&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://app.warp.dev/block/F6KkbWHvDVWLwtYFKq08Q2"&gt;More statistics&lt;/a&gt; about the first experiment.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pulumi/python-concurrency-experiments/releases/tag/TypeScript-vs-Python"&gt;Artifacts from the second experiment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://app.warp.dev/block/gspCIKn10y9bEvZDMWHe4Q"&gt;More statistics&lt;/a&gt; about the second experiment.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/docs/intro/concepts/how-pulumi-works/"&gt;Pulumi Internals&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>Justin Van Patten</author><author>Robbie McKinstry</author><category>performance</category><category>platform</category><category>engineering</category></item><item><title>Manage Pulumi Teams in Bulk with the New CLI Teams Flag</title><link>https://www.pulumi.com/blog/stack-init-teams-flag/</link><pubDate>Fri, 31 Mar 2023 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/stack-init-teams-flag/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/stack-init-teams-flag/index.png" /&gt;
&lt;p&gt;We&amp;rsquo;ve been hearing feedback from our customers that they need ways to manage permissions for their stacks at scale. Today we are announcing a &lt;code&gt;--teams&lt;/code&gt; flag for &lt;a href="https://www.pulumi.com/docs/iac/cli/commands/pulumi_stack_init"&gt;&lt;code&gt;pulumi stack init&lt;/code&gt;&lt;/a&gt;, which allows customers to assign Teams to stacks from the CLI. This flag offers a third programmatic method for assigning permissions, supplementing &lt;a href="https://www.pulumi.com/docs/reference/service-rest-api"&gt;Pulumi Service REST API&lt;/a&gt; or the &lt;a href="https://www.pulumi.com/registry/packages/pulumiservice"&gt;Pulumi Service Provider&lt;/a&gt;. Developers can now initialize their stacks with the right permissions directly from the CLI.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s some background for any Pulumi newcomers. Pulumi projects
consist of stacks, which are separate deployments of the same infrastructure. Stacks may
also be configured with separate inputs. Organization admins can leverage RBAC
to grant access permissions at the organization or Pulumi Teams levels. Pulumi Teams and RBAC are available for Enterprise and Business Critical customers.&lt;/p&gt;
&lt;p&gt;Stacks are often created via the CLI with the &lt;a href="https://www.pulumi.com/docs/iac/cli/commands/pulumi_stack_init/#options"&gt;&lt;code&gt;pulumi stack init&lt;/code&gt; command&lt;/a&gt;.
This command initializes a new stack. If you&amp;rsquo;re using the
&lt;a href="https://www.pulumi.com/docs/iac/concepts/state-and-backends/#pulumi-service-backend"&gt;Pulumi Service&lt;/a&gt; as your backend, you can view your newly created stack in the Pulumi Service console. If your organization uses teams, you will want to give your teammates access to the stack.&lt;/p&gt;
&lt;p&gt;With the release of Pulumi v3.59.0, developers can assign Pulumi Team access during stack creation. To do this, pass in the &lt;code&gt;--teams&lt;/code&gt; flag followed by the team&amp;rsquo;s name. For instance:&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;pulumi stack init --teams Red
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This saves time by eliminating context switching, allowing users to stay on task. Previously, the Service console was the most direct way to assign team access to a stack, meaning a trip to the browser before your teammates could access the stack. The &lt;code&gt;--teams&lt;/code&gt; flag improves upon the process of managing stack permissions by removing this step.&lt;/p&gt;
&lt;p&gt;Use the flag multiple times to assign access to multiple Pulumi Teams, as in:&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;pulumi stack init --teams Red --teams Blue
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Currently, the feature always grants &lt;code&gt;read&lt;/code&gt; and &lt;code&gt;write&lt;/code&gt; access, the most commonly assigned permissions.&lt;/p&gt;
&lt;p&gt;We implemented this feature as a direct result of feedback from daily users.
Context switching can be expensive when you&amp;rsquo;re creating enough stacks.
At Pulumi, we enable our users to squeak out as much productivity from our tooling
as possible. We&amp;rsquo;re always happy to make ergonomic improvements. We look
forward to continuing to raise the bar for cloud productivity.&lt;/p&gt;</description><author>Robbie McKinstry</author><category>cli</category><category>stack-init</category><category>cli-flags</category></item><item><title>Achieving Amazing Performance in the Pulumi CLI</title><link>https://www.pulumi.com/blog/amazing-performance/</link><pubDate>Thu, 26 Jan 2023 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/amazing-performance/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/amazing-performance/index.png" /&gt;
&lt;p&gt;This is the first post in a series about performance optimizations we&amp;rsquo;ve made to the Pulumi CLI. Over the last six months at Pulumi, the Platform Team has been working on a project we call &amp;ldquo;Amazing Performance.&amp;rdquo; Amazing Performance is a new initiative to improve the throughput and latency of the Pulumi CLI not only for power users but for everyone. By the end of June 2022, we assembled a list of issues containing both high-value improvements requiring a sizable investment and low-hanging fruit for quick wins. The full list, including the items we have yet to tackle, is contained in &lt;a href="https://github.com/pulumi/pulumi/issues/11598"&gt;a tracking issue on GitHub&lt;/a&gt;. This blog series will cover the highlights.&lt;/p&gt;
&lt;p&gt;This post has two sections. First, we&amp;rsquo;ll describe the tools we built previously to track performance. Secondly, we&amp;rsquo;ll recap a few quick wins. Future posts will detail our major wins as part of the Amazing Performance initiative.&lt;/p&gt;
&lt;h2 id="measuring-and-tracking-performance"&gt;Measuring and Tracking Performance&lt;/h2&gt;
&lt;p&gt;The Amazing Performance initiative didn&amp;rsquo;t come from a vacuum. We&amp;rsquo;ve been working to understand and improve the performance of the Pulumi CLI for years. Much of the earlier effort has focused on gaining performance &lt;em&gt;insights&lt;/em&gt; – building an understanding of where our performance pains were the sharpest. The two most impactful tools at our disposal for understanding performance pains are our analytics dashboard and our OpenTracing support.&lt;/p&gt;
&lt;h3 id="analytics-dashboard"&gt;Analytics Dashboard&lt;/h3&gt;
&lt;p&gt;We&amp;rsquo;ve built an analytics dashboard and monitoring system to let us know when performance dips. This system has three components: a suite of benchmarks, a data warehouse for analytics, and an alerting system.&lt;/p&gt;
&lt;p&gt;&lt;img src="architecture.svg" alt="Performancing Monitoring Architecture"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Benchmark Suite:&lt;/strong&gt; We&amp;rsquo;ve written a suite of benchmarks measuring different performance characteristics of the CLI. Each benchmark answers a simple question: &amp;ldquo;How long does the CLI take to run when the plan has no changes?&amp;rdquo; or &amp;ldquo;How long does the CLI take to run an empty plan?&amp;rdquo; In addition to these simple questions, we also have a few benchmarks to stress test creating many resources. We&amp;rsquo;ve implemented these benchmarks in Go, JavaScript, TypeScript, C#, and Python. Each night, we exercise the benchmarks, capturing samples. Once execution is complete, our nightly job uploads the results to a serverless function which stores them in our data warehouse.&lt;/p&gt;
&lt;p&gt;&lt;img src="analytics-dashboard.jpg" alt="A snippet of our analytics dashboard"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Warehouse Analytics:&lt;/strong&gt; We use Metabase to query and analyze data in our data warehouse. Pulumi engineers can log into Metabase and view our performance dashboard, which charts the nightly data as a time series, where each data point is the average of that night&amp;rsquo;s samples.&lt;/p&gt;
&lt;p&gt;Plotting the chart as a line graph allows us to identify any performance dips visually. Sometimes the data from our nightly runs are noisy, so it can take a few nights before we can attribute a change in the runtime to a change in the code. In the absence of noise, the line graph allows us to pinpoint the day the regression was introduced so we can leaf through the pull requests merged that day. More often, there&amp;rsquo;s enough noise in the data that we go through a few days of PRs to find the regression.&lt;/p&gt;
&lt;p&gt;Shortly after shipping the performance dashboard internally, we used the initial numbers we observed to establish a service-level objective (SLO).&lt;/p&gt;
&lt;p&gt;Each language has an SLO for each of the benchmarks implemented in that language. Our goal for the SLO is to ensure that we set a &amp;ldquo;do not breach&amp;rdquo; expectation across the Platform Core team. We want everyone on the team to know when performance slips beyond an acceptable level.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Alerting:&lt;/strong&gt; What good is an SLO if it&amp;rsquo;s not observable? Whether the breach comes from a major regression or creeps up over time, any time a benchmark violates an SLO, a Slackbot alerts the Platform Core team that performance has slowed beyond an acceptable level. One objective we had for the Amazing Performance initiative was to ensure there were no alerts by the end of Q3 2022, which we achieved.&lt;/p&gt;
&lt;p&gt;&lt;img src="metabot.jpg" alt="Screenshot of a Slack alert from a Metabase SLO breach"&gt;&lt;/p&gt;
&lt;p&gt;Lastly, we established the accuracy of these benchmarks as part of Amazing Performance. We compared the benchmark results we collected in the nightly job with numbers observed on our local laptops and found they were similar. Consequently, we believe our benchmarks are a good predictor of an actual developer&amp;rsquo;s experience.&lt;/p&gt;
&lt;h3 id="tracing-support"&gt;Tracing Support&lt;/h3&gt;
&lt;p&gt;If you ever encounter a particularly sluggish Pulumi program and want to know what is taking so long, you can use &lt;a href="https://www.pulumi.com/docs/support/troubleshooting/#tracing"&gt;Pulumi&amp;rsquo;s tracing support&lt;/a&gt; to look into the details. When run with the &lt;code&gt;--tracing&lt;/code&gt; flag, Pulumi will capture an &lt;a href="https://opentracing.io/"&gt;OpenTracing&lt;/a&gt; trace of the program&amp;rsquo;s execution, giving you an idea of where time is spent. We regularly trace programs large and small to see how we can shave off time.&lt;/p&gt;
&lt;p&gt;Pulumi can send traces to a Zipkin server, or aggregate them to a local file and let you view them offline. If you provide an HTTP URI to the &lt;code&gt;--tracing&lt;/code&gt; flag, then Pulumi assumes that URI points to a web server that accepts Zipkin-format traces, and it will send all tracing data there. If you provide a file URI instead, Pulumi spins up a local Zipkin server to which child processes send their traces. Once execution is complete, Pulumi aggregates the traces into a single local file and spins down the Zipkin server. You can run &lt;code&gt;PULUMI_DEBUG_COMMANDS=1 pulumi view-trace ./up.trace&lt;/code&gt; to view the trace in an embedded web UI.&lt;/p&gt;
&lt;p&gt;&lt;img src="appdash.jpg" alt="An image of a Pulumi trace"&gt;&lt;/p&gt;
&lt;p&gt;Not all spans are exceptionally well-named, and there are blind spots in our traces. However, traces are typically a reliable way to subdivide the program execution, so hotspots are easily identifiable. In the future, we plan to migrate to OpenTelemetry and improve our trace coverage. (Feel free to get in touch on the &lt;a href="https://slack.pulumi.com/"&gt;Community Slack&lt;/a&gt; if you want to help with this effort!)&lt;/p&gt;
&lt;h2 id="quick-wins-for-performance-gains"&gt;Quick Wins for Performance Gains&lt;/h2&gt;
&lt;p&gt;Some of the work we completed for Amazing Performance targeted simple, isolated changes to make the system a little snappier. The rest of this blog details two quick examples.&lt;/p&gt;
&lt;h3 id="skipping-a-slow-import"&gt;Skipping a Slow Import&lt;/h3&gt;
&lt;p&gt;We cut 300ms in boot time a class of programs by dynamically importing TypeScript.&lt;/p&gt;
&lt;p&gt;When Pulumi evaluates NodeJS programs, it compiles TypeScript into JavaScript using &lt;a href="https://www.npmjs.com/package/ts-node"&gt;TS-Node&lt;/a&gt;. Consequently, Pulumi bundles the TypeScript compiler into its NodeJS runtime so TS-Node can perform the compilation. However, we noticed JavaScript programs were importing the TypeScript compiler even when unused.&lt;/p&gt;
&lt;p&gt;Users typically hit this bug in one of two ways. Some users choose to write Pulumi programs in pure JavaScript. Pulumi was needlessly importing the TypeScript compiler, even when there was nothing to compile.&lt;/p&gt;
&lt;p&gt;The second way a user might encounter this bug is in a more advanced deployment pipeline. One common advanced pattern in CI environments or with &lt;a href="https://www.pulumi.com/docs/using-pulumi/automation-api/"&gt;Automation API&lt;/a&gt; is to typecheck Pulumi programs separately from execution. CI will enforce typechecking during pull requests to guarantee that all code hitting the repository&amp;rsquo;s main branch is valid. Once it&amp;rsquo;s typechecked and merged, the Pulumi program can precompile the TypeScript into JavaScript before execution. Finally, when it comes time to execute the Pulumi program, the deployment pipeline will run the compiled JavaScript, which is known to be well-typed. The motivation for precompiling comes from using your own preprocessor. Some customers want to ditch TS-Node for compilation and prefer to use &lt;a href="https://swc.rs/"&gt;SWC&lt;/a&gt;, &lt;a href="https://esbuild.github.io/"&gt;ESBuild&lt;/a&gt;, or other options for increased performance, or for consistency with the rest of their codebase.&lt;/p&gt;
&lt;p&gt;In both scenarios, importing TypeScript when it&amp;rsquo;s not used results in a 300ms slowdown. In &lt;a href="https://github.com/pulumi/pulumi/pull/10214"&gt;a small PR&lt;/a&gt;, we detect cases that don&amp;rsquo;t require TypeScript and dynamically import it only when needed.&lt;/p&gt;
&lt;h3 id="timely-lease-renewal"&gt;Timely Lease Renewal&lt;/h3&gt;
&lt;p&gt;Another quick win involved the renewal of a new lease, which resulted in another 120ms shaved off of boot time. When the Pulumi CLI kicks off a preview, projects that use the &lt;a href="https://www.pulumi.com/docs/iac/concepts/state-and-backends/#pulumi-service-backend"&gt;Service backend&lt;/a&gt; are authenticated with the Service so the CLI can fetch the current state. During authentication, the CLI exchanges user credentials for a short-lived access token. This access token is valid for a fixed amount of time, called a lease, after which they expire. The CLI can renew a lease by making another request to the Service to extend the duration for which the lease is valid.&lt;/p&gt;
&lt;p&gt;We noticed that the first time the CLI renewed the lease, it blocked further execution until the renewal was complete, introducing an unnecessary slowdown of 120ms. &lt;a href="https://github.com/pulumi/pulumi/pull/10462"&gt;The fix&lt;/a&gt; was to remove this renewal from the critical path by running it in the backend. By starting the HTTP call on a background thread and only blocking if the token is needed but not renewed, we&amp;rsquo;ve eliminated the 120ms slowdown.&lt;/p&gt;
&lt;p&gt;Since this bug affects anyone using the Pulumi Service backend, most users will benefit from this improvement. It&amp;rsquo;s small, &lt;a href="https://link.springer.com/chapter/10.1007/978-3-319-58475-1_4"&gt;but it&amp;rsquo;s noticeable&lt;/a&gt; to most users. Program start-up will feel slightly more snappy.&lt;/p&gt;
&lt;h2 id="stay-tuned"&gt;Stay Tuned!&lt;/h2&gt;
&lt;p&gt;This post covered tools we built before Amazing Performance and a few smaller changes we made as part of the initiative. Future posts will describe some of the larger advances we made to drive Pulumi performance ever forward! Keep an eye out for our next post in the series!&lt;/p&gt;</description><author>Robbie McKinstry</author><category>performance</category><category>platform</category><category>engineering</category></item></channel></rss>