<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[SWE from a second world country 🇻🇳]]></title><description><![CDATA[Opinions and works cited in this site are my own.]]></description><link>https://md.phuongnguyen.xyz</link><generator>RSS for Node</generator><lastBuildDate>Mon, 12 Oct 2026 13:03:50 GMT</lastBuildDate><atom:link href="https://md.phuongnguyen.xyz/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Re-exercise a classic Temporal stress test]]></title><description><![CDATA[Objective
Demonstrate how Tempura solves a typical Temporal scaling bottleneck (namespace overload) by horizontally scaling across multiple physical namespaces/clusters under load, avoiding the need t]]></description><link>https://md.phuongnguyen.xyz/re-exercise-a-classic-temporal-stress-test</link><guid isPermaLink="true">https://md.phuongnguyen.xyz/re-exercise-a-classic-temporal-stress-test</guid><category><![CDATA[temporal]]></category><category><![CDATA[golang]]></category><category><![CDATA[distributed system]]></category><dc:creator><![CDATA[Phuong Duy Nguyen]]></dc:creator><pubDate>Fri, 03 Jul 2026 22:47:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/629c3e80e9ba973b69192640/c4b57280-40c8-4649-92f5-1d010d1cba3c.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Objective</h2>
<p>Demonstrate how Tempura solves a typical Temporal scaling bottleneck (namespace overload) by horizontally scaling across multiple physical namespaces/clusters under load, avoiding the need to re-provision a cluster with higher shard counts.</p>
<h2>Background</h2>
<p>You've maybe already familiar with <a href="https://temporal.io/blog/scaling-temporal-the-basics">Scaling Temporal: The basics</a>, the canonical load test exercise for Temporal. The article mentioned a common bottleneck for Temporal under heavy load is high number of state transition &amp; request latency caused by shard lock contention. The typical resolution is increasing the History Shard count. However, the shard count <strong>cannot be changed after a cluster is built</strong>. If your namespace is overloaded, you are normally forced to migrate to a new cluster with a larger shard count or suffer degraded performance.</p>
<p>In this article, we win re-exercise the stress-test, but with Tempura in the middle to see how Tempura solves this gracefully by abstracting a single logical namespace to multiple physical namespaces across different clusters, Tempura allows you to add additional capacity on the fly without altering the client configuration or rebuilding your primary cluster.</p>
<h2>Test Scenario Setup</h2>
<h3>Step 1: Initial Setup with a Single Physical Namespace</h3>
<p>First, we start the compose <a href="https://github.com/phuongdnguyen/tempura/tree/master/sandbox">stack</a>, the stack includes:</p>
<ul>
<li><p>2 Temporal clusters</p>
</li>
<li><p>1 Redis instance</p>
</li>
<li><p>1 Prometheus and 1 Grafana instance</p>
</li>
<li><p>1 Tempura instance (our routing layer)</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/629c3e80e9ba973b69192640/6c80ab2b-7baa-4f56-b4b0-3f86c59b453c.png" alt="" style="display:block;margin:0 auto" />

<pre><code class="language-plaintext">docker-compose -f docker-compose.yml up -d 
</code></pre>
<p>We configure Tempura to route our target Virtual Namespace exclusively to Cluster A.</p>
<pre><code class="language-shell"># Create the Virtual Namespace
curl -X POST http://localhost:8089/namespace/virtual/create \
  -d '{"name": "my-app-namespace"}'

# Map Cluster A's physical namespace to the Virtual Namespace
curl -X POST http://localhost:8089/namespace/physical/add \
  -d '{
      "virtual_namespace": "my-app-namespace",
      "physical_namespace": "namespace-a",
      "cluster_address": "cluster-a.example.com:7233"
  }'
</code></pre>
<pre><code class="language-shell">Virtual namespace 'my-app-namespace' created successfully
Physical namespace 'namespace-a' added to virtual namespace 'my-app-namespace'
</code></pre>
<h3>Step 2: Apply Load (The Overload Condition)</h3>
<p>Using the <code>benchmark-workers</code> image from the Temporal load testing guide, we start applying a heavy load to the Tempura endpoint. <em>Ensure your workers are pointed to Tempura instead of the physical Temporal clusters.</em></p>
<pre><code class="language-bash"># Start Workers pointing to Tempura
docker run --network=sandbox_temporal-sandbox-network --env TEMPORAL_TASK_QUEUE=benchmark --env TEMPORAL_NAMESPACE=my-app-namespace --env TEMPORAL_GRPC_ENDPOINT=tempura.example.com:8088 ghcr.io/temporalio/benchmark-workers:main


# Start the Runner to generate workflows
# -c is the number of concurrent workflows
docker run --network=sandbox_temporal-sandbox-network --env TEMPORAL_TASK_QUEUE=benchmark --env TEMPORAL_WORKFLOW_TASK_POLLERS=16 --env TEMPORAL_ACTIVITY_TASK_POLLERS=8 --env TEMPORAL_NAMESPACE=my-app-namespace --env TEMPORAL_GRPC_ENDPOINT=tempura.example.com:8088 ghcr.io/temporalio/benchmark-workers:main runner -c 50 -t ExecuteActivity '{ "Count": 10, "Activity": "Echo", "Input": { "Message": "test" } }'
</code></pre>
<p><em>Note: Adjust the runner parallelism to generate enough load to stress Cluster A.</em></p>
<h3>Step 3: Measure &amp; Observe the Bottleneck</h3>
<p>Observe the Prometheus metrics for Cluster A:</p>
<ul>
<li><strong>State Transitions</strong>: <code>sum(rate(state_transition_count_count{namespace="namespace-a"}[1m]))</code></li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/629c3e80e9ba973b69192640/fcd0b9f4-1a1c-4f9f-afdc-b944d169e8ad.png" alt="" style="display:block;margin:0 auto" />

<ul>
<li><p><strong>Shard lock spike</strong>:</p>
<pre><code class="language-promql">histogram_quantile(0.95, sum by (instance, le)(rate(lock_latency_bucket[1m])))
</code></pre>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/629c3e80e9ba973b69192640/4ab55f59-ae3e-4f8a-8ca4-535931bee048.png" alt="" style="display:block;margin:0 auto" />

<p><strong>All workflows are allocated to cluster A</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/629c3e80e9ba973b69192640/b491bae7-7168-4c9b-a008-1004f7305856.png" alt="" style="display:block;margin:0 auto" />

<p><strong>Expected Result:</strong> As the load increases, the <code>StartWorkflowExecution</code> latency on Cluster A will spike above our acceptable SLO (e.g. &gt; 150ms). This indicates that <code>namespace-a</code> is overloaded (due to shard lock contention or database limits) and cannot comfortably handle the load.</p>
<h3>Step 4: Scale Out with Tempura</h3>
<p>Instead of rebuilding Cluster A with a higher shard count (which requires downtime and complex migrations), we seamlessly add <strong>Cluster B</strong>'s physical namespace to the Tempura Virtual Namespace.</p>
<pre><code class="language-bash"># Map Cluster B's physical namespace to the existing Virtual Namespace
curl -X POST http://localhost:8089/namespace/physical/add \
  -d '{
      "virtual_namespace": "my-app-namespace",
      "physical_namespace": "namespace-b",
      "cluster_address": "cluster-b.example.com:7233"
  }'
</code></pre>
<pre><code class="language-plaintext">Physical namespace 'namespace-b' added to virtual namespace 'my-app-namespace'
</code></pre>
<h3>Step 5: Observe Load Sharing and Recovery</h3>
<p>Continue the <code>benchmark-worker</code> load tests and monitor both clusters in Grafana. After adding a new namespace to share the load, the load in cluster A are shared to cluster B</p>
<img src="https://cdn.hashnode.com/uploads/covers/629c3e80e9ba973b69192640/7aeab1d9-864c-49b0-92df-bb6fb454ec90.png" alt="" style="display:block;margin:0 auto" />

<p><strong>State transition count steadily back to equilibrium</strong></p>
<img src="https://cdn.hashnode.com/uploads/covers/629c3e80e9ba973b69192640/b2aeb7ff-1d8e-4436-b161-cf9467539912.png" alt="" style="display:block;margin:0 auto" />

<p><strong>What Tempura does behind the scenes:</strong></p>
<p>Tempura’s proxy implementation acts as a Temporal-aware proxy layer that intercepts Temporal requests to dynamically route them based on a semantic mapping cache. When a request (like <code>StartWorkflowExecution</code> or a signal) arrives, the proxy decodes the protobuf payload to extract the <code>WorkflowId</code> and <code>VirtualNamespace</code>. It then queries its mapping cache using the <code>WorkflowId</code> to check for an existing assignment. If a mapping exists (a cache hit), it means the workflow is already running, and the proxy applies "workflow stickiness" by rewriting the request to target that exact physical cluster. If it's a cache miss (a new workflow), the proxy's resolver uses a load balancer to assign a physical namespace, stores this new <code>WorkflowId -&gt; PhysicalNamespace</code> mapping in the cache, and forwards the rewritten request. This guarantees that all subsequent updates, queries, or signals for that workflow are consistently routed to the correct backend cluster without the client needing to know the physical topology.</p>
<p>TTL (Time-to-Live) handling in Tempura is specifically designed to manage the lifecycle of the semantic mapping cache (Redis) so it doesn't grow indefinitely, while ensuring that mappings exist exactly as long as they are needed. Some key things:</p>
<ol>
<li><p><strong>Indefinite Storage for Active Workflows:</strong> When a workflow is first started and a new mapping is created (<code>WorkflowId -&gt; PhysicalNamespace</code>), it is stored in Redis with <strong>no expiration</strong>. This ensures the mapping persists for the entire duration of the workflow execution, regardless of whether it takes seconds or months.</p>
</li>
<li><p><strong>Intercepting Completion:</strong> Tempura inspects the payloads of <code>RespondWorkflowTaskCompleted</code> requests as workers send them back to the server. It scans the list of commands within the payload looking for <code>COMMAND_TYPE_COMPLETE_WORKFLOW_EXECUTION</code> (which indicates the workflow has successfully finished).</p>
</li>
<li><p><strong>Dynamic TTL Application:</strong> When a workflow completion is detected, Tempura calls <code>UpdateTTL(workflowID)</code> in the mapping layer.</p>
</li>
<li><p><strong>Namespace Retention Synchronization:</strong> Inside <code>UpdateTTL</code>, Tempura maintains namespace retention information. This layer retrieves the exact retention period configured on the underlying physical Temporal namespace (e.g., 7 days, 30 days).</p>
</li>
</ol>
<p><strong>Expected Result:</strong></p>
<ol>
<li><p>The <strong>State Transitions</strong> metric will show the workload being distributed across <em>both</em> Cluster A and Cluster B.</p>
</li>
<li><p>Cluster B's resources are actively utilized, effectively doubling the horizontal scalability of the single logical <code>my-app-namespace</code>.</p>
</li>
</ol>
<h2>Conclusion</h2>
<p>This scenario exercises Tempura's core value proposition: dynamically scaling Temporal namespaces beyond the limits of a single cluster's architecture (like fixed History Shard counts) without downtime or client-side reconfiguration.</p>
]]></content:encoded></item><item><title><![CDATA[Scaling Temporal beyond a single cluster]]></title><description><![CDATA[Temporal is an incredibly powerful platform for building resilient, highly scalable distributed applications. By abstracting away the complexities of distributed state management, it allows developers]]></description><link>https://md.phuongnguyen.xyz/scaling-temporal-beyond-a-single-cluster</link><guid isPermaLink="true">https://md.phuongnguyen.xyz/scaling-temporal-beyond-a-single-cluster</guid><dc:creator><![CDATA[Phuong Duy Nguyen]]></dc:creator><pubDate>Tue, 23 Jun 2026 04:12:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/629c3e80e9ba973b69192640/5bd800e7-d6bb-419b-8a6c-0b9b82422127.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="https://temporal.io/"><br />Temporal</a> is an incredibly powerful platform for building resilient, highly scalable distributed applications. By abstracting away the complexities of distributed state management, it allows developers to focus entirely on business logic.</p>
<p>However, as you scale out to handle extremely high throughput or look to deploy your application in a true multi-region, active-active fashion, you might hit some architectural challenges. Out of the box, Temporal doesn't natively support multi-cluster active-active deployments.</p>
<h2><strong>The Problem: Scaling Beyond a Single Cluster</strong></h2>
<p>To bridge this gap, there are several ways:</p>
<ul>
<li><p>Implement dynamic shard splitting into Temporal core, which is more complicated and I can not handle it 😅</p>
</li>
<li><p>Introduce another abstraction layer to "group" several physical entities into one unified access layer. This layer should serve Temporal SDK APIs and compatible with existing worker implementation.</p>
</li>
</ul>
<p>I chose the 2nd path, the problem becomes more easy to reason about:</p>
<ul>
<li><p>A workflow is a "sticky session", once created, it will stays in the physical location forever (until retention passed, or replicated to another physical location, in that case, it still essentially a new "session", just with the same "session name").</p>
</li>
<li><p>Following request that is scoped to a workflow, must be routed to the workflow's birthright location.</p>
</li>
<li><p>A workflow is processed when worker execute a poll request from Temporal server. The worker then executes its workflow logic and issue another RPC says that "I'm done, here is the result, tell me what next". This polling request is not workflow scoped, only contains namespace and task queue info.</p>
</li>
</ul>
<h2><strong>Enter Tempura 🍤</strong></h2>
<p>Tempura is my implementation to solves these challenges by acting as an intelligent routing layer sitting in front of your Temporal backend clusters. It intercepts gRPC calls and inspects the protobuf payloads to make appropriate routing decisions.</p>
<p>Tempura introduces the concept of a <code>VirtualNamespace</code>. From your client's perspective, they interact with a single namespace. Under the hood, Tempura maps this to multiple physical namespaces distributed across different Temporal clusters.</p>
<p>Tempura inspects gRPC calls (like <code>StartWorkflowExecution</code> or <code>RespondWorkflowTaskCompleted</code>) to extract the semantic context (<code>WorkflowId</code> and <code>Namespace</code>). Several important design decisions behind Tempura:</p>
<ol>
<li><p><strong>Workflow Stickiness:</strong> Once a workflow is assigned and started on a specific cluster, Tempura's internal resolver caches this mapping. Any subsequent signals, updates, or task completions for that <code>WorkflowId</code> are strictly and reliably routed back to the exact physical cluster where the execution lives.</p>
</li>
<li><p><strong>Connection Parking:</strong> When your workers issue poll requests (<code>PollWorkflowTaskQueue</code> and <code>PollActivityTaskQueue</code>), Tempura fans out these requests to multiple backend clusters. It parks the connection and routes the task back to the worker from whichever cluster has pending tasks available.</p>
</li>
</ol>
<p>The primary advantage of using Tempura is that it allows you to achieve massive scalability and fault tolerance by horizontally scaling workloads across multiple independent backend clusters.</p>
<p>Most importantly, you get this active-active architecture without requiring any configuration changes or custom sharding logic in your client applications or workers. To your clients, it looks like they are talking to a single, infinitely scalable Temporal cluster.</p>
<h2><strong>Things to Keep in Mind</strong></h2>
<p>Because Tempura sits between your clients/workers and the Temporal server, it only sees traffic that goes through the frontend API.</p>
<p>There is one limitation to be aware of: APIs that issue direct RPCs into another history shard within the same cluster (bypassing the Temporal frontend server) will not pass through Tempura. Examples include:</p>
<ul>
<li><p>Signaling another workflow from <em>inside</em> a workflow.</p>
</li>
<li><p>Canceling external workflows from <em>inside</em> a workflow.</p>
</li>
</ul>
<p>Since these internal APIs are handled by Temporal's internal queues (timer/transfer), you simply need to wrap these calls inside an Activity. By wrapping the call in an Activity, the request is directed through Tempura's mapping layer, ensuring it gets routed to the correct destination cluster.</p>
<p>Give it a try, if you encounter any issues, please kindly help to submit also. Github repo is <a href="https://github.com/phuongdnguyen/tempura">here</a></p>
]]></content:encoded></item><item><title><![CDATA[On self-development and efficiency at work]]></title><description><![CDATA[Self-development:

Things you want to do

Things you want to learn

Things you want to be involved with


Work efficiency:

How fast can you deliver

The relative quality of your work compared to your]]></description><link>https://md.phuongnguyen.xyz/on-self-development-and-efficiency-at-work</link><guid isPermaLink="true">https://md.phuongnguyen.xyz/on-self-development-and-efficiency-at-work</guid><dc:creator><![CDATA[Phuong Duy Nguyen]]></dc:creator><pubDate>Sun, 07 Jun 2026 09:07:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/629c3e80e9ba973b69192640/d221aa4e-82ea-4d18-9471-7c54ecd7727a.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Self-development:</p>
<ul>
<li><p>Things you want to do</p>
</li>
<li><p>Things you want to learn</p>
</li>
<li><p>Things you want to be involved with</p>
</li>
</ul>
<p>Work efficiency:</p>
<ul>
<li><p>How fast can you deliver</p>
</li>
<li><p>The relative quality of your work compared to your co-workers in the vicinities (think of team level, organization level)</p>
</li>
</ul>
<p>What is often misunderstood (from my experience): these 2 collides. But it is not, unless you are lucky. Otherwise, let's consider these scenarios normal:</p>
<ul>
<li><p>You are interested in database internals and your work require you to build a React Web using Claude.</p>
</li>
<li><p>You want to implement a cool white paper, but your work require you to spend the whole day to communicate with stakeholders to integrate a new system that only require 3 lines of code change.</p>
</li>
<li><p>You can make a feature/API more sophisticated, the kind of architecture that are being taught in every system design books. Don't do it. Implement just enough to get by, same as it was implemented everywhere else in your company, save the scaling capability for next sprint/next half OKR.</p>
</li>
</ul>
<p>That's normal and that's life. 9-18 you are paid to work for others's dreams. The stakeholders that you are complaining about, he or she is also probably hate this as much as you do. Instead of being hatred so much, let's coordinate and get the work done. Leave your fancy optimization idea behind, leave the rocket-science RFC that you want to do behind, do it for the cash.</p>
<img src="https://cdn.hashnode.com/uploads/covers/629c3e80e9ba973b69192640/c1267c29-d1dd-419c-a3d1-5daa204939f5.png" alt="" style="display:block;margin:0 auto" />

<p>What about the things that your are eager to get involved with? Wake up a bit earlier, find some spare time here and there. There is no magic formula for this. If you want more you just have to invest more.</p>
]]></content:encoded></item><item><title><![CDATA[The Multiverse in a Box: Implementing Deterministic Simulation Testing at the Hypervisor Layer]]></title><description><![CDATA[Testing a distributed system is notoriously difficult. In a production environment, systems encounter a continuous stream of non-deterministic events: network packets arrive out of order, CPU cores th]]></description><link>https://md.phuongnguyen.xyz/the-multiverse-in-a-box-implementing-deterministic-simulation-testing-at-the-hypervisor-layer</link><guid isPermaLink="true">https://md.phuongnguyen.xyz/the-multiverse-in-a-box-implementing-deterministic-simulation-testing-at-the-hypervisor-layer</guid><dc:creator><![CDATA[Phuong Duy Nguyen]]></dc:creator><pubDate>Sat, 23 May 2026 04:37:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/629c3e80e9ba973b69192640/07bea3c6-3cad-4376-a492-0aca97988a9d.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Testing a distributed system is notoriously difficult. In a production environment, systems encounter a continuous stream of non-deterministic events: network packets arrive out of order, CPU cores throttle due to thermal load, and database replicas experience varying garbage collection pauses. When these micro-events align in specific ways, they trigger rare race conditions.</p>
<p>If a bug only manifests once in tens of millions of requests, reproducing it locally using traditional testing methods is practically impossible. Engineers are often left analyzing ambiguous log lines without a reliable path to reproduction.</p>
<p>To solve this, infrastructure engineering has increasingly turned to <strong>Deterministic Simulation Testing (DST)</strong>. While application-level frameworks like Tokio’s <em>Turmoil</em> (for Rust) have brought this capability to specific language ecosystems, achieving language-agnostic, infrastructure-wide determinism requires a lower-level approach by operating at the hypervisor layer.</p>
<p>This article breaks down the engineering mechanics of building a deterministic hypervisor, which is a system capable of freezing time, controlling randomness, and snapshotting virtual states to eliminate non-determinism from distributed systems testing.</p>
<hr />
<h2>The Sources of Non-Determinism</h2>
<p>To make a system completely deterministic, a simulation environment must neutralize four core sources of environmental variation at the hardware and operating system levels:</p>
<ol>
<li><p><strong>Multi-core Thread Scheduling:</strong> When multiple CPU cores execute instructions simultaneously, the exact order in which they read or write to shared memory locations depends on hardware-level variables like cache line invalidations and internal power management.</p>
</li>
<li><p><strong>Asynchronous Hardware Interrupts:</strong> Physical hardware devices (such as network interface cards or storage controllers) notify the operating system of completed tasks by firing hardware interrupts. These interrupts halt the CPU execution flow at unpredictable instruction boundaries.</p>
</li>
<li><p><strong>System Clocks:</strong> Software that queries the physical system clock (<code>time.Now()</code> or <code>std::time::Instant::now()</code>) receives a value tied to real-world wall-clock time, which changes across test runs.</p>
</li>
<li><p><strong>Hardware Entropy:</strong> Calls to system random number generators draw from hardware-level thermal noise inside the silicon, yielding unpredictable outputs.</p>
</li>
</ol>
<hr />
<h2>Architecture of a Deterministic Hypervisor</h2>
<p>Achieving determinism without requiring developers to rewrite their application code or swap out network primitives requires dropping down to the virtualization layer.</p>
<p>By modifying an open-source hypervisor, such as FreeBSD’s minimalist <code>bhyve</code> or AWS’s Rust-based <code>Firecracker</code>, you can construct a virtual machine execution loop that intercepts and controls these hardware behaviors.</p>
<hr />
<h3>1. Enforcing Single-Threaded Context Switching</h3>
<p>To eliminate the unpredictability of multi-core scheduling, a deterministic hypervisor provisions the guest virtual machine (VM) with exactly <strong>one virtual CPU (vCPU)</strong>.</p>
<p>The hypervisor binds this single vCPU execution loop to a single dedicated thread on the host machine. Even if the guest operating system (for example, Linux running multiple Docker containers) spawns hundreds of internal application threads, the hypervisor forces them to execute sequentially on that single core. Because the hypervisor completely controls when the vCPU switches contexts between these guest threads, physical concurrency noise is eliminated.</p>
<h3>2. Instruction Counting via Hardware PMCs</h3>
<p>To control execution with instruction-level precision, the hypervisor must be able to instruct the CPU to execute a precise number of instructions and then halt.</p>
<p>This is implemented using the CPU's physical <strong>Performance Monitoring Counters (PMCs)</strong>, configured specifically to track "instructions retired." The hypervisor sets a countdown value within the PMC; when the counter hits zero, the hardware triggers a VM Exit, dropping control back out of the guest and into the hypervisor.</p>
<blockquote>
<p><strong>The Hardware Skid Challenge:</strong> Modern out-of-order CPU pipelines can occasionally experience a 1-to-2 instruction "skid" when tripping these counters. Production-grade simulators implement a correction layer within the hypervisor to detect these skids, roll back the execution state, and realign it to maintain perfect reproducibility across runs.</p>
</blockquote>
<h3>3. Intercepting Time and Randomness</h3>
<p>Whenever the guest operating system or an application executes an instruction to read external state, the CPU triggers a VM Exit, allowing the hypervisor to intercept and mock the response:</p>
<ul>
<li><p><strong>Logical Time:</strong> When code calls instructions like <code>RDTSC</code> (Read Time-Stamp Counter) on x86 architectures, the hypervisor intercepts the call. It returns a synthetic time value calculated strictly based on the number of CPU instructions executed so far. If the system is idle, virtual time stops moving.</p>
</li>
<li><p><strong>Controlled Entropy:</strong> System calls like <code>getrandom()</code> or hardware instructions like <code>RDRAND</code> are intercepted. The hypervisor feeds them data derived from a mathematical <strong>pseudo-random number generator (PRNG)</strong> tied to a fixed master seed.</p>
</li>
</ul>
<h3>4. Serializing Virtual I/O</h3>
<p>Standard virtual hardware devices (like <code>virtio-net</code> or <code>virtio-blk</code>) operate asynchronously, firing interrupts back to the OS as soon as the host completes a disk or network task.</p>
<p>In a deterministic hypervisor, virtual hardware operations are strictly serialized. When a virtual disk write occurs, the hypervisor queues the response and delivers the completion interrupt to the guest kernel only at a pre-calculated instruction boundary.</p>
<hr />
<h2>State Snapshotting and Branching Realities</h2>
<p>Once 100% deterministic execution is achieved, meaning an entire network topology runs inside a single-threaded loop bounded by a single master seed, the hypervisor can implement highly efficient, copy-on-write memory snapshotting.</p>
<p>As the distributed system executes inside the hypervisor, an external coordination engine monitors variables like code coverage, system logs, and data invariants. When the system enters a complex state, such as right before a database consensus vote, the hypervisor takes an in-memory snapshot of the entire virtual machine's RAM and disk state.</p>
<p>From that exact microsecond, the orchestration engine can branch execution into multiple test paths:</p>
<pre><code class="language-plaintext">                       [ Snapshot at State A ]
                            /          \
                           /            \
       [ Test Path Alpha ]               [ Test Path Beta ]
    Injected Network Partition           Normal Network Path
              │                                   │
              ▼                                   ▼
      (Bug Discovered!)                   (System Healthy)
</code></pre>
<p>In <em>Test Path Alpha</em>, the hypervisor rolls back to the snapshot and simulates a sudden network drop between nodes. In <em>Test Path Beta</em>, it allows the packets to flow normally. Because the underlying state transition is deterministic, if a consistency bug is discovered in <em>Path Alpha</em>, that exact failure sequence can be reproduced identically on every subsequent execution by reusing the same seed and snapshot.</p>
<hr />
<h2>Practical Application: Time-Travel Debugging</h2>
<p>The primary practical benefit of a deterministic hypervisor is the shift in how distributed systems are debugged.</p>
<p>In a traditional multi-node staging environment, finding a race condition requires adding verbose logging, running a stress test, and parsing the resulting traces after the failure occurs.</p>
<p>In a deterministic hypervisor loop, when a system invariant breaks (for instance, a distributed key-value store encounters a split-brain state), the testing platform isolates the exact seed that caused the failure. Because the entire execution path is bounded by that seed, engineers can load it into a time-travel debugger. This allows them to step both forward and backward through the entire operating system's memory space instruction-by-instruction, across multiple isolated containers, tracing pointer or memory corruptions directly back to the exact CPU cycle where the fault occurred.</p>
<hr />
<h2>Conclusion</h2>
<p>Building a deterministic hypervisor requires bypassing many of the parallel execution optimizations built into modern out-of-order CPUs and modifying core scheduling primitives within operating system kernels.</p>
<p>While the upfront engineering complexity of this approach is high, it provides a language-agnostic mechanism to test distributed infrastructure. By moving the identification of distributed edge cases into an automated, single-threaded simulation loop, infrastructure teams can transform unpredictable distributed bugs into predictable, reproducible software defects.</p>
]]></content:encoded></item><item><title><![CDATA[How to Not Get Leveraged at Work as a Software Engineer]]></title><description><![CDATA[Most software engineers do not get leveraged overnight. It happens slowly.
You fix a small issue because it is urgent. You take ownership of a messy system because no one else wants it. You stay late once, then twice, then it becomes expected. At som...]]></description><link>https://md.phuongnguyen.xyz/how-to-not-get-leveraged-at-work-as-a-software-engineer</link><guid isPermaLink="true">https://md.phuongnguyen.xyz/how-to-not-get-leveraged-at-work-as-a-software-engineer</guid><category><![CDATA[Career Growth]]></category><dc:creator><![CDATA[Phuong Duy Nguyen]]></dc:creator><pubDate>Sun, 14 Dec 2025 03:35:01 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/3GZNPBLImWc/upload/0f3259df12f7dd32921ebd144a387d68.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Most software engineers do not get leveraged overnight. It happens slowly.</p>
<p>You fix a small issue because it is urgent. You take ownership of a messy system because no one else wants it. You stay late once, then twice, then it becomes expected. At some point, you realize your workload keeps growing, but your role, influence, and recognition stay the same.</p>
<p>That is what being leveraged looks like.</p>
<p>The first step to avoiding it is clarity. If your responsibilities are vague, people will fill the gaps for you. Not out of malice, but out of convenience. Make it clear what you own, what you support, and what is not yours to carry. Writing things down helps more than people think.</p>
<p>Another common trap is becoming the hero. The engineer who always jumps in, fixes production, and saves the day feels valuable. In reality, heroes are easy to overuse. Instead of optimizing for being busy, optimize for making problems go away. Automate, simplify, and remove recurring pain. Engineers who reduce chaos are harder to exploit than engineers who absorb it.</p>
<p>You also want to be careful about becoming the only person who understands something. It can feel like job security, but it often turns into a cage. When you cannot step away, the organization leans on you harder. Share context, document systems, and bring others in early. Being valuable without being indispensable is a healthier place to be.</p>
<p>Saying no matters too. You do not need to be confrontational. A professional no sounds like explaining trade-offs. If you take on this work, something else will slip. If a task does not fit your role, say so clearly. Framing boundaries as decisions makes them easier for others to accept.</p>
<p>Many engineers get leveraged because their impact is invisible. Effort alone does not speak for itself. Make a habit of communicating what you solved, why it mattered, and what risk or cost you removed. This is not self-promotion. It is basic hygiene.</p>
<p>There is also a subtle form of leverage that comes from taking responsibility without authority. Mentoring, planning, and coordinating are real work. If you are doing leadership work, you should either have the authority that comes with it or consciously pull back. Otherwise, you are donating free labor.</p>
<p>Finally, protect your energy. Your time and focus are not infinite. Treat them like production capacity, not an emergency fund that anyone can withdraw from. Set expectations around availability and urgency. Burnout is not a badge of honor, and it rarely benefits the people who wear it.</p>
<p>Not getting leveraged does not mean doing less or caring less. It means being intentional about where your effort goes and making sure your value compounds instead of being quietly extracted.</p>
]]></content:encoded></item><item><title><![CDATA[How Does the Apache Cassandra GoCQL Driver Function?]]></title><description><![CDATA[When you connect to Cassandra, you need to initialize a Session . This session will create a 2 connection pools to C*

Session will try to discover hosts for control connection pool first by resolving the provided hosts with DNS lookup. It will then ...]]></description><link>https://md.phuongnguyen.xyz/how-does-the-apache-cassandra-gocql-driver-function</link><guid isPermaLink="true">https://md.phuongnguyen.xyz/how-does-the-apache-cassandra-gocql-driver-function</guid><dc:creator><![CDATA[Phuong Duy Nguyen]]></dc:creator><pubDate>Sat, 31 May 2025 10:08:58 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/AXqMy8MSSdk/upload/0110e4a80da7ff890908b0f3d2bdd511.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When you connect to Cassandra, you need to initialize a <code>Session</code> . This session will create a 2 connection pools to C*</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748685981468/c2f4b749-ceda-4c5f-8f76-86872e00581c.png" alt class="image--center mx-auto" /></p>
<p><code>Session</code> will try to discover hosts for control connection pool first by resolving the provided hosts with DNS lookup. It will then use this host to discover other members in the cluster by querying <code>system.peers</code> and <code>system.local</code> for host info. (<a target="_blank" href="https://github.com/apache/cassandra-gocql-driver/blob/7279756d7311211939f1466d20d1a029ab69f4a4/host_source.go#L711">https://github.com/apache/cassandra-gocql-driver/blob/7279756d7311211939f1466d20d1a029ab69f4a4/host_source.go#L711</a>)</p>
<p>Control connection pool also maintains a go routine to periodically heartbeat the connected host and reinitialize the connection when the heartbeat fails. (<a target="_blank" href="https://github.com/apache/cassandra-gocql-driver/blob/7279756d7311211939f1466d20d1a029ab69f4a4/control.go#L125">https://github.com/apache/cassandra-gocql-driver/blob/7279756d7311211939f1466d20d1a029ab69f4a4/control.go#L125</a>)</p>
<p>When a node join/leave the cluster, Cassandra will send a corresponding event to the connected client via the mentioned above control connection. The driver when then issue a ring refresh request for a new host list. (<a target="_blank" href="https://github.com/apache/cassandra-gocql-driver/blob/7279756d7311211939f1466d20d1a029ab69f4a4/events.go#L166">https://github.com/apache/cassandra-gocql-driver/blob/7279756d7311211939f1466d20d1a029ab69f4a4/events.go#L166</a>)</p>
]]></content:encoded></item><item><title><![CDATA[How my mouse fetish changed overtime]]></title><description><![CDATA[During my career as a computer engineer, my personal preference for computer mice has changed a lot. I started with a wired Logitech, which I quickly discarded after switching to a MacBook touchpad.

However, I feel like using the MacBook touchpad (o...]]></description><link>https://md.phuongnguyen.xyz/how-my-mouse-fetish-changed-overtime</link><guid isPermaLink="true">https://md.phuongnguyen.xyz/how-my-mouse-fetish-changed-overtime</guid><category><![CDATA[mouse]]></category><category><![CDATA[blog]]></category><dc:creator><![CDATA[Phuong Duy Nguyen]]></dc:creator><pubDate>Mon, 05 May 2025 12:06:56 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/nbklTnbXZ68/upload/d6ad3e32c87bec39c090b5ee3c336043.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>During my career as a computer engineer, my personal preference for computer mice has changed a lot. I started with a wired Logitech, which I quickly discarded after switching to a MacBook touchpad.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1746445146536/105561b5-705a-499b-9bf9-abfe3e7ab4d6.png" alt="My first mouse - a wired Logitech" class="image--center mx-auto" /></p>
<p>However, I feel like using the MacBook touchpad (or any other kind of touchpad) made me hunch over a lot since I'm already a guy with a huge rib cage. My back hurt, and my shoulders were also stiff because of the excessive internal rotation of my shoulders.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1746445253814/245c7e45-2ef9-454a-939b-bcbd408c7c20.png" alt class="image--center mx-auto" /></p>
<p>That’s when I searched the internet for an “ergonomic mouse” and decided to buy a Logitech (yes, Logitech again) MX Master 3, the most ergonomic mouse at that time.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1746445533233/25347ab3-965c-482f-b30c-192b5cf64bb5.png" alt class="image--center mx-auto" /></p>
<p>Do you see those 2 navigation buttons? I find them very useful when binding IDE navigation keys to them so I don’t have to switch back and forth between the keyboard and the mouse (yes, I’m that kind of person who likes to use the mouse only). 2 buttons are great, but how about more, I asked myself.</p>
<p>That is when I decided to go all out with a full T9 keyboard on the rear of the mouse. Meet Razer Naga HyperSpeed.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1746445922501/d40acd87-5a7e-4692-a2ec-01dcbb4cd479.png" alt class="image--center mx-auto" /></p>
<p>If you are like me, here are some notes:</p>
<ul>
<li><p>Razer does not have an official driver for macOS, but the binding can be installed directly on the firmware. So a workaround is to install your bindings on another Windows machine.</p>
</li>
<li><p>In order for the flashy light on the back of the mouse to work, you will need to use another driver: <a target="_blank" href="https://github.com/1kc/razer-macos">https://github.com/1kc/razer-macos</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[What i'm doing recently]]></title><description><![CDATA[It’s been a year since my last update on social media. Just to let you know, I’ve left the previous company where I built a workflow system based on Temporal that helps you distribute access to users based on their requests. I had a plan to ship an O...]]></description><link>https://md.phuongnguyen.xyz/what-im-doing-recently</link><guid isPermaLink="true">https://md.phuongnguyen.xyz/what-im-doing-recently</guid><dc:creator><![CDATA[Phuong Duy Nguyen]]></dc:creator><pubDate>Sun, 13 Apr 2025 07:03:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/q10VITrVYUM/upload/57ab5c9418f21cced3c7f3d0db976cda.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>It’s been a year since my last update on social media. Just to let you know, I’ve left the previous company where I built a workflow system based on Temporal that helps you distribute access to users based on their requests. I had a plan to ship an OSS version of it, but, well, I don’t have enough resources to do it.</p>
<p>To be honest, I was impressed with Temporal’s capabilities, but it stopped there. Like, I know how to build a workflow on top of Temporal, but I don’t know how it works, where the pitfalls are, or how it operates at a larger scale.</p>
<p>Somehow that brings me to my next adventure, where I’m actually responsible for a workflow system. I chose to learn Temporal “the hard way,” and the gains are quite amazing. Now I feel more comfortable navigating a large codebase and can grasp the design and implementations of other systems quicker. I’m excited to see where my decision will take me. But until then, cerca trova. 👨‍🚀</p>
]]></content:encoded></item><item><title><![CDATA[Fortifying our AWS Organization with temporary credentials 💂☁️]]></title><description><![CDATA[As organizations increasingly adopt cloud services such as Amazon Web Services (AWS) to improve their digital infrastructure, managing access permissions becomes a crucial concern. In AWS, addressing ad hoc access within an AWS Organization can be es...]]></description><link>https://md.phuongnguyen.xyz/fortifying-aws-organization-with-temporary-credentials</link><guid isPermaLink="true">https://md.phuongnguyen.xyz/fortifying-aws-organization-with-temporary-credentials</guid><category><![CDATA[AWS]]></category><category><![CDATA[Security]]></category><category><![CDATA[temporalio]]></category><category><![CDATA[golang]]></category><category><![CDATA[gRPC]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[beginner]]></category><category><![CDATA[Kubernetes]]></category><category><![CDATA[Clean Architecture]]></category><dc:creator><![CDATA[Phuong Duy Nguyen]]></dc:creator><pubDate>Mon, 05 Feb 2024 16:20:04 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1707284102080/2a848858-1c92-433f-a5ad-aeeedba58a13.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As organizations increasingly adopt cloud services such as Amazon Web Services (AWS) to improve their digital infrastructure, managing access permissions becomes a crucial concern. In AWS, addressing ad hoc access within an AWS Organization can be especially challenging.</p>
<h2 id="heading-background-story">Background story</h2>
<p>In our daily, our cloud operation team operates more than a hundred accounts within our AWS Organization. The process of granting user access to an account might appear straightforward with just a few accounts, but as we continue to grow, managing a vast array of permissions presents its own distinct challenges.</p>
<p>For example, an operator may need to request temporary access to a project team's AWS account to perform maintenance work. With hundreds of requests like this daily, our team burnt out. We realized that action needed to be taken.</p>
<p>In this blog post, we will explore JIT, our internal access manager that helps make cloud access more secure.</p>
<h2 id="heading-solution">Solution</h2>
<p>Why not use an existing solution, you may ask. Although there are some solutions out there from <a target="_blank" href="https://www.cyberark.com/products/secure-cloud-access/">CyberArk</a> and even <a target="_blank" href="https://aws-samples.github.io/iam-identity-center-team/">AWS</a> itself, we have a few constraints that are convincing enough to create our own masterpiece:</p>
<ul>
<li><p>We need a more customizable approval step. An approval step might come from different people, with the ability for an approver to delegate their job to others while they're busy, for example, on vacation.</p>
</li>
<li><p>We need to have a mechanism for grouping permission sets. In AWS, permission sets are created and ready to use for any account in its AWS organization.</p>
</li>
<li><p>We want to allow ticket requesters to extend their ticket time frame.</p>
</li>
<li><p>Our requester might be a team manager who wants to request tickets for other users.</p>
<p>  With that being said, let's make a quick rusty product requirement:</p>
<ul>
<li><p>As a requester, I want to be able to:</p>
<ul>
<li><p>Create a ticket for myself, another person(s), or both. The ticket should include: start time, end time, the desired permission set, the account to be accessed, and a brief description explaining the rationale for the ticket.</p>
</li>
<li><p>If my ticket is rejected, I want to edit and resend it to the approver(s).</p>
</li>
<li><p>If I need more time to complete my task, I can request that my approvers extend the end time of my ticket.</p>
</li>
</ul>
</li>
<li><p>As a system administrator, I want to be able to:</p>
<ul>
<li><p>Register and unregister accounts in my AWS organization to this system (JIT).</p>
</li>
<li><p>Register and unregister permission sets along with their access scopes to this system (JIT).</p>
</li>
<li><p>Configure approval rules for registered accounts: the number of approval stages, users in each stage, and the number of approvals required in each stage for it to be considered approved.</p>
</li>
</ul>
</li>
<li><p>As an approver, I want to be able to:</p>
<ul>
<li><p>Approve or reject tickets related only to the account(s) I am responsible for.</p>
</li>
<li><p>Have full control over the ticket I have approved: terminate them any time and audit user activities within the requested time frame for the targeted account.</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="heading-lets-dive-deeper">Let's dive deeper.</h3>
<p>We will split JIT into smaller services:</p>
<ul>
<li><p>Ticket service</p>
</li>
<li><p>Approval service</p>
</li>
<li><p>Approval rule service</p>
</li>
<li><p>Permission set manager service</p>
</li>
<li><p>Account manager service</p>
</li>
<li><p>Notification service</p>
</li>
<li><p>Audit service</p>
</li>
</ul>
<h4 id="heading-requester-flow">Requester flow</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1707147947722/4fe345f6-e6f6-4b6f-b4bd-3f9d92792885.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-approver-flow">Approver flow</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1707147918951/0359082e-716b-4b81-8d42-1179312af546.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-administrator-flow">Administrator flow</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1707220145023/8f9dac03-11b4-4b03-90ae-37d2d9a792cf.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-demo">Demo</h3>
<p>The requester requests a ticket with the necessary informations.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1707220438878/86b9e040-2c71-45a4-9aa6-fc4bb078b236.png" alt class="image--center mx-auto" /></p>
<p>Approver will receive notification via email, they can log in to JIT portal via an Identity Provider that supports OpenID Connect. After the ticket is approved, user will be granted access.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1707220597401/e99e4b68-bd07-45aa-b49a-64e356e2db2e.png" alt class="image--center mx-auto" /></p>
<p>Approver can track user activity in this time frame.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1707220499758/be6d4726-8392-44de-b176-e7f6882e726c.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-discussions">Discussions</h3>
<ul>
<li><p>At the heart of JIT is <a target="_blank" href="https://temporal.io/">Temporal</a>, which we use to orchestrate background processes handling notifications, approval verification logic, and resource creation logic. Temporal greatly assists us in building durable workflows with multiple parallel logic branches. Additionally, <a target="_blank" href="https://temporal.io/">Temporal's</a> workflow status query and workflow signal help simplify a significant amount of implementation code. We can see what's happening in the workflow of each ticket and send signals to it.</p>
</li>
<li><p>Since we are only creating account assignments behind the scenes, we've decided that <a target="_blank" href="https://www.terraform.io/">Terraform</a> (a popular IaC tool written in Go) is overkill. Instead, we create resources directly with the <a target="_blank" href="https://github.com/aws/aws-sdk-go-v2">AWS Go SDK v2</a> and utilize <a target="_blank" href="https://temporal.io/">Temporal</a> workflows with a simple stack to record which resources were created, allowing us to clean them up afterward.</p>
</li>
<li><p>Authorization in JIT is implemented with <a target="_blank" href="https://casbin.org/">Casbin</a>, a popular authorization library also written in Go. Each approval rule/permission set access scope is converted to a <a target="_blank" href="https://casbin.org/">Casbin</a> policy. With a delicate use of <a target="_blank" href="https://casbin.org/">Casbin</a>, we can enforce our own customizable logic into AWS <a target="_blank" href="https://docs.aws.amazon.com/singlesignon/latest/userguide/permissionsetsconcept.html">permission set</a> 🎉</p>
</li>
<li><p>Building the UI was also a challenge for us, as the UI needed to be as familiar as possible to end users. Since we work in an enterprise that just adopted AWS recently, we want users to feel at home whether they are using the actual AWS console or the JIT console. Fortunately, AWS has its design system open-sourced under the name <a target="_blank" href="https://cloudscape.design/">Cloudscape</a>. Users will have an AWS-like experience when using JIT.</p>
</li>
</ul>
<h3 id="heading-conclusion">Conclusion</h3>
<p>Managing access permissions in an AWS Organization can be a daunting task, especially with a growing number of accounts. Creating a custom solution like JIT, that is tailored to the specific needs of your organization, can significantly streamline the process. By breaking down the system into smaller services and leveraging existing tools and libraries like Temporal, Terraform, AWS Go SDK, and Casbin, we can build a robust, efficient, and secure access management system. Furthermore, using an AWS-like UI design system such as Cloudscape can provide a familiar user experience. We look forward to sharing JIT with the wider community soon.</p>
]]></content:encoded></item></channel></rss>