<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0"><channel><title>Pulumi Blog: Performance</title><link>https://www.pulumi.com/blog/tag/performance/</link><description>Pulumi blog posts: Performance.</description><language>en-us</language><pubDate>Thu, 05 Mar 2026 00:00:00 +0000</pubDate><item><title>Now GA: Up to 20x Faster Pulumi Operations for Everyone</title><link>https://www.pulumi.com/blog/journaling-ga/</link><pubDate>Thu, 05 Mar 2026 00:00:00 +0000</pubDate><guid>https://www.pulumi.com/blog/journaling-ga/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/journaling-ga/index.png" /&gt;
&lt;p&gt;In January, we &lt;a href="https://www.pulumi.com/blog/journaling/"&gt;introduced a major performance enhancement for Pulumi Cloud&lt;/a&gt; through a fundamental change to how Pulumi manages state that speeds up operations by up to 20x. After a staged rollout across many organizations, &lt;strong&gt;it is now enabled by default for every Pulumi Cloud operation&lt;/strong&gt;. No opt-in required—just use Pulumi CLI v3.225.0+ with Pulumi Cloud. The improvement applies to &lt;code&gt;pulumi up&lt;/code&gt;, &lt;code&gt;pulumi destroy&lt;/code&gt;, and &lt;code&gt;pulumi refresh&lt;/code&gt;; &lt;code&gt;pulumi preview&lt;/code&gt; does not modify state, so it is unchanged.&lt;/p&gt;
&lt;h2 id="what-this-means-for-you"&gt;What this means for you&lt;/h2&gt;
&lt;p&gt;First and foremost, nothing about how you work with &lt;code&gt;pulumi&lt;/code&gt; needs to change. Your updates now benefit from better parallelism and should thus complete faster. Before this change, &lt;code&gt;pulumi&lt;/code&gt; always saved a full snapshot to the cloud, so the current state could always be recovered if something goes wrong. With journaling, we now only send the state of each operation, which allows us to send these updates in parallel, as long as resources are not related to each other. For the full deep dive, see the blog post linked above.&lt;/p&gt;
&lt;h2 id="production-results"&gt;Production results&lt;/h2&gt;
&lt;p&gt;Since January, we&amp;rsquo;ve had many early adopters of journaling. This helped us shake out one final bug on the server side, and journaling has been stable since then. With that we feel confident in rolling this out to all our users.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ve also gathered some real-world data on how journaling is performing. The data from the preview period shows some significant improvements for update times. For stacks with fewer than 100 resources, the median improvement is 25.3%, while the p90 improvement is 75.2%, and we&amp;rsquo;ve seen a p99 improvement of up to 92.6% Meanwhile, for larger stacks, the median improvement is 60.2%. We need more data for stacks with more than 100 resources, we will update this blog once that comes in.&lt;/p&gt;
&lt;p&gt;This data already shows the expected significant improvement in update times, especially for larger stacks, though the improvements strongly depend on the shape and type of resources that are being set up. Stacks with many resources, that are quick to update benefit more than smaller stacks with slower to set up resources. For more numbers see also the &lt;a href="https://www.pulumi.com/blog/journaling/#benchmarks"&gt;Benchmarks section in the previous blog post&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="what-you-need-to-do"&gt;What you need to do&lt;/h2&gt;
&lt;p&gt;While this was an opt-in process using the &lt;code&gt;PULUMI_ENABLE_JOURNALING&lt;/code&gt; environment variable, this opt-in is no longer required. Just upgrade your Pulumi CLI to v3.225.0+ and use the Pulumi Cloud backend, and journaling will automatically speed up your updates.&lt;/p&gt;
&lt;p&gt;If you encounter any issues, reach out on the &lt;a href="https://slack.pulumi.com/"&gt;Pulumi Community Slack&lt;/a&gt; or through &lt;a href="https://support.pulumi.com/hc/en-us"&gt;Pulumi Support&lt;/a&gt;. You can also set the &lt;code&gt;PULUMI_DISABLE_JOURNALING=true&lt;/code&gt; env variable to opt out of journaling.&lt;/p&gt;</description><author>Thomas Gummerer</author><author>Pat Gavlin</author><category>performance</category><category>pulumi-cloud</category><category>features</category><category>releases</category></item><item><title>Speeding up Pulumi Operations by up to 20x</title><link>https://www.pulumi.com/blog/journaling/</link><pubDate>Mon, 12 Jan 2026 18:57:55 +0200</pubDate><guid>https://www.pulumi.com/blog/journaling/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/journaling/index.png" /&gt;
&lt;p&gt;Today we&amp;rsquo;re introducing an improvement that can speed up operations by up to 20x. At every operation, and at every step within an operation, &lt;code&gt;pulumi&lt;/code&gt; saves a snapshot of your cloud infrastructure. This gives &lt;code&gt;pulumi&lt;/code&gt; a current view of state even if something fails mid-operation, but it comes with a performance penalty for large stacks. Here&amp;rsquo;s how we fixed it.&lt;/p&gt;
&lt;h2 id="benchmarks"&gt;Benchmarks&lt;/h2&gt;
&lt;p&gt;Before getting into the more technical details, here are a number of benchmarks demonstrating what this new experience looks like. To run the benchmarks we picked a couple of Pulumi projects: one that can be set up massively parallel, which is the worst case scenario for the old snapshot system, and another that looks a little more like a real world example. Note that we conducted all of these benchmarks in Europe, connecting to Pulumi Cloud, which runs in AWS&amp;rsquo;s &lt;code&gt;us-west-2&lt;/code&gt; region, so exact numbers may vary based on your location and internet connection. This should however give a good indication of the performance improvements.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;re benchmarking two somewhat large stacks, both of which are or were used at Pulumi. The first program sets up a website using AWS bucket objects. We&amp;rsquo;re using the &lt;a href="https://github.com/pulumi/examples/tree/master/aws-ts-static-website"&gt;aws-ts-static-website&lt;/a&gt; example here with a small subset of the fraction from our docs site. This means we&amp;rsquo;re setting up more than 3000 bucket objects, with 3222 resources in total.&lt;/p&gt;
&lt;p&gt;The benchmarks were measured using the &lt;code&gt;time&lt;/code&gt; built-in command and using the best time in a best-of-three benchmark. The network traffic was measured using &lt;code&gt;tcpdump&lt;/code&gt;, limiting the measured traffic to only the IP addresses for Pulumi Cloud. Finally, &lt;code&gt;tshark&lt;/code&gt; was used to process the packet captures and count the bytes sent.&lt;/p&gt;
&lt;p&gt;All the benchmarks are run with journaling off (the default experience) and with journaling on (the new experience). To begin with, let&amp;rsquo;s look at the results when creating our stack from scratch:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Bytes sent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Without journaling&lt;/td&gt;
&lt;td&gt;58m26s&lt;/td&gt;
&lt;td&gt;16.5MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;With journaling&lt;/td&gt;
&lt;td&gt;02m50s&lt;/td&gt;
&lt;td&gt;2.3MB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Now let&amp;rsquo;s have a look at what this looks like if we only change half the resources, but the remaining ones remain unchanged:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Bytes sent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Without journaling&lt;/td&gt;
&lt;td&gt;34m49s&lt;/td&gt;
&lt;td&gt;13.8MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;With journaling&lt;/td&gt;
&lt;td&gt;01m45s&lt;/td&gt;
&lt;td&gt;2.3MB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The second example is setting up an instance of the Pulumi app and API. Here we&amp;rsquo;ll have an example that&amp;rsquo;s a bit more dominated by the cost of setting up the actual infrastructure in the cloud, but we still have a very noticeable improvement in the time it takes to set up the stack.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Bytes sent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Without journaling&lt;/td&gt;
&lt;td&gt;17m52s&lt;/td&gt;
&lt;td&gt;18.5MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;With journaling&lt;/td&gt;
&lt;td&gt;9m12s&lt;/td&gt;
&lt;td&gt;5.9MB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;img src="time.png" alt="Comparison chart of the timings shown in the tables above"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="size.png" alt="Comparison chart of the bytes sent shown in the tables above"&gt;&lt;/p&gt;
&lt;div class="note note-tip"&gt;
&lt;div class="icon-and-line"&gt;
&lt;svg xmlns="http://www.w3.org/2000/svg" class="ph-icon ph-icon--fill" fill="currentColor" aria-hidden="true" focusable="false"&gt;&lt;use href="https://www.pulumi.com/icons/sprite.70121449e0dde6f8c01ff68423fffaa0336ecc73c7bbc87506404126694ca58c.svg#p-lightbulb-fill"/&gt;&lt;/svg&gt;
&lt;div class="line"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="content"&gt;To use this feature, you need a &lt;code&gt;pulumi&lt;/code&gt; version newer than v3.211.0, and set the &lt;code&gt;PULUMI_ENABLE_JOURNALING&lt;/code&gt; environment variable to &lt;code&gt;true&lt;/code&gt;.&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;If you are interested in the more technical details read on!&lt;/p&gt;
&lt;h2 id="introduction-to-snapshotting"&gt;Introduction to snapshotting&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;pulumi&lt;/code&gt; keeps track of all resources in a stack in a snapshot. This snapshot is stored in the stack&amp;rsquo;s configured backend, which is either the Pulumi Cloud or a DIY backend. Future operations on the stack then use this snapshot to figure out which resources need to be created, updated or deleted.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pulumi&lt;/code&gt; creates a new snapshot at the beginning and at the end of each resource operation to minimize the possibility of untracked changes even if a deployment is aborted unexpectedly (for example due to network issues, power outages, or bugs).&lt;/p&gt;
&lt;p&gt;At the beginning of the operation, &lt;code&gt;pulumi&lt;/code&gt; adds a new &amp;ldquo;pending operation&amp;rdquo; to the snapshot. Pending operations declare the intent to mutate a resource. If a pending operation is left in the snapshot (in other words the operation started, but &lt;code&gt;pulumi&lt;/code&gt; couldn&amp;rsquo;t record the end of it), the next operation will try to resolve this. If we have an ID for the resource already, for example on partial updates/deletes, &lt;code&gt;pulumi&lt;/code&gt; will try to read the resource state from the cloud and resolve it internally. If there is no ID yet, &lt;code&gt;pulumi&lt;/code&gt; will ask the user to check the actual state of the resource. Depending on the user&amp;rsquo;s response, &lt;code&gt;pulumi&lt;/code&gt; will either remove the operation from the snapshot or import the resource. This is because it is possible that the resource has been set up correctly or that the resource creation failed. If &lt;code&gt;pulumi&lt;/code&gt; aborted midway through the operation, it&amp;rsquo;s impossible to know which state the resource is in.&lt;/p&gt;
&lt;p&gt;Once an operation finishes, the pending operation is removed and the resource&amp;rsquo;s final state is recorded in the snapshot.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s also some additional metadata that is stored in the snapshot that is only updated infrequently.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s how the &lt;a href="https://github.com/pulumi/pulumi/blob/76588836f542c95e8f43ed785cc1828c40369ada/pkg/resource/deploy/snapshot.go#L34"&gt;snapshot looks in code&lt;/a&gt;. This snapshot is serialized and sent to the backend. &lt;code&gt;Resources&lt;/code&gt; holds the list of known resource states and is updated after each operation finishes, and &lt;code&gt;PendingOperations&lt;/code&gt; is the list of pending operations described above.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Snapshot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Manifest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Manifest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// a deployment manifest of versions, checksums, and so on.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SecretsManager&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Manager&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// the secrets manager to use when serializing this snapshot.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Resources&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;State&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// all resources and their associated states.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PendingOperations&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Operation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// all currently pending resource operations.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Metadata&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SnapshotMetadata&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// metadata associated with the snapshot.&lt;/span&gt;&lt;span class="w"&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="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Before we dive in deeper, we also need to understand a little bit about how the &lt;code&gt;pulumi&lt;/code&gt; engine works internally. Whenever a &lt;code&gt;pulumi&lt;/code&gt; operation is run (e.g. &lt;code&gt;pulumi up&lt;/code&gt;, &lt;code&gt;pulumi destroy&lt;/code&gt;, &lt;code&gt;pulumi refresh&lt;/code&gt; etc.), the engine internally generates and executes a series of steps, to create, update, delete etc. resources. To maintain correct relationships between resources, the steps need to be executed in a partial order such that no step is executed until all of the steps it depends on have executed successfully. Steps may otherwise execute concurrently.&lt;/p&gt;
&lt;p&gt;As each step is responsible for updating a single resource, we can generate a snapshot of the state before each step starts, and after it completes. Before each step starts, we create a pending operation, and add it to the &lt;code&gt;PendingOperations&lt;/code&gt; list. After that step completes, we remove the pending operation from that list, and update the &lt;code&gt;Resources&lt;/code&gt; list, either adding a resource, removing it, or updating it, depending on the kind of operation we just executed.&lt;/p&gt;
&lt;p&gt;After this introduction, we can dive into what&amp;rsquo;s slow, how we fixed it, and some benchmarks.&lt;/p&gt;
&lt;h2 id="why-is-it-slow"&gt;Why is it slow?&lt;/h2&gt;
&lt;p&gt;To make sure the state is always as up-to-date as possible, even if there are any network hiccups/power outages etc., a step won&amp;rsquo;t start until the snapshot that includes the pending operation is confirmed to be stored in the backend. Similarly an operation won&amp;rsquo;t be considered finished until the snapshot with an updated resources list is confirmed to be stored in the backend.&lt;/p&gt;
&lt;p&gt;To send the current state to the backend, we simply serialize it as a JSON file, and send it to the backend. However, as mentioned above, steps can be executed in parallel. If we uploaded the snapshot at the beginning and end of every step with no serialization, there would be a risk that we overwrite a new snapshot with an older one, leading to incorrect data.&lt;/p&gt;
&lt;p&gt;Our workaround for that is to serialize the snapshot uploads, uploading one snapshot at a time. This gives us the data integrity properties we want, however it can slow step execution down, especially on internet connections with lower bandwidth, and/or high latency.&lt;/p&gt;
&lt;p&gt;This impacts performance especially for large stacks, as we upload the whole snapshot every time, which can take some time if the snapshot is getting big. For the Pulumi Cloud backend we improved on this a little &lt;a href="https://github.com/pulumi/pulumi/pull/10788"&gt;at the end of 2022&lt;/a&gt;. We implemented a diff based protocol, which is especially helpful for large snapshots, as we only need to send the diff between the old and the new snapshot, and Pulumi Cloud can then reconstruct the full snapshot based on that. This reduces the amount of data that needs to be transferred, thus improving performance.&lt;/p&gt;
&lt;p&gt;However, the snapshotting is still a major bottleneck for large &lt;code&gt;pulumi&lt;/code&gt; operations. Having to serially upload the snapshot twice for each step does still have a big impact on performance, especially if many resources are modified in parallel. Furthermore, the time spent performing textual diffs between snapshots scales in proportion to the size of the data being processed, which adds additional execution time to each operation.&lt;/p&gt;
&lt;h2 id="fast-but-lacking-data-integrity"&gt;Fast, but lacking data integrity?&lt;/h2&gt;
&lt;p&gt;As long as &lt;code&gt;pulumi&lt;/code&gt; can complete its operation, there&amp;rsquo;s no need for the intermediate checkpoints. We could allow &lt;code&gt;pulumi&lt;/code&gt; operations to skip uploading the intermittent checkpoints to the backend. This, of course, avoids the single serialization point we have sending the snapshots to the backend, and thus makes the operation much more performant.&lt;/p&gt;
&lt;p&gt;However, it also has the serious disadvantage of compromising some of the data integrity guarantees &lt;code&gt;pulumi&lt;/code&gt; gives you. If anything goes wrong during the update, &lt;code&gt;pulumi&lt;/code&gt; has no notion of what happened until then, potentially leaving orphaned resources in the provider, or leaving resources in the state that no longer exist.&lt;/p&gt;
&lt;p&gt;Neither of these solutions is very satisfying, as the tradeoff is either performance or data integrity. We would like to have our cake and eat it too, and that&amp;rsquo;s exactly what we&amp;rsquo;re doing with journaling.&lt;/p&gt;
&lt;h2 id="enter-journaling"&gt;Enter journaling&lt;/h2&gt;
&lt;p&gt;To achieve this, we went back to the drawing board, and asked ourselves, &amp;ldquo;What would a solution look like that&amp;rsquo;s both performant &lt;em&gt;and&lt;/em&gt; preserves data integrity throughout the update?&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Making that happen is possible because of three facts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We always start with the same snapshot on the backend and the CLI.&lt;/li&gt;
&lt;li&gt;Every step the engine executes affects only one resource.&lt;/li&gt;
&lt;li&gt;We have a service that can reconstruct a snapshot from what is given to it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;(The third point here already hints at it, but this feature is only available and made possible by Pulumi Cloud, not on the DIY backend).&lt;/p&gt;
&lt;p&gt;What if instead of sending the whole snapshot, or a diff of the snapshot, we could send the individual changes to the base snapshot to the service, which could then apply it, and reconstruct a full snapshot from it? This is exactly what we are doing here, in the form of what we call journal entries. Each journal entry has the following form:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;JournalEntryKindBegin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;JournalEntryKind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;JournalEntryKindSuccess&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;JournalEntryKind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;JournalEntryKindFailure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;JournalEntryKind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;JournalEntryKindRefreshSuccess&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;JournalEntryKind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;JournalEntryKindOutputs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;JournalEntryKind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;JournalEntryKindWrite&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;JournalEntryKind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;JournalEntryKindSecretsManager&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;JournalEntryKind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;JournalEntryKindRebuiltBaseState&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;JournalEntryKind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="w"&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="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&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;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;JournalEntry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Version of the journal entry format.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;version&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Kind of journal entry.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Kind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;JournalEntryKind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;kind&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Sequence ID of the operation.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SequenceID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;sequenceID&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// ID of the operation this journal entry is associated with.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;OperationID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;operationID&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// ID for the delete Operation that this journal entry is associated with.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RemoveOld&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;removeOld&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// ID for the delete Operation that this journal entry is associated with.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RemoveNew&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;removeNew&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// PendingReplacementOld is the index of the resource that&amp;#39;s to be marked as pending replacement&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PendingReplacementOld&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;pendingReplacementOld,omitempty&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// PendingReplacementNew is the operation ID of the new resource to be marked as pending replacement&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PendingReplacementNew&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;pendingReplacementNew,omitempty&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// DeleteOld is the index of the resource that&amp;#39;s to be marked as deleted.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DeleteOld&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;deleteOld,omitempty&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// DeleteNew is the operation ID of the new resource to be marked as deleted.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DeleteNew&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;deleteNew,omitempty&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// The resource state associated with this journal entry.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;State&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;ResourceV3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;state,omitempty&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// The operation associated with this journal entry, if any.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Operation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;OperationV2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;operation,omitempty&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// If true, this journal entry is part of a refresh operation.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RebuildDependencies&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;isRefresh,omitempty&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// The secrets manager associated with this journal entry, if any.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SecretsProvider&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;SecretsProvidersV1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;secretsProvider,omitempty&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// NewSnapshot is the new snapshot that this journal entry is associated with.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;NewSnapshot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;DeploymentV3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;newSnapshot,omitempty&amp;#34;`&lt;/span&gt;&lt;span class="w"&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="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;These journal entries encode all the information needed to reconstruct the snapshot from them. Each journal entry can be sent in parallel from the engine, and the snapshot will still be fully valid. All journal entries have a Sequence ID attached to them, and they need to be replayed in that order on the service side to make sure we get a valid snapshot. It is however okay to replay without journal entries that have not yet been received by the service, and whose sequence ID is thus missing. This is safe because the engine only sends entries in parallel for resources whose parents/dependencies have been fully created and confirmed by the service.&lt;/p&gt;
&lt;p&gt;This way we make sure that the resources list is always in the correct partial order that is required by the engine to function correctly, and for the snapshot to be considered valid.&lt;/p&gt;
&lt;p&gt;The algorithm looks as follows:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# Apply snapshot writes. This replaces the full snapshot we have on the service.
# We do this if default providers change, because we don&amp;#39;t emit steps for that, as
# we do for the rest of the operations.
snapshot = find_write_journal_entry_or_use_base(base, journal)
# Track changes
deletes, snapshot_deletes, mark_deleted, mark_pending = set(), set(), set(), set()
operation_id_to_resource_index = {}
# Process journal entries. This is the main algorithm, that adds new resources
# to the snapshot, removes existing ones, deals with refreshes, and operations
# that update outputs.
incomplete_ops = {}
has_refresh = false
index = 0
for entry in journal:
match entry.type:
case BEGIN:
incomplete_ops[entry.op_id] = entry
case SUCCESS:
del incomplete_ops[entry.op_id]
if entry.state and entry.op_id:
resources.append(entry.state)
operation_id_to_resource_index.add(entry.op_id, index)
index++
if entry.remove_old:
snapshot_deletes.add(entry.remove_old)
if entry.remove_new:
deletes[remove_new] = true
if entry.pending_replacement:
mark_pending(entry.pending_replacement)
if entry.delete:
mark_deleted(entry.delete)
has_refresh |= entry.is_refresh
case REFRESH_SUCCESS:
del incomplete_ops[entry.op_id]
has_refresh = true
if entry.remove_old:
if entry.state:
snapshot_replacements[entry.remove_old] = entry.state
else:
snapshot_deletes.add(entry.remove_old)
if entry.remove_new:
if entry.state:
deletes[entry.remove_new] = true
else:
resources.replace(operation_id_to_resource_index(entry.remove_new), entry.state)
case FAILURE:
del incomplete_ops[entry.op_id]
case OUTPUTS:
if entry.state and entry.remove_old:
snapshot_replacements[entry.remove_old] = entry.state
if entry.state and entry.remove_new:
resources.replace(operation_id_to_resource_index(entry.remove_new), entry.state)
deletes = deletes.map(|i| =&amp;gt; operation_id_to_resource_index[i])
# Now that we have marked all the operations, and created a new list of resources, we can
# go through them, and merge the list of new resources and old resources from the snapshot
# that remain together.
for i, res in resources:
if i in deletes:
remove_from_resources(resources, i)
# Merge snapshot resources. These resources have not been touched by the update, and will
# thus be appended to the end of the resource list. We also need to mark existing resources as
# `Delete` and `PendingReplacement` here.
for i, res in enumerate(snapshot.resources):
if i not in snapshot_deletes:
if i in snapshot_replacements:
resources.append(snapshot_replacements[i])
else:
if i in mark_deleted:
res.delete = true
if i in mark_pending:
res.pending_replacement = true
resources.append(res)
# Collect pending operations. These are stored separately from the resources list
# in the snapshot.
pending_ops = [op.operation for op in incomplete_ops.values() if op.operation]
pending_ops.extend([op for op in snapshot.pending_ops if op.type == CREATE])
# Rebuild dependencies if necessary. Refreshes can delete parents or dependencies
# of resources, without affecting the resource itself directly. We need to now remove
# these relationships to make sure the snapshot remains valid.
if has_refresh:
rebuild_dependencies(resources)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The full documentation of the algorithm can be found in our &lt;a href="https://pulumi-developer-docs.readthedocs.io/latest/docs/architecture/deployment-execution/state.html#snapshot-journaling"&gt;developer docs&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="rollout"&gt;Rollout&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;pulumi&lt;/code&gt; state is a very central part of &lt;code&gt;pulumi&lt;/code&gt;, so we wanted to be extra careful with the rollout to make sure we don&amp;rsquo;t break anything. We did this in a few stages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We implemented the replay interface inside the &lt;code&gt;pulumi&lt;/code&gt; CLI, and ran it in parallel with the current snapshotting implementation in our tests. The snapshots were then compared automatically, and tests made to fail when the result didn&amp;rsquo;t match.&lt;/li&gt;
&lt;li&gt;Since tests can&amp;rsquo;t cover all possible edge cases, the next step was to run the journaler in parallel with the current snapshotting implementation internally. This was still without sending the results to the service. However we would compare the snapshot, and send an error event to the service if the snapshot didn&amp;rsquo;t match. In our data warehouse we could then inspect any mismatches, and fix them. Since this does involve the service in a minor way, we would only do this if the user is using the Cloud backend.&lt;/li&gt;
&lt;li&gt;Next up was adding a feature flag for the service, so journaling could be turned on selectively for some orgs. At the same time we implemented an opt-in environment variable in the CLI (&lt;code&gt;PULUMI_ENABLE_JOURNALING&lt;/code&gt;), so the feature could be selectively turned on by users, if both the feature flag is enabled and the user sets the environment variable. This way we could slowly start enabling this in our repos, e.g. first in the integration tests for &lt;code&gt;pulumi/pulumi&lt;/code&gt;, then in the tests for &lt;code&gt;pulumi/examples&lt;/code&gt; and &lt;code&gt;pulumi/templates&lt;/code&gt;, etc.&lt;/li&gt;
&lt;li&gt;Allow users to start opting in. If you want to opt-in with your org, please reach out to us, either on the &lt;a href="https://slack.pulumi.com/"&gt;Community Slack&lt;/a&gt;, or through our &lt;a href="https://support.pulumi.com/hc/en-us"&gt;Support channels&lt;/a&gt;, and we&amp;rsquo;ll opt your org into the feature flag. Then you can begin seeing the performance improvements by setting the &lt;code&gt;PULUMI_ENABLE_JOURNALING&lt;/code&gt; env variable to true.&lt;/li&gt;
&lt;li&gt;Turn on the feature flag for everyone, but still require the &lt;code&gt;PULUMI_ENABLE_JOURNALING&lt;/code&gt; env variable to be set to true. (We are here right now).&lt;/li&gt;
&lt;li&gt;Flip the feature on by default, but still allow users to opt out using a &lt;code&gt;PULUMI_DISABLE_JOURNALING&lt;/code&gt; env variable.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="whats-next"&gt;What&amp;rsquo;s next&lt;/h2&gt;
&lt;p&gt;While these performance improvements hopefully make your day to day use of &lt;code&gt;pulumi&lt;/code&gt; quicker and more enjoyable, we&amp;rsquo;re not quite done here. We&amp;rsquo;re looking at some other performance improvements, that will hopefully speed up your workflows even more.&lt;/p&gt;</description><author>Thomas Gummerer</author><category>journaling</category><category>performance</category><category>data-integrity</category><category>features</category></item><item><title>Faster Secrets in Pulumi IaC</title><link>https://www.pulumi.com/blog/faster-secrets-management/</link><pubDate>Thu, 13 Mar 2025 01:00:00 -0700</pubDate><guid>https://www.pulumi.com/blog/faster-secrets-management/</guid><description>
&lt;img src="https://www.pulumi.com/images/generated/blog/faster-secrets-management/index.png" /&gt;
&lt;p&gt;Pulumi now handles &lt;a href="https://www.pulumi.com/docs/concepts/secrets/"&gt;secrets&lt;/a&gt; more efficiently through optimized encryption and decryption processes, reducing deployment times while maintaining security standards. Users of &lt;a href="https://app.pulumi.com/signin"&gt;Pulumi Cloud&lt;/a&gt; for state management will notice the most improvement due to new batch API capabilities.&lt;/p&gt;
&lt;h2 id="secrets-management-in-pulumi"&gt;Secrets Management in Pulumi&lt;/h2&gt;
&lt;p&gt;Pulumi Infrastructure as Code (IaC)&amp;rsquo;s built-in secrets management encrypts individual &lt;em&gt;secrets&lt;/em&gt; within the stack for fine-grained protection. Pulumi Cloud transmits and stores stack state securely, ensuring that sensitive information—such as database passwords, API keys, and access tokens—remains protected even if someone gains access to your stack file. For organizations with more complex secrets management needs, Pulumi also offers &lt;a href="https://www.pulumi.com/product/esc/"&gt;Pulumi ESC&lt;/a&gt; (Environments, Secrets, and Configuration), which provides centralized secrets management with hierarchical environments and dynamic credential generation.&lt;/p&gt;
&lt;p&gt;Beyond simple encryption, Pulumi tracks the transitive use of secrets to prevent accidental exposure of sensitive values. This tracking works across your entire infrastructure definition, ensuring secrets remain protected:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Within the stack state&lt;/li&gt;
&lt;li&gt;In CLI output&lt;/li&gt;
&lt;li&gt;In the &lt;a href="https://app.pulumi.com/signin"&gt;Pulumi Cloud console&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When using Pulumi Cloud as your secrets provider, all encryption and decryption operations happen server-side without transmitting the encryption key to the Pulumi program. Combined with Pulumi Cloud&amp;rsquo;s robust user authorization and SSO integration, this creates a comprehensive security framework that ensures only authorized team members can access sensitive information.&lt;/p&gt;
&lt;h2 id="performance-challenges-with-secrets"&gt;Performance Challenges with Secrets&lt;/h2&gt;
&lt;p&gt;While security is paramount, performance is equally crucial for developer productivity. Every second spent waiting for encryption or decryption operations is time not spent building and improving your infrastructure.&lt;/p&gt;
&lt;p&gt;Previously, when working with stacks containing numerous secrets, users could experience noticeable delays during operations that required encryption or decryption. This was primarily due to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Individual network requests for each secret operation&lt;/li&gt;
&lt;li&gt;Network latency compounding with each separate request&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For teams with complex infrastructure containing dozens or hundreds of secrets, these delays could add up to significant wait times.&lt;/p&gt;
&lt;h2 id="our-performance-improvements"&gt;Our Performance Improvements&lt;/h2&gt;
&lt;p&gt;We&amp;rsquo;ve reduced network overhead by batching encryption work into a single request instead of making separate encryption requests for each secret. This batching approach:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reduces the total number of HTTP requests&lt;/li&gt;
&lt;li&gt;Minimizes the impact of network latency&lt;/li&gt;
&lt;li&gt;Allows for more efficient server-side processing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is combined with identifying when no secrets have changed between intermediate deployment steps to bypass the work of re-encrypting all secrets.&lt;/p&gt;
&lt;h3 id="measurable-performance-gains"&gt;Measurable Performance Gains&lt;/h3&gt;
&lt;p&gt;The performance improvements are particularly noticeable for stacks with many secrets or in environments with higher network latency:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Secrets Count&lt;/th&gt;
&lt;th&gt;Network Latency&lt;/th&gt;
&lt;th&gt;Typical Time Savings&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;100ms&lt;/td&gt;
&lt;td&gt;~1 second&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;150ms&lt;/td&gt;
&lt;td&gt;~4 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50+&lt;/td&gt;
&lt;td&gt;150ms&lt;/td&gt;
&lt;td&gt;10+ seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100+&lt;/td&gt;
&lt;td&gt;150ms&lt;/td&gt;
&lt;td&gt;20+ seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The performance improvements are noticeable for stacks with many secrets or in environments with higher network latency. For teams running CI/CD pipelines with frequent deployments, these savings compound throughout the day, potentially saving significant cumulative wait time across your engineering organization.&lt;/p&gt;
&lt;h2 id="getting-started"&gt;Getting Started&lt;/h2&gt;
&lt;p&gt;To benefit from these performance improvements:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Update to &lt;a href="https://www.pulumi.com/docs/install/"&gt;Pulumi IaC version 3.155.0&lt;/a&gt; or later&lt;/li&gt;
&lt;li&gt;No configuration changes are needed—the optimizations work automatically&lt;/li&gt;
&lt;li&gt;Ensure you&amp;rsquo;re using Pulumi Cloud as your secrets provider for additional speed gains&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;These improvements maintain complete backward compatibility with existing stacks and secret values. Your existing encrypted secrets will continue to work without any migration steps required.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;At Pulumi, we&amp;rsquo;re committed to providing both robust security and exceptional performance. These secrets management optimizations represent our ongoing effort to improve the developer experience without compromising on security.&lt;/p&gt;
&lt;p&gt;By reducing wait times during encryption and decryption operations, we&amp;rsquo;re helping teams be more productive while maintaining the same high level of protection for sensitive information. This update exemplifies our philosophy that security and performance should complement rather than compete with each other.&lt;/p&gt;
&lt;h2 id="beyond-stack-secrets-pulumi-esc"&gt;Beyond Stack Secrets: Pulumi ESC&lt;/h2&gt;
&lt;p&gt;While these performance improvements enhance Pulumi&amp;rsquo;s built-in secrets management, some organizations require more advanced capabilities. If you need secrets to be centrally managed, composable, and reusable across teams and applications, &lt;a href="https://www.pulumi.com/product/esc/"&gt;Pulumi ESC&lt;/a&gt; (Environments, Secrets, and Configuration) may be a better fit. Pulumi ESC keeps sensitive information out of source control, supports hierarchical environments, and enables dynamic credential generation. It&amp;rsquo;s designed for organizations that need to share secrets between multiple teams, applications, and infrastructure deployments while maintaining strict access controls and auditability.&lt;/p&gt;
&lt;h3 id="get-started-today"&gt;Get Started Today&lt;/h3&gt;
&lt;p&gt;Update to Pulumi IaC 3.155.0 today to experience these performance improvements and &lt;a href="https://github.com/pulumi/pulumi/issues/new"&gt;let us know what you think&lt;/a&gt;!&lt;/p&gt;</description><author>Daniel Bradley</author><category>features</category><category>releases</category><category>secrets</category><category>performance</category></item><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>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>