<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/scripts/pretty-feed-v3.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:h="http://www.w3.org/TR/html4/"><channel><title>Krishan Chawla</title><description>Technical Lead | Software Engineer | Automation Expert | Tech Enthusiast in the Telecom Industry</description><link>https://krishanchawla.com</link><item><title>Playwright vs. Selenium — Trade-offs at Expert Depth</title><link>https://krishanchawla.com/blog/playwright-vs-selenium-trade-offs-at-expert-depth</link><guid isPermaLink="true">https://krishanchawla.com/blog/playwright-vs-selenium-trade-offs-at-expert-depth</guid><description>An engineering-level comparison of Playwright and Selenium: architecture, flakiness, parallelism, CI cost, and when each one actually wins</description><pubDate>Thu, 02 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;&quot;Playwright vs. Selenium&quot; is usually framed as a popularity contest — newer, faster tool vs. the incumbent everyone already knows. That framing hides the actual decision, which is architectural: how each tool talks to the browser, what that buys you, and what it costs you in environments Selenium was never designed for and Playwright hasn&apos;t fully solved either.&lt;/p&gt;
&lt;p&gt;This isn&apos;t a &quot;just use Playwright&quot; post. Both tools are still correct choices depending on what you&apos;re testing, what you already have in CI, and how much control you need over the browser layer.&lt;/p&gt;
&lt;p&gt;At a glance, before the detail:&lt;/p&gt;
&lt;p&gt;| Category | Playwright | Selenium |
|---|---|---|
| Protocol | CDP / WebDriver BiDi | W3C WebDriver |
| Synchronization | Auto-waiting, built in | Explicit/implicit waits you manage |
| Cross-browser | Chromium, Firefox, WebKit (bundled builds) | Virtually every real installed browser |
| Mobile | Emulation only, no native apps | Real device/native coverage via Appium |
| Parallelism | Native (workers, contexts, sharding) | Framework-dependent (Grid, TestNG, Docker) |
| Network interception | Native (&lt;code&gt;page.route()&lt;/code&gt;) | Requires CDP or a proxy like BrowserMob |
| API testing | Built in (&lt;code&gt;APIRequestContext&lt;/code&gt;) | Needs a separate client (REST Assured, etc.) |
| Debugging artifacts | Trace, video, screenshots built in | Needs third-party tooling |
| Ecosystem maturity | Growing fast | Extremely mature, 15+ years |&lt;/p&gt;
&lt;p&gt;The rest of this post is why each row is the way it is, and where the table oversimplifies.&lt;/p&gt;
&lt;h2&gt;Architecture: the actual difference&lt;/h2&gt;
&lt;h3&gt;Selenium — WebDriver, out-of-process&lt;/h3&gt;
&lt;p&gt;Selenium talks to the browser through the &lt;strong&gt;W3C WebDriver protocol&lt;/strong&gt;: your test issues an HTTP request to a driver process (chromedriver, geckodriver, msedgedriver), which translates it into native browser automation calls.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Test code → WebDriver client → HTTP (JSON Wire / W3C) → Driver binary → Browser
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every action — click, type, navigate — is a network round trip through an external process. This is &lt;em&gt;why&lt;/em&gt; Selenium is slower and why &quot;flaky&quot; has historically been associated with it: there&apos;s no shared execution context between your test and the browser, so Selenium has no native way to know when the DOM has actually settled after an action. That&apos;s what &lt;code&gt;WebDriverWait&lt;/code&gt; and &lt;code&gt;ExpectedConditions&lt;/code&gt; exist to paper over.&lt;/p&gt;
&lt;h3&gt;Playwright — CDP/BiDi, in-process control&lt;/h3&gt;
&lt;p&gt;Playwright talks to Chromium and WebKit browsers directly over the &lt;strong&gt;Chrome DevTools Protocol&lt;/strong&gt; (and increasingly WebDriver BiDi for Firefox), using a persistent WebSocket connection from a Node/Python/Java/.NET process it manages itself — no separate driver binary to version-match.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Test code → Playwright driver process → WebSocket (CDP/BiDi) → Browser
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because Playwright owns the browser process lifecycle and has a live protocol connection, it can subscribe to actual browser events — network idle, DOM mutations, frame navigation — instead of polling. This is the real source of Playwright&apos;s &quot;less flaky&quot; reputation: &lt;strong&gt;auto-waiting is a byproduct of architecture, not a feature someone bolted on.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Auto-waiting vs. explicit waiting&lt;/h2&gt;
&lt;p&gt;This is the single biggest day-to-day productivity difference.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Selenium&lt;/strong&gt; requires you to explicitly wait for actionability:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;WebElement button = new WebDriverWait(driver, Duration.ofSeconds(10))
    .until(ExpectedConditions.elementToBeClickable(By.id(&quot;submit&quot;)));
button.click();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Skip this and you get intermittent &lt;code&gt;ElementClickInterceptedException&lt;/code&gt; or &lt;code&gt;StaleElementReferenceException&lt;/code&gt; — not because your test is wrong, but because Selenium clicked before the element was interactable (covered by an overlay, animating, or detached and re-rendered by a framework).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Playwright&lt;/strong&gt; bakes actionability checks into every action — visible, stable (not animating), receives events (not obscured), enabled — before it acts:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;await page.getByRole(&apos;button&apos;, { name: &apos;Submit&apos; }).click();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the element isn&apos;t actionable within the timeout, Playwright throws a &lt;em&gt;specific&lt;/em&gt; error telling you which check failed, not a generic &quot;element not found.&quot; That diagnostic quality alone cuts triage time significantly on a large suite.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Where this cuts the other way:&lt;/strong&gt; auto-waiting can mask genuine race conditions in your app. A button that becomes clickable a beat before its handler is actually wired up will pass Playwright&apos;s actionability check and still fail intermittently — the failure just moves from &quot;test infra&quot; to &quot;app timing bug,&quot; which is more honest but not free.&lt;/p&gt;
&lt;h2&gt;Locator strategy&lt;/h2&gt;
&lt;p&gt;Selenium&apos;s &lt;code&gt;By&lt;/code&gt; locators (&lt;code&gt;id&lt;/code&gt;, &lt;code&gt;cssSelector&lt;/code&gt;, &lt;code&gt;xpath&lt;/code&gt;) return a reference to whatever matched &lt;em&gt;at that instant&lt;/em&gt;. If the DOM re-renders (React/Vue re-mount), that reference goes stale and you eat a &lt;code&gt;StaleElementReferenceException&lt;/code&gt; on the next interaction.&lt;/p&gt;
&lt;p&gt;Playwright&apos;s &lt;code&gt;Locator&lt;/code&gt; is lazy — it re-resolves the query on every action, so it survives re-renders by design:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;const row = page.getByRole(&apos;row&apos;, { name: &apos;jane@example.com&apos; });
await row.getByRole(&apos;button&apos;, { name: &apos;Delete&apos; }).click();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Playwright also pushes role/text-based locators (&lt;code&gt;getByRole&lt;/code&gt;, &lt;code&gt;getByLabel&lt;/code&gt;, &lt;code&gt;getByTestId&lt;/code&gt;) as the default idiom, which happens to align with accessible markup — a genuine secondary benefit, not just API sugar.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Shadow DOM&lt;/strong&gt; is the other place this matters in practice. Playwright pierces open shadow roots automatically — &lt;code&gt;page.locator(&apos;my-component &gt;&gt; text=Save&apos;)&lt;/code&gt; just works. Selenium requires you to explicitly traverse into &lt;code&gt;shadowRoot&lt;/code&gt; (and behavior varies by driver/browser version), which is a recurring source of custom helper methods in mature Selenium frameworks.&lt;/p&gt;
&lt;h2&gt;Network interception, mocking, and API testing&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Selenium&lt;/strong&gt; has no native network layer. Selenium 4 exposes some Chrome DevTools Protocol access, but intercepting and rewriting requests still typically means bolting on a proxy like BrowserMob, or dropping to raw CDP calls that aren&apos;t part of the stable WebDriver API. API testing is a separate concern entirely — you bring in REST Assured (Java), &lt;code&gt;requests&lt;/code&gt; (Python), or similar, wired up alongside your UI framework rather than inside it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Playwright&lt;/strong&gt; treats both as first-class:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// Mock a flaky third-party dependency (payment gateway, OTP provider) directly in the test
await page.route(&apos;**/api/payment/authorize&apos;, (route) =&gt;
  route.fulfill({ status: 200, body: JSON.stringify({ status: &apos;approved&apos; }) })
);

// Drive API calls in the same test, no separate HTTP client
const api = await request.newContext();
const response = await api.post(&apos;/api/orders&apos;, { data: { sku: &apos;ABC-123&apos;, qty: 1 } });
expect(response.status()).toBe(201);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That means you can seed state via API, then assert through the UI, in one test file with one runner — no context-switching between an HTTP client library and your browser automation stack. For flows gated behind a third-party dependency (payment, OTP, SSO), &lt;code&gt;page.route()&lt;/code&gt; mocking is often the only practical way to test the failure paths deterministically.&lt;/p&gt;
&lt;h2&gt;Authentication state reuse&lt;/h2&gt;
&lt;p&gt;Logging in through the UI once per test is one of the biggest sources of wasted CI time in both tools, but Playwright makes the fix a one-liner. Authenticate once, persist the session, and reuse it across every test that doesn&apos;t specifically need to test login itself:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// global-setup.ts — runs once
const page = await browser.newPage();
await page.goto(&apos;/login&apos;);
await page.getByLabel(&apos;Email&apos;).fill(&apos;user@example.com&apos;);
await page.getByLabel(&apos;Password&apos;).fill(&apos;••••••••&apos;);
await page.getByRole(&apos;button&apos;, { name: &apos;Sign in&apos; }).click();
await page.context().storageState({ path: &apos;auth.json&apos; });
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// playwright.config.ts
use: { storageState: &apos;auth.json&apos; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every subsequent test starts already authenticated — no repeated login flow eating seconds per test across a suite that might run thousands of times a month. Selenium has no equivalent primitive; teams typically replicate this by injecting cookies or local storage manually after driver init, which works but isn&apos;t built in and isn&apos;t as robust across auth schemes (e.g. anything involving &lt;code&gt;httpOnly&lt;/code&gt; or &lt;code&gt;SameSite&lt;/code&gt; cookie nuances).&lt;/p&gt;
&lt;h2&gt;Parallelism and execution model&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Selenium Grid&lt;/strong&gt; (v4) distributes across nodes, but each session is a full browser instance with its own driver process — heavy to spin up, and coordination overhead (hub/router, session queuing) grows with grid size. It&apos;s proven at scale, but the ops burden is real: someone owns the Grid infrastructure.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Playwright&lt;/strong&gt; ships parallelism as a first-class runner feature (&lt;code&gt;playwright.config.ts&lt;/code&gt; workers), and browser &lt;em&gt;contexts&lt;/em&gt; are cheap — isolated, cookie/storage-separated sessions within a single browser process launch. You get Selenium-Grid-like isolation without spinning up a new browser per test:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;export default defineConfig({
  workers: process.env.CI ? 4 : undefined,
  fullyParallel: true,
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For a suite of a few hundred E2E tests, Playwright&apos;s context-based parallelism is materially cheaper in CI compute than an equivalent Selenium Grid setup — fewer browser process cold-starts per test.&lt;/p&gt;
&lt;p&gt;The same context model simplifies &lt;strong&gt;multi-tab and popup flows&lt;/strong&gt; — OAuth redirects, &quot;open in new tab&quot; links, payment provider popups — which are a genuine pain point in Selenium:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// Selenium: manual window-handle juggling
const originalWindow = await driver.getWindowHandle();
const windows = await driver.getAllWindowHandles();
for (const handle of windows) {
  if (handle !== originalWindow) await driver.switchTo().window(handle);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// Playwright: the new page is just an object
const [popup] = await Promise.all([
  page.waitForEvent(&apos;popup&apos;),
  page.getByRole(&apos;link&apos;, { name: &apos;Pay with PayPal&apos; }).click(),
]);
await popup.getByLabel(&apos;Email&apos;).fill(&apos;user@example.com&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Cross-browser and real-device coverage&lt;/h2&gt;
&lt;p&gt;This is where Selenium still wins outright.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Selenium&lt;/strong&gt; drives &lt;em&gt;real&lt;/em&gt; installed browsers via vendor-maintained drivers — actual Chrome, actual Firefox, actual Safari, actual legacy Edge if you&apos;re unlucky enough to need it. It&apos;s the only realistic path to &lt;strong&gt;real mobile browser testing&lt;/strong&gt; through Appium (which reuses the WebDriver protocol) or cloud grids like BrowserStack/Sauce Labs for genuine device/OS/browser matrices.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Playwright&lt;/strong&gt; ships its own bundled builds of Chromium, Firefox, and WebKit. &quot;WebKit&quot; here is &lt;em&gt;not&lt;/em&gt; Safari — it&apos;s a Playwright-patched WebKit build, which is a very good proxy for Safari behavior but has occasionally diverged from real Safari edge cases (especially around media APIs, permissions prompts, and some CSS rendering). If your product has a Safari-specific bug class (and most do, eventually), you cannot fully rule it out with Playwright&apos;s WebKit alone.&lt;/li&gt;
&lt;li&gt;Playwright has no first-party mobile device automation — &lt;code&gt;page.emulate&lt;/code&gt; gives you viewport/UA emulation of a desktop browser pretending to be mobile, not a real mobile browser engine.&lt;/li&gt;
&lt;li&gt;Selenium is also the only option for legacy corporate environments still running Internet Explorer or older Edge builds via vendor drivers. Playwright never supported them and never will — it targets evergreen browsers only.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt; if your test matrix must include &quot;real Safari on real macOS&quot; or &quot;real Chrome on a real Android device,&quot; you still need Selenium/Appium or a cloud grid in the loop, full stop — Playwright is not a substitute there.&lt;/p&gt;
&lt;h2&gt;Debuggability&lt;/h2&gt;
&lt;p&gt;This isn&apos;t close. Playwright&apos;s tooling — Trace Viewer (full DOM snapshots, network, console, and a timeline scrubber per test run), the Inspector, &lt;code&gt;--debug&lt;/code&gt; step mode, and codegen — gives you a post-mortem of a CI failure that looks like a time-travel debugger. Selenium has nothing built-in comparable; you&apos;re left instrumenting screenshots-on-failure and video capture yourself (or paying for a cloud grid that bolts this on).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx playwright test --trace on
npx playwright show-trace trace.zip
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For a team fighting flaky-test triage time, Trace Viewer alone can be the deciding factor, independent of everything else in this comparison.&lt;/p&gt;
&lt;h2&gt;Screenshots, video, and reporting&lt;/h2&gt;
&lt;p&gt;Playwright bundles &lt;code&gt;page.screenshot()&lt;/code&gt; and &lt;code&gt;locator.screenshot()&lt;/code&gt; with built-in masking and clipping, plus &lt;code&gt;recordVideo&lt;/code&gt; on the browser context — no extra dependency. Getting the same artifacts out of Selenium means wiring up FFmpeg, a Grid video-recording sidecar, or a paid cloud grid that adds it for you.&lt;/p&gt;
&lt;p&gt;Reporting follows the same pattern. Playwright&apos;s HTML reporter ships with traces, videos, and screenshots attached out of the box; you can still layer &lt;strong&gt;Allure&lt;/strong&gt; on top if that&apos;s your org&apos;s standard. Selenium has no built-in report format — &lt;code&gt;ExtentReports&lt;/code&gt;, &lt;code&gt;Allure&lt;/code&gt;, and &lt;code&gt;ReportPortal&lt;/code&gt; are the de facto standard, but they&apos;re all separate dependencies you own the versioning and configuration for. Neither gap is fatal, but it&apos;s real setup and maintenance surface Playwright starts you without.&lt;/p&gt;
&lt;h2&gt;Language and ecosystem maturity&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Selenium&lt;/strong&gt;: bindings in Java, Python, C#, Ruby, JavaScript, Kotlin — genuinely first-class in each, backed by 15+ years of Stack Overflow answers, Page Object frameworks (Serenity, Selenide), and enterprise QA tooling that assumes WebDriver underneath (most commercial test-management and visual-regression tools integrate with Selenium by default).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Playwright&lt;/strong&gt;: first-class in TypeScript/JavaScript, Python, .NET, and Java — but the JS/TS experience is where the tool is designed and battle-tested first; other bindings sometimes lag on API parity for newly released features. If your org is Java-first with a large existing Selenium/TestNG investment, migrating fully is a bigger lift than the marketing suggests.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;When to actually choose which&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Choose Playwright when:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You&apos;re starting a new E2E suite and the target is modern web apps (React/Vue/Angular) in Chromium/Firefox/WebKit-class browsers&lt;/li&gt;
&lt;li&gt;CI cost and flakiness triage time are real pain points&lt;/li&gt;
&lt;li&gt;Your team is JS/TS-native or Python-native&lt;/li&gt;
&lt;li&gt;You want network interception/mocking and API testing as native features (&lt;code&gt;page.route()&lt;/code&gt;, &lt;code&gt;APIRequestContext&lt;/code&gt;), not a Selenium-plus-BrowserMob-plus-REST-Assured bolt-on&lt;/li&gt;
&lt;li&gt;Login flows dominate your suite&apos;s runtime and &lt;code&gt;storageState&lt;/code&gt; reuse would meaningfully cut CI time&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Choose Selenium when:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You need real Safari, real mobile browsers, or a specific legacy browser (including IE/old Edge) in the matrix&lt;/li&gt;
&lt;li&gt;You already have a mature Selenium Grid + Page Object investment and the migration cost outweighs the flakiness savings&lt;/li&gt;
&lt;li&gt;Your org&apos;s commercial test-management/visual-testing tooling is WebDriver-native&lt;/li&gt;
&lt;li&gt;You&apos;re testing through Appium for native + hybrid mobile apps, where WebDriver is the shared protocol&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The honest framing: Playwright&apos;s architecture eliminates an entire category of flakiness that Selenium engineers have spent a decade building workarounds for, and its debugging tooling is a genuine step change. But Selenium&apos;s WebDriver protocol is still the only standard that gets you real cross-browser and real-device coverage, and that&apos;s not a gap Playwright&apos;s bundled-browser model closes. Pick based on what&apos;s actually in your browser matrix, not which tool has the better changelog.&lt;/p&gt;
&lt;h2&gt;Key Takeaways&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Selenium drives real browsers out-of-process via WebDriver; Playwright drives its own bundled browser builds in-process via CDP/BiDi — this single difference explains almost every other trade-off&lt;/li&gt;
&lt;li&gt;Playwright&apos;s auto-waiting is an architectural byproduct, not a convenience feature, and materially reduces flaky-test triage time&lt;/li&gt;
&lt;li&gt;Selenium remains the only realistic choice for real Safari and real mobile-device coverage; Playwright&apos;s WebKit is a very good but imperfect Safari proxy&lt;/li&gt;
&lt;li&gt;Playwright&apos;s Trace Viewer has no real Selenium equivalent and is often the deciding factor on its own&lt;/li&gt;
&lt;li&gt;Native API testing (&lt;code&gt;APIRequestContext&lt;/code&gt;) and &lt;code&gt;storageState&lt;/code&gt; auth reuse let Playwright seed state and skip repeated logins without a separate HTTP client — a real CI-time win at scale&lt;/li&gt;
&lt;li&gt;Don&apos;t migrate a mature Selenium Grid investment to Playwright purely for speed — check your browser/device matrix requirements first&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="/_astro/hero-banner.DhrCuqts.png"/><enclosure url="/_astro/hero-banner.DhrCuqts.png"/></item><item><title>K6 Performance Testing for APIs – Quick Start Guide</title><link>https://krishanchawla.com/blog/setup-api-performance-testing-using-k6-performance-tool</link><guid isPermaLink="true">https://krishanchawla.com/blog/setup-api-performance-testing-using-k6-performance-tool</guid><description>Learn how to install, configure, and run K6 performance tests on REST APIs</description><pubDate>Tue, 16 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Performance testing is no longer optional for modern APIs. Even a perfectly functional API can fail under load if performance is not validated early.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;K6&lt;/strong&gt; is a modern, open-source performance testing tool designed for developers, QA engineers, and DevOps teams. It allows you to write readable performance tests using JavaScript and run them locally or in CI/CD pipelines.&lt;/p&gt;
&lt;h3&gt;What is K6 and When Should You Use It&lt;/h3&gt;
&lt;p&gt;K6 is best suited for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;REST and GraphQL API performance testing&lt;/li&gt;
&lt;li&gt;Load testing microservices&lt;/li&gt;
&lt;li&gt;Stress and spike testing&lt;/li&gt;
&lt;li&gt;CI/CD performance gates&lt;/li&gt;
&lt;li&gt;Pre-production validation&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Core Performance Testing Concepts&lt;/h3&gt;
&lt;p&gt;| Term |    Meaning |
|-----|--------|
| Virtual User (VU) |   Simulated user executing your test |
| Iteration |   One full execution of the test function |
| Load Test |   Expected production traffic |
| Stress Test |     Gradually increasing load to find limits |
| Spike Test |  Sudden traffic increase |
| Threshold |   SLA or pass/fail condition |&lt;/p&gt;
&lt;h3&gt;Step 1: Install k6&lt;/h3&gt;
&lt;h4&gt;macOS&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;brew install k6
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Windows&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;choco install k6
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Linux (Debian / Ubuntu)&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo apt update
sudo apt install k6
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Docker (CI/CD Friendly)&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker pull grafana/k6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Verify installation:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;k6 version
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Step 3: Your First K6 API Test&lt;/h3&gt;
&lt;p&gt;Create &lt;code&gt;api-test.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import http from &apos;k6/http&apos;
import { check, sleep } from &apos;k6&apos;

export const options = {
  vus: 10,
  duration: &apos;30s&apos;,
}

export default function () {
  const response = http.get(&apos;https://jsonplaceholder.typicode.com/posts&apos;)

  check(response, {
    &apos;status is 200&apos;: (r) =&gt; r.status === 200,
    &apos;response time &amp;#x3C; 500ms&apos;: (r) =&gt; r.timings.duration &amp;#x3C; 500,
  })

  sleep(1)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;What this test does&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Runs 10 virtual users&lt;/li&gt;
&lt;li&gt;Executes for 30 seconds&lt;/li&gt;
&lt;li&gt;Sends HTTP GET requests&lt;/li&gt;
&lt;li&gt;Validates response status and performance&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Step 4: Running the Test&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;k6 run tests/api-test.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;K6 will immediately start printing live metrics to the terminal.&lt;/p&gt;
&lt;h3&gt;Step 5: Understanding K6 Output&lt;/h3&gt;
&lt;p&gt;Key Metrics to Watch&lt;/p&gt;
&lt;p&gt;| Metric | What It Tells You |
|------|------------------|
| &lt;strong&gt;http_req_duration&lt;/strong&gt; | End-to-end request latency |
| &lt;strong&gt;http_req_failed&lt;/strong&gt; | Percentage of failed requests |
| &lt;strong&gt;vus&lt;/strong&gt; | Number of active virtual users |
| &lt;strong&gt;iterations&lt;/strong&gt; | Total test executions |
| &lt;strong&gt;checks&lt;/strong&gt; | Assertion success rate |&lt;/p&gt;
&lt;p&gt;Focus First On&lt;/p&gt;
&lt;h3&gt;Load Testing Example&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;export const options = {
  vus: 50,
  duration: &apos;2m&apos;,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use this to simulate &lt;strong&gt;normal expected traffic&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;Stress Testing Example&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;export const options = {
  stages: [
    { duration: &apos;1m&apos;, target: 20 },
    { duration: &apos;1m&apos;, target: 50 },
    { duration: &apos;1m&apos;, target: 100 },
    { duration: &apos;1m&apos;, target: 0 },
  ],
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use this to identify:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Performance degradation&lt;/li&gt;
&lt;li&gt;Breaking points&lt;/li&gt;
&lt;li&gt;Resource bottlenecks&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;API Testing with Headers and Auth&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const params = {
  headers: {
    Authorization: &apos;Bearer YOUR_TOKEN&apos;,
    &apos;Content-Type&apos;: &apos;application/json&apos;,
  },
}

http.get(&apos;https://api.example.com/users&apos;, params)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Step 6: Adding Thresholds (SLAs)&lt;/h3&gt;
&lt;p&gt;Thresholds fail the test when SLAs are violated.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;export const options = {
  thresholds: {
    http_req_duration: [&apos;p(95)&amp;#x3C;500&apos;],
    http_req_failed: [&apos;rate&amp;#x3C;0.01&apos;],
  },
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Perfect for CI/CD pipelines.&lt;/p&gt;
&lt;h3&gt;CI/CD Usage (Quick Example)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;k6 run tests/api-test.js --summary-export=summary.json
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pipeline can fail if thresholds are breached.&lt;/p&gt;
&lt;h3&gt;Best Practices&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Start small, scale gradually&lt;/li&gt;
&lt;li&gt;Keep tests deterministic&lt;/li&gt;
&lt;li&gt;Version control test scripts&lt;/li&gt;
&lt;li&gt;Avoid testing from unstable networks&lt;/li&gt;
&lt;li&gt;Always define thresholds&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;When NOT to Use K6&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;UI/browser testing&lt;/li&gt;
&lt;li&gt;Visual performance testing&lt;/li&gt;
&lt;li&gt;End-to-end user journey validation&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;K6 enables teams to shift performance testing left and catch issues early.&lt;br&gt;
With minimal setup and readable scripts, it becomes a powerful part of your DevTools ecosystem.&lt;/p&gt;
&lt;h3&gt;Key Takeaways&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;K6 is lightweight and developer-friendly&lt;/li&gt;
&lt;li&gt;Ideal for API and microservice testing&lt;/li&gt;
&lt;li&gt;Easy CI/CD integration&lt;/li&gt;
&lt;li&gt;Clear metrics and strong automation support&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="/_astro/hero-banner.D0ytrhL-.png"/><enclosure url="/_astro/hero-banner.D0ytrhL-.png"/></item><item><title>How to Install and Configure NGINX Server on CentOS</title><link>https://krishanchawla.com/blog/install-and-configure-nginx-on-centos</link><guid isPermaLink="true">https://krishanchawla.com/blog/install-and-configure-nginx-on-centos</guid><description>A simple and clear step-by-step tutorial on installing NGINX on CentOS, configuring your first website, enabling firewall rules, and verifying everything works.</description><pubDate>Tue, 09 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;🚀 How to Install and Configure NGINX on CentOS&lt;/h2&gt;
&lt;p&gt;NGINX is one of the most popular web servers in the world. It&apos;s fast, lightweight, and easy to configure — perfect for hosting websites, APIs, or static content.&lt;/p&gt;
&lt;p&gt;In this guide, you’ll learn &lt;strong&gt;how to install NGINX on CentOS&lt;/strong&gt;, start the service, configure a basic website, and verify the installation.&lt;/p&gt;
&lt;h3&gt;Step 1: Update Your System&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo yum update -y
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Step 2: Install NGINX&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo yum install nginx -y
sudo systemctl start nginx
sudo systemctl enable nginx
sudo systemctl status nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Step 3: Allow NGINX in the Firewall&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Step 4: Verify NGINX is Running&lt;/h3&gt;
&lt;p&gt;Visit:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;http://&amp;#x3C;your-server-ip&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should see the NGINX welcome page.&lt;/p&gt;
&lt;h3&gt;Step 5: Hosting Your First Web Page&lt;/h3&gt;
&lt;p&gt;Default NGINX web root:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/usr/share/nginx/html
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Edit:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo nano /usr/share/nginx/html/index.html
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;#x3C;h1&gt;Hello from NGINX on CentOS!&amp;#x3C;/h1&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Step 6: Basic Custom NGINX Configuration&lt;/h3&gt;
&lt;p&gt;Create:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo nano /etc/nginx/conf.d/mywebsite.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nginx&quot;&gt;server {
    listen 80;
    server_name example.com;
    root /var/www/mywebsite;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reload:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo nginx -t
sudo systemctl reload nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;🎉 Done!&lt;/p&gt;
&lt;p&gt;You&apos;ve successfully installed and configured NGINX on CentOS.&lt;/p&gt;</content:encoded><h:img src="/_astro/hero-banner.D9wvGpoL.png"/><enclosure url="/_astro/hero-banner.D9wvGpoL.png"/></item><item><title>MySQL Master-Slave Replication for Data Redundancy</title><link>https://krishanchawla.com/blog/mysql-master-slave-replication</link><guid isPermaLink="true">https://krishanchawla.com/blog/mysql-master-slave-replication</guid><description>Learn how to set up MySQL master-slave replication to ensure data redundancy and high availability in production environments.</description><pubDate>Sat, 22 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;In production environments, data redundancy and high availability are critical requirements. MySQL master-slave replication is a proven solution that ensures your data is safely replicated across multiple servers.&lt;/p&gt;
&lt;p&gt;This guide will walk you through setting up MySQL master-slave replication step by step.&lt;/p&gt;
&lt;h2&gt;What is MySQL Replication?&lt;/h2&gt;
&lt;p&gt;MySQL replication enables data from one MySQL database server (the master) to be copied to one or more MySQL database servers (the slaves). Replication is asynchronous by default; slaves do not need to be connected permanently to receive updates from the master.&lt;/p&gt;
&lt;h3&gt;Benefits of Replication&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Data Redundancy&lt;/strong&gt;: Keep multiple copies of your data&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Load Distribution&lt;/strong&gt;: Distribute read queries across multiple slaves&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Backup Solution&lt;/strong&gt;: Use slaves for backups without locking the master&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Disaster Recovery&lt;/strong&gt;: Quick failover to slave in case of master failure&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;Before we begin, make sure you have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MySQL 8.0 or higher installed on both servers&lt;/li&gt;
&lt;li&gt;Root access to both servers&lt;/li&gt;
&lt;li&gt;Network connectivity between servers&lt;/li&gt;
&lt;li&gt;Basic understanding of MySQL&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Step 1: Configure Master Server&lt;/h2&gt;
&lt;p&gt;First, edit the MySQL configuration file on the master server:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add the following lines:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;[mysqld]
server-id = 1
log_bin = /var/log/mysql/mysql-bin.log
binlog_do_db = production_db
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Important Configuration Parameters:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;server-id&lt;/code&gt;: Unique identifier for the server (must be unique across all servers)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;log_bin&lt;/code&gt;: Enable binary logging&lt;/li&gt;
&lt;li&gt;&lt;code&gt;binlog_do_db&lt;/code&gt;: Specify which database to replicate&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Restart MySQL after making changes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo systemctl restart mysql
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Step 2: Create Replication User&lt;/h2&gt;
&lt;p&gt;On the master server, create a dedicated replication user:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE USER &apos;replica&apos;@&apos;%&apos; IDENTIFIED BY &apos;strong_password_here&apos;;
GRANT REPLICATION SLAVE ON *.* TO &apos;replica&apos;@&apos;%&apos;;
FLUSH PRIVILEGES;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Step 3: Get Master Status&lt;/h2&gt;
&lt;p&gt;Lock the database and note the master status:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;FLUSH TABLES WITH READ LOCK;
SHOW MASTER STATUS;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You&apos;ll see output like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;+------------------+----------+--------------+------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+------------------+----------+--------------+------------------+
| mysql-bin.000001 | 154      | production_db|                  |
+------------------+----------+--------------+------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;: Note the File and Position values - you&apos;ll need them for slave configuration.&lt;/p&gt;
&lt;h2&gt;Step 4: Export Data from Master&lt;/h2&gt;
&lt;p&gt;If you have existing data, export it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mysqldump -u root -p --databases production_db &gt; master_backup.sql
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After the export completes, unlock the tables:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;UNLOCK TABLES;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Step 5: Configure Slave Server&lt;/h2&gt;
&lt;p&gt;Edit the MySQL configuration on the slave server:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add these lines:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;[mysqld]
server-id = 2
relay-log = /var/log/mysql/mysql-relay-bin.log
log_bin = /var/log/mysql/mysql-bin.log
binlog_do_db = production_db
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Restart MySQL:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo systemctl restart mysql
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Step 6: Import Data to Slave&lt;/h2&gt;
&lt;p&gt;If you exported data from master:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mysql -u root -p &amp;#x3C; master_backup.sql
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Step 7: Start Replication&lt;/h2&gt;
&lt;p&gt;On the slave server, configure the master connection:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CHANGE MASTER TO
  MASTER_HOST=&apos;master_ip_address&apos;,
  MASTER_USER=&apos;replica&apos;,
  MASTER_PASSWORD=&apos;strong_password_here&apos;,
  MASTER_LOG_FILE=&apos;mysql-bin.000001&apos;,
  MASTER_LOG_POS=154;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Start the slave:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;START SLAVE;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Step 8: Verify Replication&lt;/h2&gt;
&lt;p&gt;Check the slave status:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SHOW SLAVE STATUS\G
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Look for these indicators of successful replication:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Slave_IO_Running: Yes&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Slave_SQL_Running: Yes&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Seconds_Behind_Master: 0&lt;/code&gt; (or a small number)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Testing Replication&lt;/h2&gt;
&lt;p&gt;Create a test table on the master:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;USE production_db;
CREATE TABLE test_replication (
    id INT PRIMARY KEY,
    message VARCHAR(100)
);

INSERT INTO test_replication VALUES (1, &apos;Replication is working!&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check the slave:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;USE production_db;
SELECT * FROM test_replication;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you see the data, congratulations! Replication is working.&lt;/p&gt;
&lt;h2&gt;Monitoring and Maintenance&lt;/h2&gt;
&lt;h3&gt;Key Metrics to Monitor&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Replication Lag&lt;/strong&gt;: &lt;code&gt;Seconds_Behind_Master&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Slave Status&lt;/strong&gt;: Both IO and SQL threads should be running&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Binary Log Size&lt;/strong&gt;: Monitor disk space usage&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Connection Status&lt;/strong&gt;: Ensure slave can reach master&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Common Issues and Solutions&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Issue: Slave_IO_Running: No&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Check network connectivity&lt;/li&gt;
&lt;li&gt;Verify replication user credentials&lt;/li&gt;
&lt;li&gt;Check firewall rules&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Issue: Replication Lag&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Check slave server resources (CPU, memory, disk I/O)&lt;/li&gt;
&lt;li&gt;Consider using semi-synchronous replication&lt;/li&gt;
&lt;li&gt;Review long-running queries&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Best Practices&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Use Dedicated Replication User&lt;/strong&gt;: Don&apos;t use root for replication&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Monitor Regularly&lt;/strong&gt;: Set up alerts for replication issues&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Test Failover&lt;/strong&gt;: Regularly test your failover procedures&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Secure Connections&lt;/strong&gt;: Use SSL for replication traffic&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Backup Binary Logs&lt;/strong&gt;: Keep backups of binary logs&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Document Configuration&lt;/strong&gt;: Maintain documentation of your setup&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;You now have a working MySQL master-slave replication setup! This configuration provides data redundancy and can help with read load distribution.&lt;/p&gt;
&lt;h3&gt;Next Steps&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Set up monitoring using tools like MySQL Workbench or Prometheus&lt;/li&gt;
&lt;li&gt;Implement automatic failover with MySQL Router or ProxySQL&lt;/li&gt;
&lt;li&gt;Consider setting up semi-synchronous replication for better durability&lt;/li&gt;
&lt;li&gt;Explore multi-source replication for complex scenarios&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Additional Resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/replication.html&quot;&gt;MySQL Replication Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.mysql.com/products/enterprise/high_availability.html&quot;&gt;MySQL High Availability Solutions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.percona.com/software/mysql-database/percona-xtradb-cluster&quot;&gt;Percona XtraDB Cluster&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Happy replicating! 🚀&lt;/p&gt;</content:encoded><h:img src="/_astro/hero-banner.CyNu2jC_.png"/><enclosure url="/_astro/hero-banner.CyNu2jC_.png"/></item><item><title>Java Spring Boot Docker Deployment with Oracle Made Simple</title><link>https://krishanchawla.com/blog/java-spring-boot-docker-deployment-with-oracle-made-simple</link><guid isPermaLink="true">https://krishanchawla.com/blog/java-spring-boot-docker-deployment-with-oracle-made-simple</guid><description>Learn how to setup docker deployment of your Java Spring Boot application including Oracle Database dependency easily.</description><pubDate>Wed, 19 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;In today’s DevOps-driven world, containerization has become a crucial aspect of deploying applications efficiently. Docker enables developers to package applications along with their dependencies, ensuring seamless deployment across different environments.&lt;/p&gt;
&lt;p&gt;In this blog, we will walk through the step-by-step process of deploying a Java Spring Boot application on Docker with Oracle Database, including how to tackle the challenge of downloading the OJDBC driver, which is not available through Maven.&lt;/p&gt;
&lt;h2&gt;Why Use Docker for Java Spring Boot Applications?&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Consistency:&lt;/strong&gt; Ensures the application runs the same way across different environments.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scalability:&lt;/strong&gt; Containers can be easily scaled using tools like Kubernetes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Isolation:&lt;/strong&gt; Each application runs in an isolated environment, avoiding dependency conflicts.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Efficiency:&lt;/strong&gt; Docker containers are lightweight and resource-efficient compared to VMs.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Prerequisites:&lt;/h2&gt;
&lt;p&gt;Before getting started, ensure you have the following installed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Java JDK 17+&lt;/li&gt;
&lt;li&gt;Spring Boot Application (Maven/Gradle)&lt;/li&gt;
&lt;li&gt;Docker (Installed and running)&lt;/li&gt;
&lt;li&gt;Oracle Database (Running in Docker or accessible remotely)&lt;/li&gt;
&lt;li&gt;OJDBC Jar (Manually downloaded from &lt;a href=&quot;https://www.oracle.com/database/technologies/appdev/jdbc-downloads.html&quot;&gt;Oracle website&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Basic knowledge of Docker and Spring Boot&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Step 1: Create a Simple Spring Boot Application&lt;/h2&gt;
&lt;p&gt;If you already have a Spring Boot project, skip this step. Otherwise, create a new one using &lt;em&gt;Spring Initializr&lt;/em&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl https://start.spring.io/starter.zip -d dependencies=web -o demo.zip
unzip demo.zip -d demo
cd demo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ensure your &lt;em&gt;pom.xml&lt;/em&gt; has the following dependencies:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;#x3C;dependency&gt;
	&amp;#x3C;groupId&gt;org.springframework.boot&amp;#x3C;/groupId&gt;
	&amp;#x3C;artifactId&gt;spring-boot-strarter-web&amp;#x3C;artifactId&gt;
&amp;#x3C;/dependency&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a simple REST controller in &lt;code&gt;/src/main/java/com/example/demo/HelloController.java&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@RestController
@RequestMapping(“/api”)
Public class HelloController {

	@GetMapping(“helloWorld”)
	public String sayHello() {
		return “”Hello, Dockerized Spring Boot Application!”;
	}

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, build the application:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mvn clean package -DskipTests
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Step 2: Download and Add the Oracle JDBC Driver&lt;/h2&gt;
&lt;p&gt;Since Oracle’s JDBC driver is not available in public Maven repositories, you must download it manually from &lt;a href=&quot;https://www.oracle.com/database/technologies/appdev/jdbc-downloads.html&quot;&gt;Oracle JDBC Downloads page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Once downloaded, add it to your local Maven repository:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mvn install:install \
	-Dfile=ojdbc8.jar \
	-DgroupId=com.oracle \
	-DartifactId=ojdbc8 \
	-Dversion=21.9.0.0 \
	-Dpackaging=jar
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, include the dependency in your &lt;code&gt;pom.xml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;#x3C;dependency&gt;
	&amp;#x3C;groupId&gt;com.oracle&amp;#x3C;/groupId&gt;
	&amp;#x3C;artifactId&gt;ojdbc8&amp;#x3C;/artifactId&gt;
	&amp;#x3C;version&gt;21.9.0.0&amp;#x3C;/version&gt;
&amp;#x3C;/dependency&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Step 3: Create a Dockerfile&lt;/h2&gt;
&lt;p&gt;A Dockerfile defines the environment needed to run your application. Create a Dockerfile in the project root:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Use an official OpenJDK runtime as base image
FROM openjdk:17-jdk-slim

# Set working directory in container
WORKDIR /app

# Copy JAR file into the container
COPY target/demo-0.0.1-SNAPSHOT.jar app.jar 

# Expose the port the app runs on
EXPOSE 8080

# Run the application
CMD [“java”, “”-jar, “app.jar”]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Step 4: Build and Run the Docker Container&lt;/h2&gt;
&lt;p&gt;Now, build your Docker image:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Docker build -t my-spring-app .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run the container:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;Docker run -d -p 8080:8080 —name spring-app my-spring-app
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Verify that the application is running by opening your browser and visiting:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;http://localhost:8080/api/hello&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;Step 5: Run Oracle Database in a Docker Container&lt;/h2&gt;
&lt;p&gt;To run an Oracle Database instance in Docker, use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;Docker run -d —name oracle-test \
	-p 1521:1521 \
	-e ORACLE_SID=ORCL \
	-e ORACLE_PBD=ORCLPDB1 \
	-e ORACLE_PWD=admin123 \
	container-registry.oracle.com/database/enterprise:latest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Update your &lt;em&gt;application.properties&lt;/em&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;spring.datasource.url=jdbc:oracle:thin:@//oracle-db:1521/ORCLPDB1
spring.datasource.username=system
spring.datasource.password=admin123
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ensure both services are running on the same Docker network:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker network create mynetwork
docker network connect mynetwork spring-app
docker network connect mynetwork oracle-db
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;By following this guide, you have successfully Dockerized a Java Spring Boot application, connected it to an Oracle database using Docker, and resolved the OJDBC download challenge.&lt;/p&gt;
&lt;h2&gt;Key Takeaways&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Docker ensures portability and scalability for Java applications.&lt;/li&gt;
&lt;li&gt;Using Docker Compose simplifies multi-container deployment.&lt;/li&gt;
&lt;li&gt;Overcoming OJDBC driver challenges is possible with manual integration.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="/_astro/hero-banner.D3qvwIjM.png"/><enclosure url="/_astro/hero-banner.D3qvwIjM.png"/></item><item><title>Oracle XE In Docker On Windows: An Ultimate Guide</title><link>https://krishanchawla.com/blog/ultimate-guide-to-setting-up-oracle-xe-in-docker-on-windows</link><guid isPermaLink="true">https://krishanchawla.com/blog/ultimate-guide-to-setting-up-oracle-xe-in-docker-on-windows</guid><description>Learn how to set up Oracle XE on Windows using Docker. Follow this step-by-step guide to create a local database environment and streamline development.</description><pubDate>Wed, 25 Dec 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Setting up Oracle XE in your local development environment can significantly speed up application development by giving you a powerful relational database to work with. This guide provides a straightforward approach to setting up Oracle XE using Docker on a Windows system, followed by creating a schema to start your development journey.&lt;/p&gt;
&lt;h2&gt;Why Use Oracle XE with Docker for Local Development?&lt;/h2&gt;
&lt;p&gt;Oracle XE is a lightweight, free version of Oracle Database that caters to the needs of developers and small-scale applications. Running it in Docker adds the benefit of containerization, allowing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Easy setup and cleanup.&lt;/li&gt;
&lt;li&gt;Isolation from your host environment.&lt;/li&gt;
&lt;li&gt;Seamless portability across systems.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;Before we dive into the setup, ensure you have the following installed on your Windows machine:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Docker Desktop: &lt;a href=&quot;https://www.docker.com/products/docker-desktop/&quot;&gt;Download and install Docker.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Windows Subsystem for Linux (WSL): Enable WSL if not already configured.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://krishanchawla.com/_astro/docker-desktop.BHw5u08C_DVOpo.webp&quot; alt=&quot;Docker Desktop&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Containerize An Oracle Database&lt;/h2&gt;
&lt;h3&gt;Step 1: Pull the Oracle XE Docker Image&lt;/h3&gt;
&lt;p&gt;The first step is to pull the Oracle XE Docker image from Oracle’s repository or Docker Hub. Open a terminal and run:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://krishanchawla.com/_astro/docker-pull.CMAqQuWJ_ZzEF5w.webp&quot; alt=&quot;Docker Pull Oracle&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker pull container-registry.oracle.com/database/express:21.3.0-xe
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This command downloads the Oracle XE 21c image to your local system.&lt;/p&gt;
&lt;h3&gt;Step 2: Run the Oracle XE Container&lt;/h3&gt;
&lt;p&gt;Create and run a Docker container using the image you just pulled. Execute the following command:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://krishanchawla.com/_astro/docker-create-container.YTeU3mfm_rc18p.webp&quot; alt=&quot;Create a Docker Container&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker container create `
   -it ` # Run the container in interactive mode
   --name [container-name] `  # Name of the container
   -p [host-port]:1521 `  # Map the port from host to container for DB
   -e ORACLE_PWD=[custom-pass] `  # Password for default user
   container-registry.oracle.com/database/express:[version]  # Image
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Explanation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;--name oracle-xe-container:&lt;/em&gt; Names your container for easier reference.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;-p 1521:1521:&lt;/em&gt; Maps the database’s port to your local machine.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;-e ORACLE_PWD=your_password_here:&lt;/em&gt; Sets the password for the default SYS and SYSTEM users.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After running the command, Oracle XE will initialize inside the container. It may take a few minutes to start up.&lt;/p&gt;
&lt;h3&gt;Step 3: Verify the Container is Running&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://krishanchawla.com/_astro/docker-container-status.B0OPFaxR_Zv5Do4.webp&quot; alt=&quot;Docker Container Status&quot;&gt;&lt;/p&gt;
&lt;p&gt;To ensure the Oracle XE container is up and running, execute:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker ps
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Look for a container named &lt;strong&gt;oracle-xe-container&lt;/strong&gt; in the list. If it’s not running, check the logs for any issues:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker logs oracle-xe-container
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Step 4: Connect to the Oracle XE Database&lt;/h3&gt;
&lt;p&gt;You can connect to Oracle XE using any database client, such as SQL*Plus or Oracle SQL Developer. Use the following connection details:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Host:&lt;/strong&gt; localhost&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Port:&lt;/strong&gt; 1521&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Service Name:&lt;/strong&gt; XEPDB1&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Username:&lt;/strong&gt; SYS&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Password:&lt;/strong&gt; The password you set with ORACLE_PWD&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Role:&lt;/strong&gt; SYSDBA&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Oracle XE database container should be up and running now. Once the database is accessible, we need to create schema.&lt;/p&gt;
&lt;h2&gt;Create an Oracle XE Schema&lt;/h2&gt;
&lt;h3&gt;Step 1: Access the Oracle Database Container&lt;/h3&gt;
&lt;p&gt;First, connect to the running container:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker exec -it &amp;#x3C;container_name_or_id&gt; bash 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://krishanchawla.com/_astro/docker-exec-container.BeDGsBAW_mQhUg.webp&quot; alt=&quot;Docker Execute Container&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Step 2: Log in to SQL*Plus&lt;/h3&gt;
&lt;p&gt;Inside the container, use the sqlplus utility to log in as the default database user (e.g., &lt;em&gt;SYS&lt;/em&gt; or &lt;em&gt;SYSTEM&lt;/em&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sqlplus SYS/&amp;#x3C;password&gt;@localhost/XEPDB1 as sysdba
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Replace &lt;code&gt;&amp;#x3C;password&gt;&lt;/code&gt; with the SYS password you provided when running the container. XEPDB1 is the default pluggable database name for Oracle XE.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://krishanchawla.com/_astro/sqlplus-connet.BefSa6Ff_QmW6V.webp&quot; alt=&quot;SQL Plus Connectivity&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Step 3: Create a New User (Schema)&lt;/h3&gt;
&lt;p&gt;A schema in Oracle is effectively a user. To create one:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;CREATE USER your_schema_name IDENTIFIED BY your_password;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Step 4: Grant Privileges to the User&lt;/h3&gt;
&lt;p&gt;To allow the user to connect to the database and create objects:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;GRANT CONNECT, RESOURCE TO your_schema_name;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Optionally, you can grant additional privileges as needed, such as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;GRANT DBA TO your_schema_name;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Step 5: Connect to the New Schema&lt;/h3&gt;
&lt;p&gt;Exit SQL*Plus by typing exit, and log back in as the new user:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sqlplus your_schema_name/your_password@localhost/XEPDB1
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Step 6: Test the Schema&lt;/h3&gt;
&lt;p&gt;You can now create tables and other objects in the new schema:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE Test (
    id NUMBER PRIMARY KEY,
    name VARCHAR2(100)
);
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="/_astro/hero-banner.mgrudWxv.png"/><enclosure url="/_astro/hero-banner.mgrudWxv.png"/></item><item><title>Instrument JaCoCo Code Coverage agent with WebLogic</title><link>https://krishanchawla.com/blog/quickly-instrument-jacoco-agent-with-weblogic</link><guid isPermaLink="true">https://krishanchawla.com/blog/quickly-instrument-jacoco-agent-with-weblogic</guid><description>Learn how to instrument JaCoCo code coverage agent with WebLogic server.</description><pubDate>Sat, 18 May 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;JaCoCo is an open-source library created by the EclEmma team for measuring and reporting code coverage in an application.
It is quite popular among the variety of code coverage libraries for Java out there.
If you wish to setup JaCoCo with Maven, you can go through my previous blog which provides a detailed &lt;a href=&quot;/blog/setup-jacoco-code-coverage-with-maven&quot;&gt;Guide to setup JacoCo Code Coverage Tool with Maven.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In this blog, we will be talking about how to set up a JaCoCo agent on the WebLogic server. To get started, we have a sample License Eligibility Checker application.&lt;/p&gt;
&lt;h2&gt;Sample Application&lt;/h2&gt;
&lt;p&gt;For demonstration, we are going to use a simple License Eligibility Checker application consisting of a UI that prints if the user is eligible for a license or not based on the provided age.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://krishanchawla.com/_astro/license-eligibility-checker-sample-app.Jsex0B25_1NzlVG.webp&quot; alt=&quot;License Eligibility Application&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Integrating Jacoco Agent with WebLogic Server&lt;/h2&gt;
&lt;p&gt;JaCoCo uses class file instrumentation to record the coverage data against the test execution. Class files are instrumented on the fly using a so-called Java agent. When a Java agent is attached to the JVM, the agent can see when the class file is called and what lines are executed.&lt;/p&gt;
&lt;h4&gt;Download the latest JaCoCo distribution from JaCoCo’s official website&lt;/h4&gt;
&lt;p&gt;https://www.eclemma.org/jacoco/index.html&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://krishanchawla.com/_astro/jacoco-official-website.uT76kBEL_gHnRO.webp&quot; alt=&quot;JaCoCo official website&quot;&gt;&lt;/p&gt;
&lt;h4&gt;Extract the JaCoCo Distribution file&lt;/h4&gt;
&lt;p&gt;Once downloaded, extract the downloaded JaCoCo distribution zip file. You will be able to see multiple JAR’s in the lib folder inside the extracted distribution, which we will be needing in further steps.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://krishanchawla.com/_astro/jacoco-extracted-zip.D-2ADeiz_2f7h90.webp&quot; alt=&quot;Extracted Jacoco distribution file&quot;&gt;&lt;/p&gt;
&lt;h4&gt;Move the JaCoCo distribution under WebLogic&lt;/h4&gt;
&lt;p&gt;Create a new directory inside &lt;code&gt;&amp;#x3C;WEBLOGIC_HOME&gt;/user_projects/domains/&amp;#x3C;DOMAIN_DIRECTORY&gt;/&lt;/code&gt; named &lt;strong&gt;jacoco&lt;/strong&gt;. Copy the &lt;strong&gt;jacocoagent.jar&lt;/strong&gt;, &lt;strong&gt;jacocoant.jar&lt;/strong&gt;, and &lt;strong&gt;jacococli.jar&lt;/strong&gt; inside the newly-created directory.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Please Note -&lt;/strong&gt; Creating the jacoco directory inside the above provided path is not mandatory. You can create the directory anywhere according to your convivence. I generally prefer the above path for better maintenance.&lt;/p&gt;
&lt;h4&gt;Attach JaCoCo Agent to JVM&lt;/h4&gt;
&lt;p&gt;We now need to attach the JaCoCo agent with WebLogic JVM. To attach the JaCoCo agent, add the below argument to &lt;strong&gt;setDomainEnv.sh (For Linux) / setDomainEnv.cmd (For Windows)&lt;/strong&gt; file available inside &lt;code&gt;&amp;#x3C;WEBLOGIC_HOME&gt;/user_projects/domains/&amp;#x3C;DOMAIN_DIRECTORY&gt;/bin/&lt;/code&gt; directory.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;set JAVA_OPTIONS=-javaagent:PATH_TO_JACOCO_AGENT_JAR/jacocoagent.jar=destfile=PATH_TO_GENERATE_JACOCO_EXEC_FILE/jacoco.exec,output=file,append=false
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;JaCoCo agent is successfully attached to JVM now. When we start the WebLogic server, there should be a jacoco.exec file generated at the provided destination file path.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Please Note –&lt;/strong&gt; The size of jacoco.exec file is zero initially. On successful WebLogic server shutdown, the Jacoco agent appends the coverage data to an already created binary file.&lt;/p&gt;
&lt;h2&gt;Convert JaCoCo Binary to HTML report&lt;/h2&gt;
&lt;p&gt;The Java agent generates a Binary file that is not human readable. The file cannot be singlehandedly interpreted. In order to convert the generated binary file (jacoco.exec) we will utilize jacococli.jar. In order to generate the HTML report, perform the following steps:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 1 –&lt;/strong&gt; Create a new directory inside the jacoco directory which we previously created with name ‘reports’. We will utilize this folder to generate the HTML report.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 2 –&lt;/strong&gt; Execute the following jacococli.jar with parameters:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;java -jar PATH_TO_JACOCO_DIRECTORY/jacococli.jar report jacoco.exec --classfiles PATH_TO_APPLICATION_CLASS_FILES --html PATH_TO_HTML_REPORT_DIRECTORY
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="/_astro/hero-banner.CZMiUFoq.png"/><enclosure url="/_astro/hero-banner.CZMiUFoq.png"/></item><item><title>JaCoCo Code Coverage Setup with Maven</title><link>https://krishanchawla.com/blog/setup-jacoco-code-coverage-with-maven</link><guid isPermaLink="true">https://krishanchawla.com/blog/setup-jacoco-code-coverage-with-maven</guid><description>Step-by-step guide to integrate JaCoCo code coverage tool with Maven for measuring and improving test coverage in Java projects.</description><pubDate>Sat, 11 May 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;JaCoCo is an open-source library created by the EclEmma team for measuring and reporting code coverage in an application. It is quite popular among the variety of code coverage libraries for Java out there.&lt;/p&gt;
&lt;p&gt;In this blog, we will be talking about how to set up JaCoCo with Maven Project. To understand JaCoCo’s code coverage capabilities, we need to have a code sample.&lt;/p&gt;
&lt;h2&gt;Sample Maven Project&lt;/h2&gt;
&lt;p&gt;Here, we are going to use a simple class named ‘LicensingService’ consisting of a simple Java Function that prints if the user is eligible for a license or not based on the provided age and ‘LicensingServiceTest’ consisting of Unit Tests.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class LicensingService {

    public void eligibilityChecker(int age) {
         System.out.println(&quot;User Provided Age: &quot; + age);
         if (age &gt;= 18) {
              System.out.println(&quot;You are eligible to apply for license&quot;);     
         } else {
              System.out.println(&quot;Sorry, you are not eligible to apply for license&quot;);     
        }
    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://krishanchawla.com/_astro/java-project-for-jacoco.CZlMFaRh_lUTPE.webp&quot; alt=&quot;Sample Project Code for JaCoCo&quot;&gt;&lt;/p&gt;
&lt;p&gt;Now, we need to add JUnit Tests against which the coverage will be generated.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class LicensingServiceTest {

     @Test
     public void licenseEligibilityChecker() {
          LicensingService licensingService = new LicensingService();
          licensingService.eligibilityChecker(18);
     }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Configuring Jacoco Maven Plugin&lt;/h2&gt;
&lt;p&gt;Now to configure Jacoco with the project, we need to declare this maven plugin in our pom.xml file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;#x3C;build&gt;
    &amp;#x3C;plugins&gt;
        &amp;#x3C;plugin&gt;
            &amp;#x3C;groupId&gt;org.jacoco&amp;#x3C;/groupId&gt;
            &amp;#x3C;artifactId&gt;jacoco-maven-plugin&amp;#x3C;/artifactId&gt;
            &amp;#x3C;executions&gt;
                &amp;#x3C;execution&gt;
                    &amp;#x3C;goals&gt;
                        &amp;#x3C;goal&gt;prepare-agent&amp;#x3C;/goal&gt;
                    &amp;#x3C;/goals&gt;
                &amp;#x3C;/execution&gt;
                &amp;#x3C;execution&gt;
                    &amp;#x3C;id&gt;report&amp;#x3C;/id&gt;
                    &amp;#x3C;phase&gt;prepare-package&amp;#x3C;/phase&gt;
                    &amp;#x3C;goals&gt;
                        &amp;#x3C;goal&gt;report&amp;#x3C;/goal&gt;
                    &amp;#x3C;/goals&gt;
                &amp;#x3C;/execution&gt;
            &amp;#x3C;/executions&gt;
        &amp;#x3C;/plugin&gt;
    &amp;#x3C;/plugins&gt;
&amp;#x3C;/build&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The JaCoCo Java agent will collect coverage information when maven-surefire-plugin runs the tests. It will write it to &lt;code&gt;target/jacoco.exec&lt;/code&gt; by default. &lt;a href=&quot;https://www.eclemma.org/jacoco/trunk/doc/prepare-agent-mojo.html&quot;&gt;Read more&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Executing the Tests and Creating the Coverage Reports&lt;/h2&gt;
&lt;p&gt;Execute the Tests using &lt;code&gt;mvn clean test&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;On execution of tests, Jacoco agent will automatically instrument to the code, thus, it will create a coverage report in binary format in the target directory – &lt;code&gt;target/jacoco.exec&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The generated report can further be interpreted through two different ways.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;#interactive-intellij-report&quot;&gt;Display Interactive Report in IntelliJ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#binary-html-report&quot;&gt;Interpret Binary Report to HTML format&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt; 1. Display Interactive Report in IntelliJ&lt;/h2&gt;
&lt;p&gt;IntelliJ provides an interactive coverage interface that can be utilized to analyze line-by-line coverage of complete code. The &lt;em&gt;jacoco.exec&lt;/em&gt; binary file generated after the execution of tests can simply be accessed in the IntelliJ Coverage window using the below steps.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Step 1&lt;/em&gt; – Click on Run -&gt; Show Coverage Data. [Shortcut : Ctrl + Alt + F6]&lt;/p&gt;
&lt;p&gt;A new Coverage Suite Dialog box will appear.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://krishanchawla.com/_astro/intellij-show-code-coverage-data.3OAdzjIn_ZNbhUA.webp&quot; alt=&quot;Show Code Coverage Data in IntelliJ&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Step 2&lt;/em&gt; – Click on (+) add button in the Coverage Suite Dialog box. Select the jacoco.exec binary file and click Show Selected.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://krishanchawla.com/_astro/intellij-show-code-coverage-data-jacoco-file-selector.C_YfgKoZ_MV1YA.webp&quot; alt=&quot;Show Code Coverage Data in IntelliJ Jacoco File Selector&quot;&gt;&lt;/p&gt;
&lt;p&gt;You will now be able to view the line-by-line coverage.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://krishanchawla.com/_astro/intellij-jacoco-interactive-report.9c804wpZ_1tfVTL.webp&quot; alt=&quot;Interactive Jacoco Report in IntelliJ&quot;&gt;&lt;/p&gt;
&lt;h2&gt; 2. Interpret Binary Report to HTML format&lt;/h2&gt;
&lt;p&gt;The generated jacoco.exec can be interpreted to the HTML, CSV, XML Report format which is human readable. To achieve this, we can simply run the below maven command.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;bash mvn jacoco:report&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Please Note:&lt;/em&gt; This step can be completely avoided by adding the above goal during the test execution itself.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Example: mvn clean test jacoco:report&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;We can now take a look at the &lt;code&gt;target/site/jacoco/index.html&lt;/code&gt; page to see what the generated report looks like.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://krishanchawla.com/_astro/jacoco_code_coverage_html_report.DaGg7UGv_mMCWh.webp&quot; alt=&quot;JaCoCo HTML Based Report&quot;&gt;&lt;/p&gt;</content:encoded><h:img src="/_astro/hero-banner.HdyowiqI.png"/><enclosure url="/_astro/hero-banner.HdyowiqI.png"/></item><item><title>Automate MySQL Database Backups on Linux</title><link>https://krishanchawla.com/blog/automate-mysql-backups-on-linux</link><guid isPermaLink="true">https://krishanchawla.com/blog/automate-mysql-backups-on-linux</guid><description>Learn how to automate MySQL database backups on Linux using a simple shell script. Ensure your data is safe and secure with scheduled backups.</description><pubDate>Tue, 30 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Ensuring the safety and security of your MySQL databases is crucial for any business. Regular MySQL database backups are a key component of a solid data protection strategy. In this blog, I’ll show you how to automate MySQL database backups on Linux using a simple shell script.&lt;/p&gt;
&lt;h2&gt;STEP 1: Create a Backup Shell Script&lt;/h2&gt;
&lt;p&gt;First, create a new shell script named mysql_backup_script.sh and add the following content:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash

# MySQL database credentials
DB_USER=&quot;&amp;#x3C;DB_USER&gt;&quot;
DB_PASS=&quot;&amp;#x3C;DB_PASSWORD&gt;&quot;
DB_NAME=&quot;&amp;#x3C;DB_NAME&gt;&quot;

# Backup directory
BACKUP_DIR=&quot;&amp;#x3C;path/to/backup/directory&gt;&quot;

# Backup file name
BACKUP_FILE=&quot;$BACKUP_DIR/$DB_NAME-$(date +%Y%m%d%H%M%S).sql&quot;

# Dump the MySQL database
mysqldump --user=$DB_USER --password=$DB_PASS $DB_NAME &gt; $BACKUP_FILE

# Optional: Compress the backup file
# gzip $BACKUP_FILE
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Replace &lt;code&gt;&amp;#x3C;DB_USER&gt;&lt;/code&gt;, &lt;code&gt;&amp;#x3C;DB_PASSWORD&gt;&lt;/code&gt;, &lt;code&gt;&amp;#x3C;DB_NAME&gt;&lt;/code&gt;, and &lt;code&gt;&amp;#x3C;path/to/backup/directory&gt;&lt;/code&gt; with your actual MySQL credentials, database name, and backup directory path.&lt;/p&gt;
&lt;h2&gt;Step 2: Configure executable permissions for the script&lt;/h2&gt;
&lt;p&gt;Add the executable permissions to the script file by running:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;chmod +x mysql_backup.sh&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;Step 3: Schedule the MySQL database Backups&lt;/h2&gt;
&lt;p&gt;Schedule the backup script to run automatically on a specified time using cron. Open your crontab file by running:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;crontab -e&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Add the following line to schedule the backup to run daily at midnight:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;0 0 * * * /path/to/mysql_backup_script.sh&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Replace &lt;code&gt;/path/to/mysql_backup_script.sh&lt;/code&gt; with the actual path to your backup script.&lt;/p&gt;
&lt;p&gt;You can utilize the following &lt;a href=&quot;https://crontab.guru/&quot;&gt;reference&lt;/a&gt;, if you wish to configure different schedule for your backup.&lt;/p&gt;</content:encoded><h:img src="/_astro/hero-banner.Dy7jNV2v.png"/><enclosure url="/_astro/hero-banner.Dy7jNV2v.png"/></item><item><title>How to download maven dependencies behind organization proxy</title><link>https://krishanchawla.com/blog/maven-dependencies-behind-proxy</link><guid isPermaLink="true">https://krishanchawla.com/blog/maven-dependencies-behind-proxy</guid><description>A quick guide on how to download Maven dependencies behind your organization’s proxy.</description><pubDate>Wed, 10 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Whether you’re a Software Developer or an Automation Engineer, Maven is a widely used build tool today. One common challenge in some environments is the inability to download Maven dependencies due to organizational proxies. In this article, I’ll share a quick guide on how to download Maven dependencies behind your organization’s proxy.&lt;/p&gt;
&lt;h2&gt;Proxy Configuration&lt;/h2&gt;
&lt;p&gt;Let’s understand how to setup proxy configuration. This configuration can be done in settings.xml file.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Open your settings.xml file located in &lt;code&gt;&amp;#x3C;user_home&gt;/.m2&lt;/code&gt; directory. If the file does not exist, you can create it.&lt;/li&gt;
&lt;li&gt;Add the following &lt;code&gt;&amp;#x3C;proxies&gt;&lt;/code&gt; configuration inside the &lt;code&gt;&amp;#x3C;settings&gt;&lt;/code&gt; element, replacing proxy.example.com and 8080 with your server’s hostname and port:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&amp;#x3C;proxies&gt;
    &amp;#x3C;proxy&gt;
        &amp;#x3C;id&gt;example-proxy&amp;#x3C;/id&gt;
        &amp;#x3C;active&gt;true&amp;#x3C;/active&gt;
        &amp;#x3C;protocol&gt;http&amp;#x3C;/protocol&gt;
        &amp;#x3C;host&gt;proxy.example.com&amp;#x3C;/host&gt;
        &amp;#x3C;port&gt;8080&amp;#x3C;/port&gt;
        &amp;#x3C;!-- Optional: Proxy username and password --&gt;
        &amp;#x3C;!--&amp;#x3C;username&gt;proxy-username&amp;#x3C;/username&gt;--&gt;
        &amp;#x3C;!--&amp;#x3C;password&gt;proxy-password&amp;#x3C;/password&gt;--&gt;
        &amp;#x3C;!--&amp;#x3C;nonProxyHosts&gt;localhost|127.0.0.1&amp;#x3C;/nonProxyHosts&gt;--&gt;
    &amp;#x3C;/proxy&gt;
&amp;#x3C;/proxies&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If your organization requires authentication, uncomment the &lt;code&gt;&amp;#x3C;username&gt;&lt;/code&gt; and &lt;code&gt;&amp;#x3C;password&gt;&lt;/code&gt; tags and provide your credentials. Additionally, you can specify hosts that should bypass the proxy in the &lt;code&gt;&amp;#x3C;nonProxyHosts&gt;&lt;/code&gt; tag.&lt;/p&gt;
&lt;h2&gt;Test the Configuration:&lt;/h2&gt;
&lt;p&gt;To test the proxy configuration, run a Maven command such as mvn clean install for a Maven project. Maven should now be able to download dependencies through the proxy.&lt;/p&gt;</content:encoded><h:img src="/_astro/hero-banner.CZy6VbyU.png"/><enclosure url="/_astro/hero-banner.CZy6VbyU.png"/></item></channel></rss>