{"id":54133,"date":"2025-11-24T06:13:41","date_gmt":"2025-11-24T06:13:41","guid":{"rendered":"https:\/\/www.devopsschool.com\/blog\/?p=54133"},"modified":"2026-02-21T08:29:02","modified_gmt":"2026-02-21T08:29:02","slug":"performance-engineering-in-net-lab-demo-1","status":"publish","type":"post","link":"https:\/\/www.devopsschool.com\/blog\/performance-engineering-in-net-lab-demo-1\/","title":{"rendered":"Performance Engineering in .NET &#8211; Lab &amp; Demo 1"},"content":{"rendered":"\n<p>It is constructed specifically to <strong>demonstrate real-world performance engineering concepts in .NET<\/strong>, using a scenario every enterprise system deals with: <strong>bulk inserts, EF Core behavior, SQL bottlenecks, GC pressure, and threadpool starvation<\/strong>.<\/p>\n\n\n\n<p>Below is the clear breakdown of <strong>what each endpoint proves<\/strong>, <strong>what performance concept it demonstrates<\/strong>, and <strong>what your trainees should observe<\/strong>.<\/p>\n\n\n\n<p>Sample Code &#8211; <a href=\"https:\/\/github.com\/devopsschool-demo-labs-projects\/Performance-Engineering-in-.NET\/tree\/master\/Lab1\/PerfLabDemov1\" target=\"_blank\" rel=\"noopener\">https:\/\/github.com\/devopsschool-demo-labs-projects\/Performance-Engineering-in-.NET\/tree\/master\/Lab1\/PerfLabDemov1<\/a><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h1 class=\"wp-block-heading\">\u2705 <strong>1. What Naive Bulk Insert Proves<\/strong><\/h1>\n\n\n\n<h3 class=\"wp-block-heading\">Endpoint:<\/h3>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">api\/orders\/bulk-naive?count=1000\n<\/code><\/span><\/pre>\n\n\n<h3 class=\"wp-block-heading\">What the code does:<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Loops 1000 times.<\/li>\n\n\n\n<li>Adds 1 entity.<\/li>\n\n\n\n<li>Calls <code>SaveChanges()<\/code> 1000 times.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">What it demonstrates:<\/h3>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>A. EF Core is very slow when SaveChanges() is called repeatedly<\/strong><\/h3>\n\n\n\n<p>SaveChanges triggers:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Change tracking validation<\/li>\n\n\n\n<li>DB command generation<\/li>\n\n\n\n<li>Opening a DB connection (sometimes pooled)<\/li>\n\n\n\n<li>Executing the SQL INSERT<\/li>\n\n\n\n<li>Committing a transaction<\/li>\n<\/ul>\n\n\n\n<p>Doing it <strong>1000 times<\/strong> = big overhead.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>B. SQL Server becomes chatty<\/strong><\/h3>\n\n\n\n<p>You generate <strong>1000 round-trips<\/strong> to the DB engine.<\/p>\n\n\n\n<p>This simulates real-world anti-patterns:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Chatty repositories<\/li>\n\n\n\n<li>Overuse of <code>Insert()<\/code> inside loops<\/li>\n\n\n\n<li>Poor DDD aggregates causing many tiny writes<\/li>\n\n\n\n<li>Repository pattern over-abstraction<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>C. ThreadPool starvation \/ long-running synchronous IO<\/strong><\/h3>\n\n\n\n<p><code>SaveChanges()<\/code> waits for the DB:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Can exhaust threadpool threads<\/li>\n\n\n\n<li>Causes slower Kestrel handling<\/li>\n\n\n\n<li>Latency increases dramatically<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>D. GC Pressure<\/strong><\/h3>\n\n\n\n<p>Creating many objects inside a loop causes:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>More allocations<\/li>\n\n\n\n<li>More Gen0\/Gen1 collections<\/li>\n\n\n\n<li>More pauses<\/li>\n<\/ul>\n\n\n\n<p>Which you can show live using:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">dotnet-counters monitor --process-id {pid}\n<\/code><\/span><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h1 class=\"wp-block-heading\"><strong>Scientific Output Audience Should Observe<\/strong><\/h1>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Metric<\/th><th>Naive Bulk<\/th><th>Impact<\/th><\/tr><\/thead><tbody><tr><td>CPU<\/td><td>Low (waiting on DB)<\/td><td>Slow<\/td><\/tr><tr><td>Memory<\/td><td>Higher<\/td><td>More GC cycles<\/td><\/tr><tr><td>Time<\/td><td><strong>Slowest<\/strong><\/td><td>5\u201330 sec<\/td><\/tr><tr><td>DB Calls<\/td><td><strong>1000 INSERTs<\/strong><\/td><td>Very chatty<\/td><\/tr><tr><td>Locks<\/td><td>More<\/td><td>Slows everyone else<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h1 class=\"wp-block-heading\"><strong>2. What Optimized Bulk Insert Proves<\/strong><\/h1>\n\n\n\n<h3 class=\"wp-block-heading\">Endpoint:<\/h3>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">api\/orders\/bulk-optimized?count=1000\n<\/code><\/span><\/pre>\n\n\n<h3 class=\"wp-block-heading\">What the code does:<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Adds 1000 entities into DbContext<\/li>\n\n\n\n<li>Calls <strong>SaveChanges once<\/strong><\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">What it demonstrates:<\/h3>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>A. EF Core\u2019s Unit-of-Work Model Works Best<\/strong><\/h3>\n\n\n\n<p>One SaveChanges:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>One transaction<\/li>\n\n\n\n<li>Batch of INSERTs<\/li>\n\n\n\n<li>Less change tracker overhead<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>B. Up to 50x Faster Performance<\/strong><\/h3>\n\n\n\n<p>Typical behavior:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Naive: 10\u201340 seconds<\/li>\n\n\n\n<li>Optimized: 100\u2013400 ms<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>C. Far fewer SQL round-trips<\/strong><\/h3>\n\n\n\n<p>Instead of 1000 statements:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>SQL batches efficiently<\/li>\n\n\n\n<li>Reduces latency<\/li>\n\n\n\n<li>Reduces network round-trips<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>D. Lower GC Pressure<\/strong><\/h3>\n\n\n\n<p>One batch = fewer objects alive at once \u2192 fewer collections.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h1 class=\"wp-block-heading\"><strong>Scientific Output Audience Should Observe<\/strong><\/h1>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Metric<\/th><th>Optimized Bulk<\/th><th>Improvement<\/th><\/tr><\/thead><tbody><tr><td>CPU<\/td><td>Slightly higher<\/td><td>Good (doing real work)<\/td><\/tr><tr><td>Memory<\/td><td>Lower<\/td><td>Steady<\/td><\/tr><tr><td>Time<\/td><td><strong>Very fast<\/strong><\/td><td>10\u201340x faster<\/td><\/tr><tr><td>DB Calls<\/td><td>1<\/td><td>Huge gain<\/td><\/tr><tr><td>Locks<\/td><td>Minimal<\/td><td>Helps concurrency<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h1 class=\"wp-block-heading\"><strong>3. Check Count \u2014 Proves Persistence<\/strong><\/h1>\n\n\n\n<h3 class=\"wp-block-heading\">Endpoint:<\/h3>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">api\/orders\/count\n<\/code><\/span><\/pre>\n\n\n<p>Purpose:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Prove that both naive &amp; optimized inserts actually inserted data.<\/li>\n\n\n\n<li>Visualize DB growth.<\/li>\n\n\n\n<li>Show effect of concurrent inserts.<\/li>\n<\/ul>\n\n\n\n<p>In training, we proved:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>DB is storing state<\/li>\n\n\n\n<li>Optimized insert quickly increases count<\/li>\n\n\n\n<li>Naive insert slowly increases count<\/li>\n<\/ul>\n\n\n\n<p>This makes the performance difference real and visible.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h1 class=\"wp-block-heading\"><strong>Overall What This Demo Proves<\/strong><\/h1>\n\n\n\n<p>This small demo actually teaches <strong>6 core performance engineering concepts<\/strong>:<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\u2714 1. Impact of chatty database I\/O<\/h2>\n\n\n\n<p>1000 tiny writes vs 1 batch write.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\u2714 2. EF Core change tracking and SaveChanges cost<\/h2>\n\n\n\n<p>Understanding EF internals.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\u2714 3. Async vs Sync I\/O behavior<\/h2>\n\n\n\n<p>Bulk naive blocks threads \u2192 causes starvation.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\u2714 4. Garbage Collection impact<\/h2>\n\n\n\n<p>Small object allocation patterns in loops.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\u2714 5. SQL Server round-trip cost<\/h2>\n\n\n\n<p>Network + parsing + locking overhead.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\u2714 6. Importance of batching &amp; correct architectural patterns<\/h2>\n\n\n\n<p>Avoid anti-patterns like:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Repository over-abstraction<\/li>\n\n\n\n<li>N+1 operations<\/li>\n\n\n\n<li>Per-row SaveChanges<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h1 class=\"wp-block-heading\"><strong>How to Experience Demo <\/strong><\/h1>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>I. Run Naive Bulk Insert<\/strong><\/h2>\n\n\n\n<p>It hangs or takes long.<br>Show CPU, memory, threadpool, GC logs.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>II. Run Optimized Bulk Insert<\/strong><\/h2>\n\n\n\n<p>Instant.<br>Show difference in metrics.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>III. Ask the audience: Why did it happen?<\/strong><\/h2>\n\n\n\n<p>Discuss:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>DB round-trips<\/li>\n\n\n\n<li>EF Core internals<\/li>\n\n\n\n<li>Transaction management<\/li>\n\n\n\n<li>GC<\/li>\n\n\n\n<li>ThreadPool starvation<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>IV. Tie it to real architecture<\/strong><\/h2>\n\n\n\n<p>&#8220;Your microservice might look healthy, but if its DB access pattern is naive, you will suffer scaling issues.&#8221;<\/p>\n\n\n\n<p>This is exactly the pain point of real enterprise systems.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h1 class=\"wp-block-heading\"><\/h1>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"957\" height=\"911\" src=\"https:\/\/www.devopsschool.com\/blog\/wp-content\/uploads\/2025\/11\/image-19.png\" alt=\"\" class=\"wp-image-54134\" srcset=\"https:\/\/www.devopsschool.com\/blog\/wp-content\/uploads\/2025\/11\/image-19.png 957w, https:\/\/www.devopsschool.com\/blog\/wp-content\/uploads\/2025\/11\/image-19-300x286.png 300w, https:\/\/www.devopsschool.com\/blog\/wp-content\/uploads\/2025\/11\/image-19-768x731.png 768w\" sizes=\"auto, (max-width: 957px) 100vw, 957px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"976\" height=\"654\" src=\"https:\/\/www.devopsschool.com\/blog\/wp-content\/uploads\/2025\/11\/image-22.png\" alt=\"\" class=\"wp-image-54140\" srcset=\"https:\/\/www.devopsschool.com\/blog\/wp-content\/uploads\/2025\/11\/image-22.png 976w, https:\/\/www.devopsschool.com\/blog\/wp-content\/uploads\/2025\/11\/image-22-300x201.png 300w, https:\/\/www.devopsschool.com\/blog\/wp-content\/uploads\/2025\/11\/image-22-768x515.png 768w\" sizes=\"auto, (max-width: 976px) 100vw, 976px\" \/><\/figure>\n\n\n\n<p>I\u2019ll give you:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>A simple <strong>performance chart dataset<\/strong> (Naive vs Optimized) you can paste into Excel\/PowerPoint.<\/li>\n\n\n\n<li>A <strong>perf counters command list<\/strong> (dotnet-counters + PerfMon).<\/li>\n\n\n\n<li><strong>Ready-to-use scripts<\/strong> for <strong>Postman, k6, and JMeter<\/strong>.<\/li>\n<\/ol>\n\n\n\n<h1 class=\"wp-block-heading\">\ud83d\udfe6 <strong>1. Process Memory (Private Bytes)<\/strong><\/h1>\n\n\n\n<p>In your screenshot:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Memory rises gradually \u2192 EF Core and SQL client allocating objects.<\/li>\n\n\n\n<li>Yellow triangles (GC events) \u2192 frequent Gen0\/Gen1 garbage collections.<\/li>\n\n\n\n<li>After a spike, memory stabilizes because GC reclaimed space.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">\ud83c\udfaf Training Insight:<\/h3>\n\n\n\n<p><strong>Naive bulk insert = more allocations = more frequent GC = lower throughput<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Each <code>SaveChanges<\/code> call causes allocations of:\n<ul class=\"wp-block-list\">\n<li>SQL parameters<\/li>\n\n\n\n<li>Commands<\/li>\n\n\n\n<li>Transaction objects<\/li>\n\n\n\n<li>Change tracker nodes<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>So memory climbs quickly and GC fires often.<\/li>\n<\/ul>\n\n\n\n<p>Optimized bulk will show:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>A single memory spike<\/li>\n\n\n\n<li>Fewer GC events<\/li>\n\n\n\n<li>Faster completion<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h1 class=\"wp-block-heading\">\ud83d\udfe9 <strong>2. CPU (% of all processors)<\/strong><\/h1>\n\n\n\n<p>Your screenshot shows:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>CPU is mostly idle<\/li>\n\n\n\n<li>Small bumps, but never sustained high usage<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">\ud83c\udfaf Training Insight:<\/h3>\n\n\n\n<p><strong>Naive bulk insert does not maximize CPU \u2014 it blocks waiting for DB I\/O.<\/strong><\/p>\n\n\n\n<p>That\u2019s why:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>CPU stays low \u2192 app is waiting on the database<\/li>\n\n\n\n<li>The bottleneck is not CPU \u2192 it&#8217;s SQL round-trips<\/li>\n<\/ul>\n\n\n\n<p>Optimized bulk will show:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Higher CPU bursts (doing real work)<\/li>\n\n\n\n<li>Shorter total duration<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h1 class=\"wp-block-heading\">\ud83d\udfe6 <strong>3. ThreadPool Thread Count<\/strong><\/h1>\n\n\n\n<p>Graph shows slow growth from 8 \u2192 ~12 threads.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">\ud83c\udfaf Training Insight:<\/h3>\n\n\n\n<p><strong>This indicates ThreadPool grows because threads are waiting on database I\/O.<\/strong><\/p>\n\n\n\n<p>Naive bulk:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>1000 calls to <code>SaveChangesAsync()<\/code> = 1000 I\/O waits<\/li>\n\n\n\n<li>ThreadPool sees \u201ctoo many blocking tasks\u201d and tries to grow<\/li>\n<\/ul>\n\n\n\n<p>This is how real production APIs experience:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>ThreadPool starvation<\/li>\n\n\n\n<li>Request queues<\/li>\n\n\n\n<li>High latency<\/li>\n<\/ul>\n\n\n\n<p>Optimized bulk stays closer to 8 threads (default), because the work is not blocking.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h1 class=\"wp-block-heading\">\ud83d\udfe7 <strong>4. GC Heap Size<\/strong><\/h1>\n\n\n\n<p>Increasing heap \u2192 many small allocations from:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>EF Core<\/li>\n\n\n\n<li>SQL Client<\/li>\n\n\n\n<li>LINQ<\/li>\n\n\n\n<li>Loops &amp; entity allocations<\/li>\n<\/ul>\n\n\n\n<p>Frequent GC pauses slow down naive bulk significantly.<\/p>\n\n\n\n<p>Optimized bulk = fewer allocations, fewer collections.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h2 class=\"wp-block-heading\">1\ufe0f\u20e3 Performance Chart \u2013 Naive vs Optimized (Sample Data)<\/h2>\n\n\n\n<p>These are <strong>example numbers<\/strong> you can either:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Use as-is for slides, <strong>or<\/strong><\/li>\n\n\n\n<li>Replace with your actual measurements from your machine.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Suggested Scenario<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>1000 orders (<code>count=1000<\/code>)<\/li>\n\n\n\n<li>Single request to <code>\/bulk-naive<\/code> and <code>\/bulk-optimized<\/code><\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Example Results (for chart)<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Metric<\/th><th>Naive Bulk Insert<\/th><th>Optimized Bulk Insert<\/th><\/tr><\/thead><tbody><tr><td>Total time (ms)<\/td><td>12000<\/td><td>600<\/td><\/tr><tr><td>Requests\/sec (effective)<\/td><td>~83<\/td><td>~1666<\/td><\/tr><tr><td>SQL INSERT statements executed<\/td><td>1000<\/td><td>1 (batched)<\/td><\/tr><tr><td>CPU utilization (peak, %)<\/td><td>25%<\/td><td>40% (short burst)<\/td><\/tr><tr><td>GC Gen0 collections during run<\/td><td>15<\/td><td>4<\/td><\/tr><tr><td>Avg DB round-trip time (ms)<\/td><td>8\u201312<\/td><td>5\u20138<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">How to turn this into a chart<\/h3>\n\n\n\n<p><strong>Chart 1 \u2013 Response Time (ms)<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>X-axis: <code>Naive<\/code>, <code>Optimized<\/code><\/li>\n\n\n\n<li>Y-axis: <code>Total time (ms)<\/code><\/li>\n\n\n\n<li>Values: <code>12000<\/code> vs <code>600<\/code> (log scale works nicely)<\/li>\n<\/ul>\n\n\n\n<p><strong>Chart 2 \u2013 SQL Round-Trips<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>X-axis: <code>Naive<\/code>, <code>Optimized<\/code><\/li>\n\n\n\n<li>Y-axis: <code>Number of DB round-trips<\/code><\/li>\n\n\n\n<li>Values: <code>1000<\/code> vs <code>1<\/code><\/li>\n<\/ul>\n\n\n\n<p><strong>Chart 3 \u2013 Requests\/sec (effective throughput)<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>X-axis: <code>Naive<\/code>, <code>Optimized<\/code><\/li>\n\n\n\n<li>Y-axis: <code>Requests\/sec<\/code><\/li>\n\n\n\n<li>Values: <code>83<\/code> vs <code>1666<\/code><\/li>\n<\/ul>\n\n\n\n<p>On the slide, your message is:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>\u201cSame functionality. Only difference is <strong>how<\/strong> we use EF Core and SaveChanges \u2013 and performance differs by <strong>~20x<\/strong>.\u201d<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h2 class=\"wp-block-heading\">2\ufe0f\u20e3 Perf Counters Command List<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">A. Using <code>dotnet-counters<\/code> (recommended in session)<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Find the process ID<\/strong> of your running API:<\/li>\n<\/ol>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">dotnet tool install --<span class=\"hljs-keyword\">global<\/span> dotnet-counters\ndotnet-counters ps\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Note the PID for <code>Orders.Api<\/code>.<\/p>\n\n\n\n<ol start=\"2\" class=\"wp-block-list\">\n<li><strong>Monitor runtime + ASP.NET + GC counters live:<\/strong><\/li>\n<\/ol>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">dotnet-counters<\/span> <span class=\"hljs-selector-tag\">monitor<\/span> \n  <span class=\"hljs-selector-tag\">--process-id<\/span> &lt;<span class=\"hljs-selector-tag\">PID<\/span>&gt; \n  <span class=\"hljs-selector-tag\">System<\/span><span class=\"hljs-selector-class\">.Runtime<\/span> \n  <span class=\"hljs-selector-tag\">Microsoft<\/span><span class=\"hljs-selector-class\">.AspNetCore<\/span><span class=\"hljs-selector-class\">.Hosting<\/span> \n  <span class=\"hljs-selector-tag\">Microsoft<\/span><span class=\"hljs-selector-class\">.AspNetCore<\/span><span class=\"hljs-selector-class\">.Http<\/span><span class=\"hljs-selector-class\">.Connections<\/span> \n  <span class=\"hljs-selector-tag\">Microsoft<\/span><span class=\"hljs-selector-class\">.EntityFrameworkCore<\/span>\n\n<span class=\"hljs-selector-tag\">dotnet-counters<\/span> <span class=\"hljs-selector-tag\">monitor<\/span> <span class=\"hljs-selector-tag\">--process-id<\/span> 55788 <span class=\"hljs-selector-tag\">System<\/span><span class=\"hljs-selector-class\">.Runtime<\/span> <span class=\"hljs-selector-tag\">Microsoft<\/span><span class=\"hljs-selector-class\">.AspNetCore<\/span><span class=\"hljs-selector-class\">.Hosting<\/span> <span class=\"hljs-selector-tag\">Microsoft<\/span><span class=\"hljs-selector-class\">.AspNetCore<\/span><span class=\"hljs-selector-class\">.Http<\/span><span class=\"hljs-selector-class\">.Connections<\/span> <span class=\"hljs-selector-tag\">Microsoft<\/span><span class=\"hljs-selector-class\">.EntityFrameworkCore<\/span>\n\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/www.devopsschool.com\/blog\/wp-content\/uploads\/2025\/11\/image-20.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"356\" src=\"https:\/\/www.devopsschool.com\/blog\/wp-content\/uploads\/2025\/11\/image-20-1024x356.png\" alt=\"\" class=\"wp-image-54135\" srcset=\"https:\/\/www.devopsschool.com\/blog\/wp-content\/uploads\/2025\/11\/image-20-1024x356.png 1024w, https:\/\/www.devopsschool.com\/blog\/wp-content\/uploads\/2025\/11\/image-20-300x104.png 300w, https:\/\/www.devopsschool.com\/blog\/wp-content\/uploads\/2025\/11\/image-20-768x267.png 768w, https:\/\/www.devopsschool.com\/blog\/wp-content\/uploads\/2025\/11\/image-20-1536x535.png 1536w, https:\/\/www.devopsschool.com\/blog\/wp-content\/uploads\/2025\/11\/image-20.png 1916w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<p>Useful counters to point out:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>System.Runtime<\/code>\n<ul class=\"wp-block-list\">\n<li><code>cpu-usage<\/code><\/li>\n\n\n\n<li><code>gc-heap-size<\/code><\/li>\n\n\n\n<li><code>gen-0-gc-count<\/code>, <code>gen-1-gc-count<\/code>, <code>gen-2-gc-count<\/code><\/li>\n\n\n\n<li><code>threadpool-thread-count<\/code><\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><code>Microsoft.AspNetCore.Hosting<\/code>\n<ul class=\"wp-block-list\">\n<li><code>requests-per-second<\/code><\/li>\n\n\n\n<li><code>total-requests<\/code><\/li>\n\n\n\n<li><code>current-requests<\/code><\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><code>Microsoft.EntityFrameworkCore<\/code>\n<ul class=\"wp-block-list\">\n<li><code>active-db-contexts<\/code><\/li>\n\n\n\n<li><code>queries-per-second<\/code> (depending on provider\/version)<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<p>Run <code>dotnet-counters<\/code> while you hit:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">curl -X POST <span class=\"hljs-string\">\"http:\/\/localhost:5000\/api\/orders\/bulk-naive?count=1000\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>then:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">curl -X POST <span class=\"hljs-string\">\"http:\/\/localhost:5000\/api\/orders\/bulk-optimized?count=1000\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>\u2026and talk through the visible differences (GC counts, RPS, etc.).<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h3 class=\"wp-block-heading\">B. Windows PerfMon (classic counters)<\/h3>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"653\" src=\"https:\/\/www.devopsschool.com\/blog\/wp-content\/uploads\/2025\/11\/image-21-1024x653.png\" alt=\"\" class=\"wp-image-54136\" srcset=\"https:\/\/www.devopsschool.com\/blog\/wp-content\/uploads\/2025\/11\/image-21-1024x653.png 1024w, https:\/\/www.devopsschool.com\/blog\/wp-content\/uploads\/2025\/11\/image-21-300x191.png 300w, https:\/\/www.devopsschool.com\/blog\/wp-content\/uploads\/2025\/11\/image-21-768x490.png 768w, https:\/\/www.devopsschool.com\/blog\/wp-content\/uploads\/2025\/11\/image-21.png 1075w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Open <strong>perfmon.exe<\/strong> \u2192 Add these counters:<\/p>\n\n\n\n<p><strong>Processor<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>Processor(_Total)% Processor Time<\/code><\/li>\n<\/ul>\n\n\n\n<p><strong>Process (dotnet \/ w3wp if hosted under IIS)<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>Process(dotnet)% Processor Time<\/code><\/li>\n\n\n\n<li><code>Process(dotnet)Private Bytes<\/code><\/li>\n\n\n\n<li><code>Process(dotnet)Working Set<\/code><\/li>\n<\/ul>\n\n\n\n<p><strong>.NET CLR Memory<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>.NET CLR Memory(Orders.Api)# Gen 0 Collections<\/code><\/li>\n\n\n\n<li><code>.NET CLR Memory(Orders.Api)# Gen 2 Collections<\/code><\/li>\n\n\n\n<li><code>.NET CLR Memory(Orders.Api)% Time in GC<\/code><\/li>\n<\/ul>\n\n\n\n<p><strong>SQL Server<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>SQLServer:SQL StatisticsBatch Requests\/sec<\/code><\/li>\n\n\n\n<li><code>SQLServer:SQL StatisticsSQL Compilations\/sec<\/code><\/li>\n\n\n\n<li><code>SQLServer:SQL StatisticsSQL Re-Compilations\/sec<\/code><\/li>\n<\/ul>\n\n\n\n<p>This lets you show:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Naive = many batches\/sec, more compilations, more GC.<\/li>\n\n\n\n<li>Optimized = fewer batches\/sec, smoother GC.<\/li>\n<\/ul>\n\n\n\n<h1 class=\"wp-block-heading\">\u2705 <strong>How to Add These Counters in Windows PerfMon<\/strong><\/h1>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>1. Open PerfMon<\/strong><\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Press <strong>Win + R<\/strong><\/li>\n\n\n\n<li>Type: <code>perfmon.exe<\/code><\/li>\n\n\n\n<li>Press <strong>Enter<\/strong><\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h1 class=\"wp-block-heading\"><strong>2. Add Counters<\/strong><\/h1>\n\n\n\n<p>In the left panel:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Expand <strong>Monitoring Tools<\/strong><\/li>\n\n\n\n<li>Click <strong>Performance Monitor<\/strong><\/li>\n\n\n\n<li>Click the <strong>green \u201c+\u201d button<\/strong> (Add Counters)<\/li>\n<\/ul>\n\n\n\n<p>Now you will add counters category by category.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h1 class=\"wp-block-heading\">\u2705 <strong>A. Processor Counters<\/strong><\/h1>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Category:<\/strong> <strong>Processor<\/strong><\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Select <strong>Processor<\/strong><\/li>\n\n\n\n<li>Select counter:\n<ul class=\"wp-block-list\">\n<li><strong>% Processor Time<\/strong><\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Select instance:\n<ul class=\"wp-block-list\">\n<li><strong>_Total<\/strong><\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Click <strong>Add<\/strong><\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h1 class=\"wp-block-heading\">\u2705 <strong>B. Process Counters (dotnet \/ w3wp)<\/strong><\/h1>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Category:<\/strong> <strong>Process<\/strong><\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Select <strong>Process<\/strong><\/li>\n\n\n\n<li>Select counters:\n<ul class=\"wp-block-list\">\n<li><strong>% Processor Time<\/strong><\/li>\n\n\n\n<li><strong>Private Bytes<\/strong><\/li>\n\n\n\n<li><strong>Working Set<\/strong><\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Select instance:\n<ul class=\"wp-block-list\">\n<li>If running as console \u2192 <strong>dotnet<\/strong><\/li>\n\n\n\n<li>If hosted in IIS \u2192 <strong>w3wp<\/strong><\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Click <strong>Add<\/strong><\/li>\n<\/ol>\n\n\n\n<p>(You can add multiple counters at once before clicking <strong>Add<\/strong>.)<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h1 class=\"wp-block-heading\">\u2705 <strong>C. .NET CLR Memory Counters<\/strong><\/h1>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Category:<\/strong> <strong>.NET CLR Memory<\/strong><\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Select <strong>.NET CLR Memory<\/strong><\/li>\n\n\n\n<li>Select counters:\n<ul class=\"wp-block-list\">\n<li><strong># Gen 0 Collections<\/strong><\/li>\n\n\n\n<li><strong># Gen 2 Collections<\/strong><\/li>\n\n\n\n<li><strong>% Time in GC<\/strong><\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Select instance:\n<ul class=\"wp-block-list\">\n<li>Your process name \u2192 <strong>Orders.Api<\/strong><br>(or the DLL name <strong>without .dll<\/strong>)<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Click <strong>Add<\/strong><\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h1 class=\"wp-block-heading\">\u2705 <strong>D. SQL Server Counters<\/strong><\/h1>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Category:<\/strong> <strong>SQLServer:SQL Statistics<\/strong><\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Select <strong>SQLServer:SQL Statistics<\/strong><\/li>\n\n\n\n<li>Select counters:\n<ul class=\"wp-block-list\">\n<li><strong>Batch Requests\/sec<\/strong><\/li>\n\n\n\n<li><strong>SQL Compilations\/sec<\/strong><\/li>\n\n\n\n<li><strong>SQL Re-Compilations\/sec<\/strong><\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Instance = (select default instance \/ MSSQLSERVER)<\/li>\n\n\n\n<li>Click <strong>Add<\/strong><\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h1 class=\"wp-block-heading\">\ud83c\udfaf <strong>Result Summary<\/strong><\/h1>\n\n\n\n<p>You will now be monitoring:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>CPU<\/strong><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Processor(_Total)% Processor Time<\/li>\n\n\n\n<li>Process(dotnet)% Processor Time<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Memory<\/strong><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Process(dotnet)Private Bytes<\/li>\n\n\n\n<li>Process(dotnet)Working Set<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>.NET GC<\/strong><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>CLR Memory Gen0 collections<\/li>\n\n\n\n<li>CLR Memory Gen2 collections<\/li>\n\n\n\n<li>CLR % Time in GC<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>SQL Server Load<\/strong><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Batch Requests\/sec<\/li>\n\n\n\n<li>SQL Compilations\/sec<\/li>\n\n\n\n<li>SQL Re-Compilations\/sec<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h1 class=\"wp-block-heading\">\ud83c\udfaf <strong>The Story Your Graphs Will Show<\/strong><\/h1>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Naive Insert (1000 \u00d7 SaveChanges)<\/strong><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\ud83d\udd3a <strong>High Batch Requests\/sec<\/strong> (many round-trips)<\/li>\n\n\n\n<li>\ud83d\udd3a <strong>Higher SQL compilations<\/strong> (same query many times)<\/li>\n\n\n\n<li>\ud83d\udd3a <strong>More GC activity<\/strong> (more allocations)<\/li>\n\n\n\n<li>\ud83d\udd3a <strong>Higher CPU usage<\/strong><\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Optimized Insert (1 SaveChanges with AddRange)<\/strong><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\ud83d\udd3b <strong>Lower Batch Requests\/sec<\/strong> (single round-trip)<\/li>\n\n\n\n<li>\ud83d\udd3b <strong>Minimal SQL compilations<\/strong><\/li>\n\n\n\n<li>\ud83d\udd3b <strong>Less GC pressure<\/strong><\/li>\n\n\n\n<li>\ud83d\udd3b <strong>Lower CPU, smoother curve<\/strong><\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h2 class=\"wp-block-heading\">3\ufe0f\u20e3 Postman Collection (JSON)<\/h2>\n\n\n\n<p>You can <strong>import this directly into Postman<\/strong>.<\/p>\n\n\n\n<p>Create a file <code>PerfLab.postman_collection.json<\/code> with:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json\">{\n  <span class=\"hljs-attr\">\"info\"<\/span>: {\n    <span class=\"hljs-attr\">\"name\"<\/span>: <span class=\"hljs-string\">\"PerfLab Orders API\"<\/span>,\n    <span class=\"hljs-attr\">\"_postman_id\"<\/span>: <span class=\"hljs-string\">\"e9d1d5f3-0000-0000-0000-000000000001\"<\/span>,\n    <span class=\"hljs-attr\">\"description\"<\/span>: <span class=\"hljs-string\">\"Naive vs Optimized bulk insert demo for Orders API\"<\/span>,\n    <span class=\"hljs-attr\">\"schema\"<\/span>: <span class=\"hljs-string\">\"https:\/\/schema.getpostman.com\/json\/collection\/v2.1.0\/collection.json\"<\/span>\n  },\n  <span class=\"hljs-attr\">\"item\"<\/span>: &#91;\n    {\n      <span class=\"hljs-attr\">\"name\"<\/span>: <span class=\"hljs-string\">\"Bulk Naive (1000)\"<\/span>,\n      <span class=\"hljs-attr\">\"request\"<\/span>: {\n        <span class=\"hljs-attr\">\"method\"<\/span>: <span class=\"hljs-string\">\"POST\"<\/span>,\n        <span class=\"hljs-attr\">\"header\"<\/span>: &#91;],\n        <span class=\"hljs-attr\">\"url\"<\/span>: {\n          <span class=\"hljs-attr\">\"raw\"<\/span>: <span class=\"hljs-string\">\"{{baseUrl}}\/api\/orders\/bulk-naive?count=1000\"<\/span>,\n          <span class=\"hljs-attr\">\"host\"<\/span>: &#91;<span class=\"hljs-string\">\"{{baseUrl}}\"<\/span>],\n          <span class=\"hljs-attr\">\"path\"<\/span>: &#91;<span class=\"hljs-string\">\"api\"<\/span>, <span class=\"hljs-string\">\"orders\"<\/span>, <span class=\"hljs-string\">\"bulk-naive\"<\/span>],\n          <span class=\"hljs-attr\">\"query\"<\/span>: &#91;\n            {\n              <span class=\"hljs-attr\">\"key\"<\/span>: <span class=\"hljs-string\">\"count\"<\/span>,\n              <span class=\"hljs-attr\">\"value\"<\/span>: <span class=\"hljs-string\">\"1000\"<\/span>\n            }\n          ]\n        }\n      }\n    },\n    {\n      <span class=\"hljs-attr\">\"name\"<\/span>: <span class=\"hljs-string\">\"Bulk Optimized (1000)\"<\/span>,\n      <span class=\"hljs-attr\">\"request\"<\/span>: {\n        <span class=\"hljs-attr\">\"method\"<\/span>: <span class=\"hljs-string\">\"POST\"<\/span>,\n        <span class=\"hljs-attr\">\"header\"<\/span>: &#91;],\n        <span class=\"hljs-attr\">\"url\"<\/span>: {\n          <span class=\"hljs-attr\">\"raw\"<\/span>: <span class=\"hljs-string\">\"{{baseUrl}}\/api\/orders\/bulk-optimized?count=1000\"<\/span>,\n          <span class=\"hljs-attr\">\"host\"<\/span>: &#91;<span class=\"hljs-string\">\"{{baseUrl}}\"<\/span>],\n          <span class=\"hljs-attr\">\"path\"<\/span>: &#91;<span class=\"hljs-string\">\"api\"<\/span>, <span class=\"hljs-string\">\"orders\"<\/span>, <span class=\"hljs-string\">\"bulk-optimized\"<\/span>],\n          <span class=\"hljs-attr\">\"query\"<\/span>: &#91;\n            {\n              <span class=\"hljs-attr\">\"key\"<\/span>: <span class=\"hljs-string\">\"count\"<\/span>,\n              <span class=\"hljs-attr\">\"value\"<\/span>: <span class=\"hljs-string\">\"1000\"<\/span>\n            }\n          ]\n        }\n      }\n    },\n    {\n      <span class=\"hljs-attr\">\"name\"<\/span>: <span class=\"hljs-string\">\"Get Count\"<\/span>,\n      <span class=\"hljs-attr\">\"request\"<\/span>: {\n        <span class=\"hljs-attr\">\"method\"<\/span>: <span class=\"hljs-string\">\"GET\"<\/span>,\n        <span class=\"hljs-attr\">\"header\"<\/span>: &#91;],\n        <span class=\"hljs-attr\">\"url\"<\/span>: {\n          <span class=\"hljs-attr\">\"raw\"<\/span>: <span class=\"hljs-string\">\"{{baseUrl}}\/api\/orders\/count\"<\/span>,\n          <span class=\"hljs-attr\">\"host\"<\/span>: &#91;<span class=\"hljs-string\">\"{{baseUrl}}\"<\/span>],\n          <span class=\"hljs-attr\">\"path\"<\/span>: &#91;<span class=\"hljs-string\">\"api\"<\/span>, <span class=\"hljs-string\">\"orders\"<\/span>, <span class=\"hljs-string\">\"count\"<\/span>]\n        }\n      }\n    }\n  ],\n  <span class=\"hljs-attr\">\"variable\"<\/span>: &#91;\n    {\n      <span class=\"hljs-attr\">\"key\"<\/span>: <span class=\"hljs-string\">\"baseUrl\"<\/span>,\n      <span class=\"hljs-attr\">\"value\"<\/span>: <span class=\"hljs-string\">\"http:\/\/localhost:5000\"<\/span>\n    }\n  ]\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p><strong>How to use:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Postman \u2192 Import \u2192 Select this JSON.<\/li>\n\n\n\n<li>Use <code>baseUrl = http:\/\/localhost:5000<\/code> or <code>https:\/\/localhost:5001<\/code>.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h2 class=\"wp-block-heading\">4\ufe0f\u20e3 k6 Load Test Script<\/h2>\n\n\n\n<p>Create <code>k6-orders-perf.js<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">import<\/span> http <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'k6\/http'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { check, sleep } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'k6'<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { Trend } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">'k6\/metrics'<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> naiveTrend = <span class=\"hljs-keyword\">new<\/span> Trend(<span class=\"hljs-string\">'naive_duration'<\/span>);\n<span class=\"hljs-keyword\">const<\/span> optTrend = <span class=\"hljs-keyword\">new<\/span> Trend(<span class=\"hljs-string\">'optimized_duration'<\/span>);\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> options = {\n  <span class=\"hljs-attr\">scenarios<\/span>: {\n    <span class=\"hljs-attr\">naive<\/span>: {\n      <span class=\"hljs-attr\">executor<\/span>: <span class=\"hljs-string\">'constant-vus'<\/span>,\n      <span class=\"hljs-attr\">vus<\/span>: <span class=\"hljs-number\">5<\/span>,\n      <span class=\"hljs-attr\">duration<\/span>: <span class=\"hljs-string\">'30s'<\/span>,\n      <span class=\"hljs-attr\">exec<\/span>: <span class=\"hljs-string\">'testNaive'<\/span>,\n    },\n    <span class=\"hljs-attr\">optimized<\/span>: {\n      <span class=\"hljs-attr\">executor<\/span>: <span class=\"hljs-string\">'constant-vus'<\/span>,\n      <span class=\"hljs-attr\">vus<\/span>: <span class=\"hljs-number\">5<\/span>,\n      <span class=\"hljs-attr\">duration<\/span>: <span class=\"hljs-string\">'30s'<\/span>,\n      <span class=\"hljs-attr\">exec<\/span>: <span class=\"hljs-string\">'testOptimized'<\/span>,\n      <span class=\"hljs-attr\">startTime<\/span>: <span class=\"hljs-string\">'35s'<\/span>\n    }\n  }\n};\n\n<span class=\"hljs-keyword\">const<\/span> BASE_URL = __ENV.BASE_URL || <span class=\"hljs-string\">'http:\/\/localhost:5000'<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">testNaive<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> res = http.post(<span class=\"hljs-string\">`<span class=\"hljs-subst\">${BASE_URL}<\/span>\/api\/orders\/bulk-naive?count=100`<\/span>, <span class=\"hljs-literal\">null<\/span>);\n  naiveTrend.add(res.timings.duration);\n  check(res, {\n    <span class=\"hljs-string\">'status is 200'<\/span>: <span class=\"hljs-function\"><span class=\"hljs-params\">r<\/span> =&gt;<\/span> r.status === <span class=\"hljs-number\">200<\/span>\n  });\n  sleep(<span class=\"hljs-number\">1<\/span>);\n}\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">testOptimized<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> res = http.post(<span class=\"hljs-string\">`<span class=\"hljs-subst\">${BASE_URL}<\/span>\/api\/orders\/bulk-optimized?count=100`<\/span>, <span class=\"hljs-literal\">null<\/span>);\n  optTrend.add(res.timings.duration);\n  check(res, {\n    <span class=\"hljs-string\">'status is 200'<\/span>: <span class=\"hljs-function\"><span class=\"hljs-params\">r<\/span> =&gt;<\/span> r.status === <span class=\"hljs-number\">200<\/span>\n  });\n  sleep(<span class=\"hljs-number\">1<\/span>);\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Run:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">k6 run k6-orders-perf.js\n<span class=\"hljs-comment\"># or explicitly<\/span>\nBASE_URL=http:<span class=\"hljs-comment\">\/\/localhost:5000 k6 run k6-orders-perf.js<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In the output, point at:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>naive_duration{}<\/code> summary vs <code>optimized_duration{}<\/code><\/li>\n\n\n\n<li><code>http_reqs<\/code>, <code>http_req_duration<\/code>, <code>http_req_failed<\/code><\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n\n\n\n<h2 class=\"wp-block-heading\">5\ufe0f\u20e3 JMeter Test Plan (Concept + Minimal XML)<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Concept for your training<\/h3>\n\n\n\n<p>Create <strong>2 Thread Groups<\/strong>:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><code>NaiveBulk<\/code>:\n<ul class=\"wp-block-list\">\n<li>10 threads (users)<\/li>\n\n\n\n<li>10 loops<\/li>\n\n\n\n<li>HTTP Request Sampler \u2192 <code>POST \/api\/orders\/bulk-naive?count=100<\/code><\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><code>OptimizedBulk<\/code>:\n<ul class=\"wp-block-list\">\n<li>10 threads<\/li>\n\n\n\n<li>10 loops<\/li>\n\n\n\n<li>HTTP Request Sampler \u2192 <code>POST \/api\/orders\/bulk-optimized?count=100<\/code><\/li>\n<\/ul>\n<\/li>\n<\/ol>\n\n\n\n<p>Add:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>View Results in Table<\/code><\/li>\n\n\n\n<li><code>Summary Report<\/code><\/li>\n\n\n\n<li><code>Aggregate Report<\/code><\/li>\n<\/ul>\n\n\n\n<p>Compare:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Avg response time<\/li>\n\n\n\n<li>90th\/95th percentile<\/li>\n\n\n\n<li>of samples<\/li>\n\n\n\n<li>Throughput<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Minimal <code>.jmx<\/code> skeleton (for reference)<\/h3>\n\n\n\n<p>I won\u2019t blow this up with full XML, but here is the <strong>key idea<\/strong> you can recreate in the JMeter GUI:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Test Plan\n<ul class=\"wp-block-list\">\n<li>HTTP Request Defaults: <code>Server Name or IP = localhost<\/code>, <code>Port = 5000<\/code><\/li>\n\n\n\n<li>Thread Group: <code>NaiveGroup<\/code>\n<ul class=\"wp-block-list\">\n<li>HTTP Sampler: <code>POST \/api\/orders\/bulk-naive<\/code> with param <code>count=100<\/code><\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Thread Group: <code>OptimizedGroup<\/code>\n<ul class=\"wp-block-list\">\n<li>HTTP Sampler: <code>POST \/api\/orders\/bulk-optimized<\/code> with param <code>count=100<\/code><\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Listeners: <code>Summary Report<\/code>, <code>Aggregate Report<\/code><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<p>You can save it as <code>PerfLabNaiveVsOptimized.jmx<\/code> and share with students.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\">\n","protected":false},"excerpt":{"rendered":"<p>It is constructed specifically to demonstrate real-world performance engineering concepts in .NET, using a scenario every enterprise system deals with: bulk inserts, EF Core behavior, SQL bottlenecks, GC pressure, and&#8230; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"_joinchat":[],"footnotes":""},"categories":[11138],"tags":[],"class_list":["post-54133","post","type-post","status-publish","format-standard","hentry","category-best-tools"],"_links":{"self":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/54133","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/comments?post=54133"}],"version-history":[{"count":6,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/54133\/revisions"}],"predecessor-version":[{"id":59881,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/54133\/revisions\/59881"}],"wp:attachment":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/media?parent=54133"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/categories?post=54133"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/tags?post=54133"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}