<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: longtk26</title>
    <description>The latest articles on DEV Community by longtk26 (@longtk26).</description>
    <link>https://dev.to/longtk26</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F915607%2F896c224d-6196-4cd9-93a2-a51507438d28.png</url>
      <title>DEV Community: longtk26</title>
      <link>https://dev.to/longtk26</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/longtk26"/>
    <language>en</language>
    <item>
      <title>Step-by-Step Guide: Fine-Tuning an IELTS Examiner LLM with Unsloth, QLoRA</title>
      <dc:creator>longtk26</dc:creator>
      <pubDate>Tue, 20 Oct 2026 16:02:54 +0000</pubDate>
      <link>https://dev.to/longtk26/step-by-step-guide-fine-tuning-an-ielts-examiner-llm-with-unsloth-qlora-20a1</link>
      <guid>https://dev.to/longtk26/step-by-step-guide-fine-tuning-an-ielts-examiner-llm-with-unsloth-qlora-20a1</guid>
      <description>&lt;p&gt;In &lt;strong&gt;&lt;a href="https://dev.to/longtk26/llm-fine-tuning-concepts-architecture-and-fine-tuning-vs-rag-1j84"&gt;Part 1: Concepts &amp;amp; Architecture&lt;/a&gt;&lt;/strong&gt;, we explored what fine-tuning is, how it differs fundamentally from RAG, and how QLoRA enables training large models on consumer hardware.&lt;/p&gt;

&lt;p&gt;In this practical, hands-on tutorial, we will build a domain-specialized LLM from scratch: &lt;strong&gt;an official IELTS Examiner AI&lt;/strong&gt;. We will fine-tune an 8-Billion parameter foundation model (&lt;strong&gt;Qwen 2.5 7B&lt;/strong&gt;) directly on a free Google Colab instance, export the model to GGUF format.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The Tool Stack: Why Unsloth + QLoRA?&lt;/li&gt;
&lt;li&gt;Step 1: Dataset Engineering for IELTS Evaluation&lt;/li&gt;
&lt;li&gt;Step 2: Colab Environment Setup &amp;amp; GPU Check&lt;/li&gt;
&lt;li&gt;Step 3: Loading the 4-bit Base Model&lt;/li&gt;
&lt;li&gt;Step 4: Attaching LoRA Adapters&lt;/li&gt;
&lt;li&gt;Step 5: Formatting Prompts &amp;amp; Chat Templates&lt;/li&gt;
&lt;li&gt;Step 6: Configuring SFTTrainer &amp;amp; Training&lt;/li&gt;
&lt;li&gt;Step 7: Qualitative Inference &amp;amp; Testing&lt;/li&gt;
&lt;li&gt;Step 8: Exporting to GGUF &amp;amp; Serving with Ollama&lt;/li&gt;
&lt;li&gt;Troubleshooting &amp;amp; Pro Tips&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. The Tool Stack: Why Unsloth + QLoRA?
&lt;/h2&gt;

&lt;p&gt;Fine-tuning an 8B parameter model natively requires over &lt;strong&gt;110 GB of VRAM&lt;/strong&gt;. On Google Colab's free tier, we are constrained to a single &lt;strong&gt;NVIDIA Tesla T4 GPU (15.3 GB VRAM)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To make this feasible, we use:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Why We Selected It&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://github.com/unslothai/unsloth" rel="noopener noreferrer"&gt;Unsloth&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fine-tuning Acceleration&lt;/td&gt;
&lt;td&gt;Cuts VRAM consumption by &lt;strong&gt;70%&lt;/strong&gt; and speeds up training by &lt;strong&gt;2x&lt;/strong&gt; without any loss of accuracy.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;BitsAndBytes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4-bit NormalFloat (NF4)&lt;/td&gt;
&lt;td&gt;Compresses the 16-bit base model (~16 GB) down to just &lt;strong&gt;~5.5 GB&lt;/strong&gt;, fitting comfortably on the T4 GPU.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PEFT (Hugging Face)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Parameter-Efficient Fine-Tuning&lt;/td&gt;
&lt;td&gt;Trains only low-rank adapter matrices ($r=16$), representing just &lt;strong&gt;0.53%&lt;/strong&gt; of all model parameters (40.3M trainable parameters out of 7.6B).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TRL (&lt;code&gt;SFTTrainer&lt;/code&gt;)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Supervised Training Loop&lt;/td&gt;
&lt;td&gt;Handles batching, sequence packing, loss masking, and gradient accumulation.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;llama.cpp + Ollama&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Quantization &amp;amp; Local Serving&lt;/td&gt;
&lt;td&gt;Compiles the fine-tuned model into a single 4.5 GB &lt;code&gt;.gguf&lt;/code&gt; file that runs with low latency on your local laptop (CPU, Mac M-series, or NVIDIA GPU).&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Step 1: Dataset Engineering for IELTS Evaluation
&lt;/h2&gt;

&lt;p&gt;The model's grading accuracy and feedback quality depend entirely on the consistency of the training data. For IELTS, our model must evaluate essays strictly according to the &lt;strong&gt;four official Cambridge assessment criteria&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Task Response (TR)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Coherence &amp;amp; Cohesion (CC)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lexical Resource (LR)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Grammatical Range &amp;amp; Accuracy (GRA)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  ChatML Data Schema (&lt;code&gt;sample_ielts_dataset.jsonl&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Each training row is a JSON object with a &lt;code&gt;"messages"&lt;/code&gt; array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"messages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"system"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"You are an official IELTS Senior Examiner. Evaluate the candidate's response using the official IELTS Band Descriptors: Task Response (TR), Coherence and Cohesion (CC), Lexical Resource (LR), and Grammatical Range and Accuracy (GRA). Provide overall and component band scores, diagnostic feedback, specific sentence-level corrections, and a Band 8.5+ model revision."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"IELTS Writing Task 2 Prompt:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;In many cities, the use of private cars is increasing rapidly. What are the problems associated with this, and what measures can be taken to solve them?&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;Candidate Essay:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;In modern cities, people buy more cars than before. This phenomenon cause many issues for society and environment..."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"assistant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"### 📊 Estimated Band Score: 6.0&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;#### Component Scores Breakdown:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;- **Task Response (6.5):** Clearly identifies problems (traffic congestion, air pollution) and plausible solutions...&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;- **Coherence &amp;amp; Cohesion (6.0):** Information is logically arranged, but transitions are mechanical...&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;- **Lexical Resource (5.5):** Adequate for basic communication, but relies on informal phrasing...&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;- **Grammatical Range &amp;amp; Accuracy (5.5):** Frequent errors with articles and subject-verb agreements...&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;#### ✍️ Diagnostic Sentence Improvements:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;1. *Original:* 'This phenomenon cause many issues...'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;   *Correction:* 'This phenomenon causes numerous detrimental consequences for both society and the environment.'&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;#### 🌟 Band 8.5+ Model Rephrase:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;'The proliferation of private motor vehicles precipitates severe urban complications...'"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 2: Colab Environment Setup &amp;amp; GPU Check
&lt;/h2&gt;

&lt;p&gt;Open Google Colab and set your runtime to GPU: &lt;code&gt;Runtime &amp;gt; Change runtime type &amp;gt; T4 GPU&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing Pre-built Wheels (Avoiding Source Compilation)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Check GPU hardware
&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="n"&gt;nvidia&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;smi&lt;/span&gt;

&lt;span class="c1"&gt;# Fast installation using pre-compiled binary wheels (~1 minute)
&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unsloth[colab-new] @ git+https://github.com/unslothai/unsloth.git&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;no&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;deps&lt;/span&gt; &lt;span class="n"&gt;trl&lt;/span&gt; &lt;span class="n"&gt;peft&lt;/span&gt; &lt;span class="n"&gt;accelerate&lt;/span&gt; &lt;span class="n"&gt;bitsandbytes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Critical Gotcha:&lt;/strong&gt; Never run &lt;code&gt;!pip install "xformers&amp;lt;0.0.27"&lt;/code&gt; on modern Colab. Doing so triggers a 40-minute source compilation (&lt;code&gt;Building wheels for collected packages: xformers&lt;/code&gt;). Installing via the command above installs pre-built binary wheels in under 90 seconds.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 3: Loading the 4-bit Base Model
&lt;/h2&gt;

&lt;p&gt;We load &lt;strong&gt;&lt;code&gt;Qwen2.5-7B-Instruct&lt;/code&gt;&lt;/strong&gt; in 4-bit precision via Unsloth's &lt;code&gt;FastLanguageModel&lt;/code&gt;. Qwen 2.5 is exceptional at linguistic reasoning, grammatical parsing, and instruction following.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;unsloth&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastLanguageModel&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;

&lt;span class="n"&gt;max_seq_length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2048&lt;/span&gt;  &lt;span class="c1"&gt;# Ample context for full essay + comprehensive grading feedback
&lt;/span&gt;&lt;span class="n"&gt;dtype&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;           &lt;span class="c1"&gt;# Auto-detection (Float16 on T4)
&lt;/span&gt;&lt;span class="n"&gt;load_in_4bit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;    &lt;span class="c1"&gt;# 4-bit NormalFloat (NF4)
&lt;/span&gt;
&lt;span class="n"&gt;model_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unsloth/Qwen2.5-7B-Instruct-bnb-4bit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tokenizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FastLanguageModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_seq_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;max_seq_length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;load_in_4bit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;load_in_4bit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Memory Footprint:&lt;/strong&gt; The entire 7.6B model is loaded into only &lt;strong&gt;~5.5 GB of VRAM&lt;/strong&gt;, leaving ~9 GB of free VRAM for activations and optimizer states.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Attaching LoRA Adapters
&lt;/h2&gt;

&lt;p&gt;Next, we wrap the base model with low-rank trainable matrices targeting both the attention projections and the feed-forward MLP layers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FastLanguageModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_peft_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                  &lt;span class="c1"&gt;# LoRA rank (16-32 provides optimal expressive capacity)
&lt;/span&gt;    &lt;span class="n"&gt;target_modules&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;q_proj&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;k_proj&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;v_proj&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;o_proj&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gate_proj&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;up_proj&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;down_proj&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;lora_alpha&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="c1"&gt;# Scaling factor (usually equals r or 2*r)
&lt;/span&gt;    &lt;span class="n"&gt;lora_dropout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;# Optimized to 0 by Unsloth for maximum speed
&lt;/span&gt;    &lt;span class="n"&gt;bias&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;none&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;use_gradient_checkpointing&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unsloth&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# Drastically cuts activation memory
&lt;/span&gt;    &lt;span class="n"&gt;random_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3407&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5: Formatting Prompts &amp;amp; Chat Templates
&lt;/h2&gt;

&lt;p&gt;We apply the model's native chat template to convert the multi-role JSON conversation into standard token sequences:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datasets&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dataset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Dataset&lt;/span&gt;

&lt;span class="c1"&gt;# Load your JSONL dataset
# dataset = load_dataset("json", data_files="sample_ielts_dataset.jsonl")
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;formatting_prompts_func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;examples&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;convos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;examples&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;texts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;tokenizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply_chat_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;convo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tokenize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;add_generation_prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;convo&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;convos&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;texts&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;dataset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;formatting_prompts_func&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;batched&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 6: Configuring SFTTrainer &amp;amp; Training
&lt;/h2&gt;

&lt;p&gt;Now we configure the &lt;code&gt;SFTTrainer&lt;/code&gt; with an 8-bit AdamW optimizer and cosine learning rate schedule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;trl&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SFTTrainer&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;transformers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TrainingArguments&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;unsloth&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;is_bfloat16_supported&lt;/span&gt;

&lt;span class="n"&gt;trainer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SFTTrainer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tokenizer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tokenizer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;train_dataset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dataset_text_field&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_seq_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;max_seq_length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;dataset_num_proc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;packing&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;TrainingArguments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;per_device_train_batch_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;gradient_accumulation_steps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# Effective batch size = 2 * 4 = 8
&lt;/span&gt;        &lt;span class="n"&gt;warmup_steps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;max_steps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                  &lt;span class="c1"&gt;# Adjust based on dataset size (e.g. 100-300)
&lt;/span&gt;        &lt;span class="n"&gt;learning_rate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;2e-4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="c1"&gt;# Standard LoRA learning rate
&lt;/span&gt;        &lt;span class="n"&gt;fp16&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;is_bfloat16_supported&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;bf16&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;is_bfloat16_supported&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;logging_steps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;optim&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;adamw_8bit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="c1"&gt;# Saves 75% optimizer memory
&lt;/span&gt;        &lt;span class="n"&gt;weight_decay&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;lr_scheduler_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cosine&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3407&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;output_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ielts_outputs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;trainer_stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;trainer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;train&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 7: Qualitative Inference &amp;amp; Testing
&lt;/h2&gt;

&lt;p&gt;We switch the model to inference mode (which Unsloth accelerates by an additional 2x) and test on a completely unseen essay:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;FastLanguageModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;for_inference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;test_messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You are an official IELTS Senior Examiner. Grade the candidate&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s essay using the official IELTS Band Descriptors (Task Response, Coherence and Cohesion, Lexical Resource, Grammatical Range and Accuracy). Provide estimated band scores, granular feedback for each criterion, sentence-level corrections, and a Band 8.5+ model revision.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Prompt: Some people say that advertising encourages us to buy things we do not really need. Others think that advertisements inform us about new products that can improve our lives. Discuss both views and give your opinion.&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;Candidate Essay:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Advertising is everywhere today. In television and internet, we can see many ads. Some people think advertisements make people waste money on useless items, but others think ads provide useful knowledge. I agree that ads are helpful if we are smart consumers.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;inputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tokenizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply_chat_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;test_messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tokenize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;add_generation_prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;return_tensors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cuda&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;outputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;input_ids&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_new_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# Low temperature ensures calibrated, deterministic grading
&lt;/span&gt;    &lt;span class="n"&gt;use_cache&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tokenizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outputs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]:],&lt;/span&gt; &lt;span class="n"&gt;skip_special_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 8: Exporting to GGUF &amp;amp; Serving with Ollama
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Save Adapter &amp;amp; Export 4-bit GGUF in Colab
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 1. Save LoRA weights locally
&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ielts_lora_adapter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tokenizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ielts_lora_adapter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 2. Automatically merge weights into 16-bit and quantize to GGUF Q4_K_M
&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save_pretrained_gguf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ielts_examiner_gguf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tokenizer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;quantization_method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;q4_k_m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This compiles &lt;code&gt;ielts_examiner_gguf_gguf/Qwen2.5-7B-Instruct.Q4_K_M.gguf&lt;/code&gt; (~4.5 GB) and an automatic &lt;code&gt;Modelfile&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Backup to Google Drive
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.colab&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;drive&lt;/span&gt;
&lt;span class="n"&gt;drive&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/content/drive&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="n"&gt;cp&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="n"&gt;ielts_examiner_gguf_gguf&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;drive&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;MyDrive&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Run Locally with Ollama
&lt;/h3&gt;

&lt;p&gt;Download the folder from Google Drive to your local machine, open your terminal, and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Register the model with Ollama&lt;/span&gt;
ollama create ielts-examiner &lt;span class="nt"&gt;-f&lt;/span&gt; Modelfile

&lt;span class="c"&gt;# Start an interactive session&lt;/span&gt;
ollama run ielts-examiner
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You now have a private, dedicated IELTS Examiner AI running locally on your laptop with zero API fees!&lt;/p&gt;




&lt;h2&gt;
  
  
  10. Troubleshooting
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Problem&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Solution&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;Building wheels for collected packages: xformers&lt;/code&gt; hangs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Incompatible pip version attempting to build CUDA C++ from source&lt;/td&gt;
&lt;td&gt;Avoid pinning &lt;code&gt;xformers&amp;lt;0.0.27&lt;/code&gt;; use the modern install command with pre-built wheels shown in Step 2.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CUDA Out-of-Memory (OOM)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sequence length too long or batch size too large&lt;/td&gt;
&lt;td&gt;Reduce &lt;code&gt;per_device_train_batch_size&lt;/code&gt; to &lt;code&gt;1&lt;/code&gt; or &lt;code&gt;2&lt;/code&gt;, increase &lt;code&gt;gradient_accumulation_steps&lt;/code&gt; to &lt;code&gt;8&lt;/code&gt;, and ensure &lt;code&gt;use_gradient_checkpointing="unsloth"&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Grade Inflation (Scores too high)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Dataset consists primarily of high-band essays&lt;/td&gt;
&lt;td&gt;Ensure at least 30% of your training dataset consists of Band 4.5–5.5 essays with rigorous feedback explaining their weaknesses.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Lost Files after Colab Disconnect&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Colab runtime disk is temporary&lt;/td&gt;
&lt;td&gt;Always mount Google Drive at the beginning of the notebook and save checkpoints directly to &lt;code&gt;/content/drive/MyDrive/&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Conclusion &amp;amp; Resources
&lt;/h2&gt;

&lt;p&gt;By combining &lt;strong&gt;QLoRA&lt;/strong&gt; with &lt;strong&gt;Unsloth&lt;/strong&gt;, we transformed an 8B foundation model into a specialized IELTS assessment engine within a single hour on free cloud hardware.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>finetuning</category>
      <category>llm</category>
    </item>
    <item>
      <title>LLM Fine-Tuning: Concepts, Architecture, and Fine-Tuning vs. RAG</title>
      <dc:creator>longtk26</dc:creator>
      <pubDate>Tue, 20 Oct 2026 16:01:45 +0000</pubDate>
      <link>https://dev.to/longtk26/llm-fine-tuning-concepts-architecture-and-fine-tuning-vs-rag-1j84</link>
      <guid>https://dev.to/longtk26/llm-fine-tuning-concepts-architecture-and-fine-tuning-vs-rag-1j84</guid>
      <description>&lt;p&gt;Fine-tuning has emerged as one of the most critical skills for modern AI engineers. While large general-purpose foundation models (like GPT, Claude, or Qwen) are impressive out of the box, building production-grade domain applications often demands specialized behavior, deterministic formatting, or deep domain expertise that prompting alone cannot reliably achieve.&lt;/p&gt;

&lt;p&gt;In this guide, we break down the foundational concepts of LLM fine-tuning, compare it with Retrieval-Augmented Generation (RAG), examine parameter-efficient training techniques (LoRA/QLoRA), and help you decide exactly when and when not to fine-tune.&lt;/p&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
What is Fine-Tuning?

&lt;ul&gt;
&lt;li&gt;The LLM Training Lifecycle&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Why Fine-Tune? Core Motivations&lt;/li&gt;
&lt;li&gt;
Fine-Tuning vs. RAG

&lt;ul&gt;
&lt;li&gt;Core Mental Model: Open-Book vs. Closed-Book Exam&lt;/li&gt;
&lt;li&gt;Detailed Comparison&lt;/li&gt;
&lt;li&gt;The Hybrid Approach: RAG + Fine-Tuning&lt;/li&gt;
&lt;li&gt;Decision Framework: Which Should You Choose?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
How an LLM Processes Input and Generates Output (Under the Hood)

&lt;ul&gt;
&lt;li&gt;The End-to-End Inference Lifecycle&lt;/li&gt;
&lt;li&gt;The Tokenization Mechanism: From Words to Numbers&lt;/li&gt;
&lt;li&gt;Internal Processing: Embeddings, Positional Encoding &amp;amp; Attention&lt;/li&gt;
&lt;li&gt;The Prediction Loop: Logits, Softmax &amp;amp; Autoregressive Decoding&lt;/li&gt;
&lt;li&gt;Sampling Controls: Temperature, Top-p &amp;amp; Stop Tokens&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
Methods of Fine-Tuning

&lt;ul&gt;
&lt;li&gt;Full Parameter Fine-Tuning (FPFT)&lt;/li&gt;
&lt;li&gt;Parameter-Efficient Fine-Tuning (PEFT) &amp;amp; LoRA&lt;/li&gt;
&lt;li&gt;QLoRA: 4-bit Quantized LoRA&lt;/li&gt;
&lt;li&gt;Hardware Comparison: FPFT vs. LoRA vs. QLoRA&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
Essential Hyperparameters &amp;amp; Micro-Concepts

&lt;ul&gt;
&lt;li&gt;Epochs vs. Steps vs. Batch Size&lt;/li&gt;
&lt;li&gt;Gradient Accumulation &amp;amp; Effective Batch Size&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
When is Fine-Tuning Useful? (And When is it Not?)

&lt;ul&gt;
&lt;li&gt;Best Scenarios for Fine-Tuning&lt;/li&gt;
&lt;li&gt;Scenarios Where You Should NOT Fine-Tune&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Summary &amp;amp; What's Next&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  1. What is Fine-Tuning?
&lt;/h2&gt;

&lt;p&gt;At its core, &lt;strong&gt;fine-tuning&lt;/strong&gt; is the process of taking a pre-trained model (which already understands grammar, world facts, and language patterns from trillions of tokens) and training it further on a curated, domain-specific dataset to adapt its weights for a specific task, persona, or style.&lt;/p&gt;

&lt;h3&gt;
  
  
  The LLM Training Lifecycle
&lt;/h3&gt;

&lt;p&gt;Modern language models undergo a three-stage lifecycle:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    A["1. Pre-training&amp;lt;br/&amp;gt;Web Corpus&amp;lt;br/&amp;gt;Base Knowledge"] --&amp;gt; B["2. SFT / Instruct&amp;lt;br/&amp;gt;Curated QA&amp;lt;br/&amp;gt;Instruction Following"]
    B --&amp;gt; C["3. Alignment&amp;lt;br/&amp;gt;RLHF / DPO&amp;lt;br/&amp;gt;Safety &amp;amp; Preference"]

    style A fill:#0369a1,stroke:#38bdf8,stroke-width:2px,color:#ffffff;
    style B fill:#15803d,stroke:#4ade80,stroke-width:2px,color:#ffffff;
    style C fill:#b45309,stroke:#fbbf24,stroke-width:2px,color:#ffffff;&lt;/code&gt;&lt;/pre&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pre-training (Base Models):&lt;/strong&gt; Models like &lt;code&gt;Llama-3-8B-Base&lt;/code&gt; or &lt;code&gt;Qwen2.5-7B&lt;/code&gt; read massive swaths of the internet. Their objective is raw next-token prediction. They complete sentences, but they cannot converse or reliably follow instructions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supervised Fine-Tuning (SFT / Instruct Models):&lt;/strong&gt; The base model is trained on question-answer or multi-turn conversational datasets. This transforms a raw text completer into an assistant (&lt;code&gt;Llama-3-8B-Instruct&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Domain-Specific Fine-Tuning (Our Focus):&lt;/strong&gt; You take an Instruct model and fine-tune it on proprietary tasks—for example, evaluating IELTS essays according to strict Cambridge band descriptors.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  2. Why Fine-Tune? Core Motivations
&lt;/h2&gt;

&lt;p&gt;Why go through the effort of fine-tuning when we can simply write a prompt for GPT-4 or Claude?&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;mindmap
  root((Why Fine-Tune?))
    Style &amp;amp; Persona
      Strict tone consistency
      Unwavering examiner persona
      Specialized grading rubrics
    Output Determinism
      Reliable JSON schemas
      Predictable structured output
      Zero prompt leakage
    Efficiency &amp;amp; Cost
      Distill 70B behavior into 7B
      Drastically shorter prompts
      Lower latency and API costs
    Edge &amp;amp; Privacy
      Run on-premise or locally
      Zero data sent to third parties
      Full sovereignty over weights&lt;/code&gt;&lt;/pre&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Strict Style and Persona Consistency:&lt;/strong&gt; Prompt engineering often suffers from "drift" or breaks character over extended contexts. Fine-tuning hardcodes the persona directly into the model's weights.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complex Output Formatting:&lt;/strong&gt; When your application requires a strict multi-section output, fine-tuned models produce the exact schema reliably without needing fragile retry loops.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompt Compression &amp;amp; Latency Reduction:&lt;/strong&gt; With zero-shot prompting, you might need to send 2,000 tokens of rubrics, instructions, and few-shot examples with &lt;em&gt;every single API request&lt;/em&gt;. Fine-tuning bakes those instructions into the model, cutting input prompt lengths by up to 80% and slashing latency and inference costs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance on Edge Hardware:&lt;/strong&gt; You can fine-tune a compact 7B or 8B model to rival or outperform a 70B+ generic model on your specific vertical task, allowing you to deploy on lightweight hardware or on-premise servers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Privacy &amp;amp; Governance:&lt;/strong&gt; Fine-tuning open-weight models allows enterprises to keep all sensitive customer data within their own Virtual Private Cloud (VPC) or local machine.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  3. Fine-Tuning vs. RAG
&lt;/h2&gt;

&lt;p&gt;One of the most frequent question in AI is: &lt;strong&gt;"Should I use RAG or Fine-Tuning?"&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Core Mental Model: Open-Book vs. Closed-Book Exam
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RAG (Retrieval-Augmented Generation) is an Open-Book Exam:&lt;/strong&gt; The student has good general reasoning skills. When asked a question, they look up external manuals, company wikis, or database records and synthesize an answer based on what they just retrieved.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fine-Tuning is Studying for the Exam (Internalized Expertise):&lt;/strong&gt; The student has memorized patterns, stylistic habits, and specialized procedural skills. When asked to perform, the reaction is instinctive and fast.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Detailed Comparison
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Retrieval-Augmented Generation (RAG)&lt;/th&gt;
&lt;th&gt;Supervised Fine-Tuning (SFT)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Primary Purpose&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Injecting &lt;strong&gt;dynamic, up-to-date factual knowledge&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Modifying &lt;strong&gt;form, behavior, tone, style, and reasoning patterns&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Knowledge Freshness&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Real-time:&lt;/strong&gt; Update your vector database, and the model instantly accesses new documents&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Static:&lt;/strong&gt; Knowledge is frozen at training time; updating requires retraining&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hallucination Mitigation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High: Grounded directly in retrieved source chunks with explicit citations&lt;/td&gt;
&lt;td&gt;Moderate: Can still hallucinate facts if trained on noisy or contradictory data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost Profile&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Low upfront setup; higher recurring per-query cost (longer context embeddings &amp;amp; tokens)&lt;/td&gt;
&lt;td&gt;Higher upfront compute cost; lower recurring query cost (shorter prompts, smaller models)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Slower (Vector search + Embedding generation + Large context window)&lt;/td&gt;
&lt;td&gt;Faster (Direct inference with compact prompts)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Context Window Consumption&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High (Passages and reference chunks occupy prompt tokens)&lt;/td&gt;
&lt;td&gt;Low (Knowledge and rules are internal to the model)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  The Hybrid Approach: RAG + Fine-Tuning
&lt;/h3&gt;

&lt;p&gt;In modern production systems, RAG and Fine-Tuning are &lt;strong&gt;not mutually exclusive&lt;/strong&gt;—they are complementary:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    UserQuery["User Prompt&amp;lt;br/&amp;gt;Essay + Exam Question"] --&amp;gt; RAG["RAG Module&amp;lt;br/&amp;gt;Official Rubrics Database"]
    RAG --&amp;gt; ContextAugmented["Augmented Prompt&amp;lt;br/&amp;gt;User Essay + Rubrics"]
    ContextAugmented --&amp;gt; FTModel["Fine-Tuned 8B Model&amp;lt;br/&amp;gt;Calibrated Examiner Persona"]
    FTModel --&amp;gt; Output["Assessment Report&amp;lt;br/&amp;gt;Accurate &amp;amp; Structured"]

    style RAG fill:#0369a1,stroke:#38bdf8,stroke-width:2px,color:#ffffff;
    style FTModel fill:#15803d,stroke:#4ade80,stroke-width:2px,color:#ffffff;
    style Output fill:#7e22ce,stroke:#c084fc,stroke-width:2px,color:#ffffff;&lt;/code&gt;&lt;/pre&gt;



&lt;ul&gt;
&lt;li&gt;Use &lt;strong&gt;Fine-Tuning&lt;/strong&gt; to teach the model &lt;strong&gt;HOW&lt;/strong&gt; to behave (e.g. grade like an official Cambridge examiner, never give generic praise, generate exact JSON).&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;RAG&lt;/strong&gt; to feed the model &lt;strong&gt;WHAT&lt;/strong&gt; facts or reference materials to consult (e.g. the specific exam version question, updated vocabulary).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Decision Framework: Which Should You Choose?
&lt;/h3&gt;

&lt;p&gt;Use this decision tree to pick the right strategy:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    Start["Improve LLM Performance"] --&amp;gt; Q1{"Need dynamic or&amp;lt;br/&amp;gt;private facts?"}
    Q1 -- Yes --&amp;gt; Q2{"Changes daily&amp;lt;br/&amp;gt;or frequently?"}
    Q2 -- Yes --&amp;gt; UseRAG["✅ Use RAG&amp;lt;br/&amp;gt;(Vector DB + Search)"]
    Q2 -- No --&amp;gt; Q3{"Need custom tone&amp;lt;br/&amp;gt;or structure?"}
    Q3 -- Yes --&amp;gt; UseHybrid["✅ Use Hybrid&amp;lt;br/&amp;gt;(RAG + Fine-Tuning)"]
    Q3 -- No --&amp;gt; UseRAG

    Q1 -- No --&amp;gt; Q4{"Works with&amp;lt;br/&amp;gt;few-shot prompts?"}
    Q4 -- Yes --&amp;gt; UsePrompt["✅ Use Prompting&amp;lt;br/&amp;gt;(Fastest &amp;amp; Low Cost)"]
    Q4 -- No --&amp;gt; Q5{"Have 500+ quality&amp;lt;br/&amp;gt;examples?"}
    Q5 -- Yes --&amp;gt; UseFT["✅ Fine-Tune&amp;lt;br/&amp;gt;(PEFT / QLoRA)"]
    Q5 -- No --&amp;gt; CollectData["⚠️ Curate Data&amp;lt;br/&amp;gt;(Bootstrap Pairs First)"]

    style Start fill:#1e293b,stroke:#94a3b8,stroke-width:2px,color:#ffffff;
    style UseRAG fill:#0369a1,stroke:#38bdf8,stroke-width:2px,color:#ffffff;
    style UseHybrid fill:#7e22ce,stroke:#c084fc,stroke-width:2px,color:#ffffff;
    style UsePrompt fill:#15803d,stroke:#4ade80,stroke-width:2px,color:#ffffff;
    style UseFT fill:#c2410c,stroke:#fb923c,stroke-width:2px,color:#ffffff;
    style CollectData fill:#b45309,stroke:#fbbf24,stroke-width:2px,color:#ffffff;&lt;/code&gt;&lt;/pre&gt;






&lt;h2&gt;
  
  
  4. How an LLM Processes Input and Generates Output (Under the Hood)
&lt;/h2&gt;

&lt;p&gt;To understand how fine-tuning alters a model's behavior, we must first understand how an LLM natively receives text, processes it through its neural architecture, and generates a response.&lt;/p&gt;

&lt;h3&gt;
  
  
  The End-to-End Inference Lifecycle
&lt;/h3&gt;

&lt;p&gt;When you ask an LLM to evaluate an IELTS essay, the following cycle occurs:&lt;/p&gt;

&lt;h4&gt;
  
  
  Architectural Flow Breakdown:
&lt;/h4&gt;



&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    In["1. User Prompt&amp;lt;br/&amp;gt;(Raw String)"] --&amp;gt; Tok["2. Tokenizer&amp;lt;br/&amp;gt;(BPE / IDs)"]
    Tok --&amp;gt; Emb["3. Embedding&amp;lt;br/&amp;gt;+ RoPE"]
    Emb --&amp;gt; Trans["4. 28 Layers&amp;lt;br/&amp;gt;(Attention + LoRA)"]
    Trans --&amp;gt; Head["5. LM Head&amp;lt;br/&amp;gt;(Logits)"]
    Head --&amp;gt; Soft["6. Softmax&amp;lt;br/&amp;gt;(Temp T=0.3)"]
    Soft --&amp;gt; Sam["7. Next Token&amp;lt;br/&amp;gt;(Top-p=0.9)"]
    Sam --&amp;gt; Loop{"Stop Token&amp;lt;br/&amp;gt;&amp;amp;lt;|im_end|&amp;amp;gt;?"}
    Loop -- No --&amp;gt; Trans
    Loop -- Yes --&amp;gt; Out["8. Output Essay&amp;lt;br/&amp;gt;Review Report"]

    style In fill:#0f172a,stroke:#38bdf8,stroke-width:2px,color:#f8fafc;
    style Tok fill:#1e293b,stroke:#818cf8,stroke-width:2px,color:#f8fafc;
    style Emb fill:#1e293b,stroke:#a855f7,stroke-width:2px,color:#f8fafc;
    style Trans fill:#14532d,stroke:#4ade80,stroke-width:2px,color:#f8fafc;
    style Head fill:#1e293b,stroke:#38bdf8,stroke-width:2px,color:#f8fafc;
    style Soft fill:#7c2d12,stroke:#fb923c,stroke-width:2px,color:#f8fafc;
    style Sam fill:#581c87,stroke:#c084fc,stroke-width:2px,color:#f8fafc;
    style Loop fill:#334155,stroke:#94a3b8,stroke-width:2px,color:#f8fafc;
    style Out fill:#0f172a,stroke:#4ade80,stroke-width:2px,color:#f8fafc;&lt;/code&gt;&lt;/pre&gt;






&lt;h3&gt;
  
  
  The Tokenization Mechanism: From Words to Numbers
&lt;/h3&gt;

&lt;p&gt;Neural networks cannot process raw characters or words directly; they only perform linear algebra on numbers. &lt;/p&gt;

&lt;h4&gt;
  
  
  Why Not Words or Characters?
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Word-level tokenization:&lt;/strong&gt; The vocabulary would be infinite. It would fail completely on typos, hyphenated words, or domain jargon (e.g. &lt;em&gt;"lexicalization"&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Character-level tokenization:&lt;/strong&gt; Sequences become too long (each letter is an input), and individual characters convey minimal semantic meaning.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  The Modern Solution: Subword Tokenization (Byte-Pair Encoding - BPE)
&lt;/h4&gt;

&lt;p&gt;Modern models (like Qwen 2.5 and Llama 3) use &lt;strong&gt;Byte-Pair Encoding (BPE)&lt;/strong&gt;. Frequent words remain single tokens, while rare words are broken into meaningful subword chunks:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Original Word&lt;/th&gt;
&lt;th&gt;Subword Breakdown&lt;/th&gt;
&lt;th&gt;Why?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"education"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;["education"]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Common word; assigned its own dedicated token ID.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"unquestionably"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;["un", "question", "ably"]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Split into common prefix, root, and suffix tokens.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"overemphasizing"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;["over", "emphas", "izing"]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Common subword stems combined flexibly.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  Example: Tokenizing an IELTS Prompt
&lt;/h4&gt;

&lt;p&gt;When you send:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IELTS Writing Task 2: Plastic pollution is damaging oceans.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tokenizer consults its dictionary table and produces an array of integer &lt;strong&gt;Token IDs&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;38384&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;18274&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8431&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;220&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;27329&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;21890&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;374&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;21950&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;24590&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Special Tokens in ChatML Templates
&lt;/h4&gt;

&lt;p&gt;In conversational models, special delimiter tokens tell the model who is speaking. Without special tokens, the model cannot distinguish between instructions and candidate essays:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;|im_start|&amp;gt;system&lt;/code&gt; : Marks the start of the Senior Examiner rubric persona.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;|im_start|&amp;gt;user&lt;/code&gt; : Marks the candidate essay and prompt.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;|im_start|&amp;gt;assistant&lt;/code&gt; : Signals the model that it is now its turn to generate the score report.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;|im_end|&amp;gt;&lt;/code&gt; : &lt;strong&gt;The Stop Token.&lt;/strong&gt; Signals that generation is complete.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Internal Processing: Embeddings, Positional Encoding &amp;amp; Attention
&lt;/h3&gt;

&lt;p&gt;Once text is tokenized into IDs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Embedding Lookup Matrix (&lt;code&gt;W_e in R^(V * d_model)&lt;/code&gt;):&lt;/strong&gt;
Each token ID is mapped to a continuous, high-dimensional vector. For example, in an 8B model with &lt;code&gt;d_model = 4096&lt;/code&gt;, the integer &lt;code&gt;38384&lt;/code&gt; becomes a vector of 4,096 floating-point numbers encoding its base semantic meaning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rotary Positional Embeddings (RoPE):&lt;/strong&gt;
Self-attention on its own is order-agnostic ("dog bites man" would look identical to "man bites dog"). RoPE mathematically rotates the embedding vectors in complex coordinate space so the model knows the exact position and distance of each word in the sequence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-Head Self-Attention (The Core Engine):&lt;/strong&gt;
Each token projects into &lt;strong&gt;Query (&lt;code&gt;Q&lt;/code&gt;)&lt;/strong&gt;, &lt;strong&gt;Key (&lt;code&gt;K&lt;/code&gt;)&lt;/strong&gt;, and &lt;strong&gt;Value (&lt;code&gt;V&lt;/code&gt;)&lt;/strong&gt; vectors:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Attention(Q, K, V) = softmax((QK^T) / (sqrt(d_k)))V
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows the token &lt;code&gt;"congestion"&lt;/code&gt; to attend back to &lt;code&gt;"traffic"&lt;/code&gt; and forward to &lt;code&gt;"delays"&lt;/code&gt;, capturing deep contextual grammar and semantic dependencies across the entire essay.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Prediction Loop: Logits, Softmax &amp;amp; Autoregressive Decoding
&lt;/h3&gt;

&lt;p&gt;LLMs do not produce a whole paragraph in a single forward pass. They are &lt;strong&gt;autoregressive&lt;/strong&gt;: they generate text &lt;strong&gt;one single token at a time&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. From Hidden States to Logits
&lt;/h4&gt;

&lt;p&gt;After passing through all 28–36 transformer layers, the model extracts the final hidden state vector corresponding to the very last token.&lt;/p&gt;

&lt;p&gt;This vector passes through the &lt;strong&gt;Language Model Head (&lt;code&gt;lm_head&lt;/code&gt;)&lt;/strong&gt;, which calculates raw confidence scores (called &lt;strong&gt;logits&lt;/strong&gt;) for each of the 152,000+ words in the model's vocabulary.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. From Logits to Probabilities (Softmax)
&lt;/h4&gt;

&lt;p&gt;Because raw logits can be negative, zero, or arbitrary numbers, the model passes them through the &lt;strong&gt;Softmax function&lt;/strong&gt; to turn them into clean percentages (probabilities) between 0% and 100% that all add up to 1.0 (100%).&lt;/p&gt;

&lt;p&gt;In simple terms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Words with higher logit scores receive higher probabilities.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Temperature (&lt;code&gt;T&lt;/code&gt;)&lt;/strong&gt; setting controls how bold or conservative the model is: a low temperature concentrates probability onto the top word (more focused and deterministic), while a higher temperature spreads probability more evenly (more creative).&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Sampling Controls: Temperature, Top-p &amp;amp; Stop Tokens
&lt;/h3&gt;

&lt;p&gt;Once we have a probability distribution over the 152,000 candidate tokens, how do we pick the winner?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;Candidate Tokens:
  "###"        -&amp;gt; 64.2%  &amp;lt;-- Most likely next token!
  "Overall"    -&amp;gt; 21.5%
  "The"        -&amp;gt; 8.1%
  "Candidate"  -&amp;gt; 4.3%
  "banana"     -&amp;gt; 0.000001% (Cut off by Top-p)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  1. Temperature (&lt;code&gt;T&lt;/code&gt;)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Low Temperature (&lt;code&gt;T = 0.1 - 0.3&lt;/code&gt;):&lt;/strong&gt; Sharpen the distribution! The highest probability token becomes overwhelmingly favored. This provides &lt;strong&gt;deterministic, consistent grading&lt;/strong&gt;—essential for IELTS examiner scoring.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High Temperature (&lt;code&gt;T = 0.8 - 1.2&lt;/code&gt;):&lt;/strong&gt; Flatten the distribution! Lower-probability words get a higher chance to be chosen, creating creative, diverse storytelling (but increasing the risk of hallucinations).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Greedy Decoding (&lt;code&gt;T = 0&lt;/code&gt;):&lt;/strong&gt; Strictly pick the token with the highest probability (&lt;code&gt;argmax&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  2. Top-p (Nucleus Sampling)
&lt;/h4&gt;

&lt;p&gt;Instead of considering all 152,000 tokens, the model pools only the top tokens whose cumulative probability adds up to &lt;code&gt;p&lt;/code&gt; (e.g., &lt;code&gt;p = 0.90&lt;/code&gt;). The remaining 10% "long tail" of weird or irrelevant words is discarded, eliminating bizarre hallucinations.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Stop Tokens (&lt;code&gt;&amp;lt;|im_end|&amp;gt;&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;How does the generation loop know when to finish?&lt;br&gt;
When the model concludes its final revision paragraph, it predicts the special token &lt;code&gt;&amp;lt;|im_end|&amp;gt;&lt;/code&gt;. The inference engine recognizes this stop token, halts the loop, and triggers the &lt;strong&gt;Detokenizer&lt;/strong&gt; to convert all generated token IDs back into human-readable text on your screen.&lt;/p&gt;
&lt;h2&gt;
  
  
  5. Methods of Fine-Tuning
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Full Parameter Fine-Tuning (FPFT)
&lt;/h3&gt;

&lt;p&gt;In Full Parameter Fine-Tuning, every single parameter in the model is updated.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Problem:&lt;/strong&gt; An 8-Billion parameter model stored in 16-bit float requires:

&lt;ul&gt;
&lt;li&gt;Model weights: &lt;code&gt;8 * 2 GB = 16 GB&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Gradient tensors: &lt;code&gt;16 GB&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Optimizer states (AdamW tracks 2 momentum variables per weight): &lt;code&gt;8 * 8 GB = 64 GB&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Activations &amp;amp; buffers: &lt;code&gt;~ 16 GB&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Total VRAM required: &amp;gt; 110 GB!&lt;/strong&gt; (Requires multi-A100 server clusters).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Parameter-Efficient Fine-Tuning (PEFT) &amp;amp; LoRA
&lt;/h3&gt;

&lt;p&gt;In 2021, Microsoft researchers published &lt;strong&gt;LoRA (Low-Rank Adaptation)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The core insight: The weight updates &lt;code&gt;ΔW&lt;/code&gt; during domain adaptation have a low "intrinsic dimension" (rank). Instead of updating the full weight matrix &lt;code&gt;W_0 in R^(d * k)&lt;/code&gt;, LoRA decomposes the update into two low-rank matrices &lt;code&gt;A&lt;/code&gt; and &lt;code&gt;B&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ΔW = B * A
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where &lt;code&gt;B in R^(d * r)&lt;/code&gt; and &lt;code&gt;A in R^(r * k)&lt;/code&gt;, with rank &lt;code&gt;r &amp;lt;&amp;lt; \min(d, k)&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   Original Weight Matrix W (Frozen)            LoRA Adapter Matrices (Trainable)
        [ d x k ]                                    B [ d x r ]   x   A [ r x k ]
  (Millions of parameters)                           (e.g., r = 16: ~0.5% of parameters)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;During training, &lt;code&gt;W_0&lt;/code&gt; is frozen (requires no optimizer states).&lt;/li&gt;
&lt;li&gt;Only &lt;code&gt;A&lt;/code&gt; and &lt;code&gt;B&lt;/code&gt; receive gradient updates.&lt;/li&gt;
&lt;li&gt;Memory usage for optimizer states drops by &lt;strong&gt;over 95%&lt;/strong&gt;!&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  QLoRA: 4-bit Quantized LoRA
&lt;/h3&gt;

&lt;p&gt;Introduced by Dettmers et al. (2023), &lt;strong&gt;QLoRA&lt;/strong&gt; takes LoRA further by combining:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;4-bit NormalFloat (NF4):&lt;/strong&gt; Quantizes the frozen base model weights from 16-bit down to 4-bit without noticeable perplexity degradation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Double Quantization (DQ):&lt;/strong&gt; Quantizes the quantization constants themselves, saving an additional 0.37 bits per parameter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Paged Optimizers:&lt;/strong&gt; Uses CUDA Unified Memory to automatically page memory between GPU and CPU RAM during gradient spikes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With QLoRA, an 8-Billion parameter model requires only &lt;strong&gt;~5.5 GB VRAM&lt;/strong&gt; for weights, allowing full fine-tuning on a single &lt;strong&gt;free Google Colab T4 GPU (15 GB VRAM)&lt;/strong&gt;!&lt;/p&gt;

&lt;h3&gt;
  
  
  Hardware Comparison: FPFT vs. LoRA vs. QLoRA
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Technique&lt;/th&gt;
&lt;th&gt;Precision of Base Model&lt;/th&gt;
&lt;th&gt;Trainable Parameters&lt;/th&gt;
&lt;th&gt;GPU VRAM Required (8B Model)&lt;/th&gt;
&lt;th&gt;Can run on Free Colab T4?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Full Fine-Tuning&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;16-bit FP16/BF16&lt;/td&gt;
&lt;td&gt;100% (8.0B)&lt;/td&gt;
&lt;td&gt;~110 GB+&lt;/td&gt;
&lt;td&gt;❌ No (Requires multiple A100s)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Standard LoRA&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;16-bit FP16/BF16&lt;/td&gt;
&lt;td&gt;~0.5% – 2% (40M)&lt;/td&gt;
&lt;td&gt;~24 GB – 32 GB&lt;/td&gt;
&lt;td&gt;❌ No (Requires A10G / L4 / V100)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;QLoRA&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4-bit NF4&lt;/td&gt;
&lt;td&gt;~0.5% – 2% (40M)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~7 GB – 10 GB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ &lt;strong&gt;Yes (Runs smoothly on T4 15GB)&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  6. Essential Hyperparameters &amp;amp; Micro-Concepts
&lt;/h2&gt;

&lt;p&gt;When beginning LLM fine-tuning, you will encounter parameters and configuration options that look familiar from traditional machine learning, but carry distinct rules of thumb in modern LLM training.&lt;/p&gt;

&lt;h3&gt;
  
  
  Epochs vs. Steps vs. Batch Size
&lt;/h3&gt;

&lt;p&gt;To understand how fine-tuning progresses, you must grasp how data flows into the model:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    Dataset["Full Dataset&amp;lt;br/&amp;gt;(800 Samples)"] --&amp;gt; Split["Split into Mini-Batches"]
    Split --&amp;gt; Step1["Step 1: 8 Samples&amp;lt;br/&amp;gt;Forward + Update"]
    Step1 --&amp;gt; Step2["Step 2: 8 Samples&amp;lt;br/&amp;gt;Forward + Update"]
    Step2 --&amp;gt; Dots["... 100 Total Steps ..."]
    Dots --&amp;gt; EpochEnd["All 800 Seen&amp;lt;br/&amp;gt;= 1 Complete Epoch"]

    style Dataset fill:#0369a1,stroke:#38bdf8,stroke-width:2px,color:#ffffff;
    style Split fill:#1e293b,stroke:#94a3b8,stroke-width:2px,color:#ffffff;
    style Step1 fill:#1e293b,stroke:#64748b,stroke-width:2px,color:#ffffff;
    style Step2 fill:#1e293b,stroke:#64748b,stroke-width:2px,color:#ffffff;
    style Dots fill:#1e293b,stroke:#64748b,stroke-width:2px,color:#ffffff;
    style EpochEnd fill:#15803d,stroke:#4ade80,stroke-width:2px,color:#ffffff;&lt;/code&gt;&lt;/pre&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Epoch:&lt;/strong&gt; &lt;strong&gt;One complete cycle&lt;/strong&gt; through the entire training dataset. If your dataset has 1,000 examples, 1 epoch means the model has seen all 1,000 examples exactly once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step (Iteration):&lt;/strong&gt; &lt;strong&gt;A single weight update&lt;/strong&gt;. One step consists of:

&lt;ol&gt;
&lt;li&gt;Passing a batch of data through the model (&lt;em&gt;Forward Pass&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;Calculating loss and computing gradients (&lt;em&gt;Backward Pass&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;Updating the model weights using the optimizer.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The simple calculation formula is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Total Training Steps&lt;/strong&gt; = &lt;code&gt;(Total Examples * Number of Epochs) / Effective Batch Size&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Example:&lt;/em&gt; If you have 800 essays, train for 3 epochs, and use an effective batch size of 8:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Total Steps&lt;/strong&gt; = &lt;code&gt;(800 * 3) / 8 = 300 steps&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Gradient Accumulation &amp;amp; Effective Batch Size
&lt;/h3&gt;

&lt;p&gt;On consumer GPUs like the Colab T4 (15 GB VRAM), setting a physical batch size of 16 or 32 for an 8B model will immediately trigger an &lt;strong&gt;Out-Of-Memory (OOM)&lt;/strong&gt; error. &lt;/p&gt;

&lt;p&gt;However, a tiny batch size (like 1 or 2) makes the gradient updates noisy. How do we solve this? &lt;strong&gt;Gradient Accumulation.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Per-Device Batch Size (&lt;code&gt;B_device&lt;/code&gt;):&lt;/strong&gt; The number of samples loaded into GPU memory at any single moment (e.g., 2).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gradient Accumulation Steps (&lt;code&gt;G&lt;/code&gt;):&lt;/strong&gt; Instead of updating weights after every single batch, the optimizer computes gradients for &lt;code&gt;G&lt;/code&gt; batches, &lt;em&gt;accumulates (sums) the gradients in memory&lt;/em&gt;, and only updates the weights once every &lt;code&gt;G&lt;/code&gt; steps!&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Effective Batch Size&lt;/strong&gt; = &lt;code&gt;Per-Device Batch Size * Gradient Accumulation Steps * Number of GPUs&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  7. When is Fine-Tuning Useful? (And When is it Not?)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Best Scenarios for Fine-Tuning
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Domain Assessment &amp;amp; Scoring:&lt;/strong&gt; e.g., IELTS, TOEFL, SAT essays, or coding interview grading adhering to specific rubrics.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complex Schema Adherence:&lt;/strong&gt; Enforcing strict, unvarying JSON, YAML, or XML output without preamble or conversational fluff.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Domain Vocabulary &amp;amp; Tone:&lt;/strong&gt; Medical triage dialogues, legal clause rewriting, financial auditing tone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latency &amp;amp; Edge Deployments:&lt;/strong&gt; Shrinking prompt sizes so a compact model can be deployed locally on laptops with Ollama.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Scenarios Where You Should NOT Fine-Tune
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic / Fast-Changing Knowledge:&lt;/strong&gt; Stock prices, company product catalogs, weather, news. &lt;em&gt;Use RAG instead.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;General Fact Retrieval:&lt;/strong&gt; Fine-tuning on facts frequently induces subtle hallucinations. &lt;em&gt;Use RAG instead.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Insufficient Data:&lt;/strong&gt; If you have fewer than 100 examples, few-shot prompting in Claude or GPT-4 will almost always outperform a fine-tuned model.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  8. Summary &amp;amp; What's Next
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Core Concept&lt;/th&gt;
&lt;th&gt;Summary Takeaway&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;What is it?&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Adapting model weights to internalize style, structure, and specialized reasoning.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RAG vs. FT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;RAG brings the facts (open book); Fine-Tuning shapes the behavior and skill (retained mastery).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Epochs &amp;amp; Steps&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;LLMs only need 1–3 epochs; gradient accumulation achieves large effective batch sizes on limited VRAM.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In &lt;strong&gt;&lt;a href="https://dev.to/longtk26/step-by-step-guide-fine-tuning-an-ielts-examiner-llm-with-unsloth-qlora-20a1"&gt;Part 2: Step-by-Step Implementation Guide&lt;/a&gt;&lt;/strong&gt;, we will implement a complete end-to-end training pipeline using &lt;strong&gt;Unsloth&lt;/strong&gt;, &lt;strong&gt;QLoRA&lt;/strong&gt;, and &lt;strong&gt;Google Colab&lt;/strong&gt;, training our own custom &lt;strong&gt;IELTS Examiner AI&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>finetuning</category>
      <category>llm</category>
    </item>
    <item>
      <title>Understanding Observability Stack with Grafana, Prometheus, Loki, and Alloy</title>
      <dc:creator>longtk26</dc:creator>
      <pubDate>Thu, 27 Aug 2026 17:09:00 +0000</pubDate>
      <link>https://dev.to/longtk26/understanding-observability-stack-with-grafana-prometheus-loki-and-alloy-16p8</link>
      <guid>https://dev.to/longtk26/understanding-observability-stack-with-grafana-prometheus-loki-and-alloy-16p8</guid>
      <description>&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
Understanding Observability Stack with Grafana, Prometheus, Loki, and Alloy

&lt;ul&gt;
&lt;li&gt;Table of Contents&lt;/li&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;How Observability Components Fit Together&lt;/li&gt;
&lt;li&gt;Simple Data Flow&lt;/li&gt;
&lt;li&gt;Core Stack Components &amp;amp; Their Usages&lt;/li&gt;
&lt;li&gt;1. Prometheus: Metrics Database&lt;/li&gt;
&lt;li&gt;2. Grafana Loki: Logs Database&lt;/li&gt;
&lt;li&gt;3. Grafana: Dashboards &amp;amp; Visualization&lt;/li&gt;
&lt;li&gt;4. Exporters: System Metric Translators&lt;/li&gt;
&lt;li&gt;5. Grafana Alloy: Telemetry Collector&lt;/li&gt;
&lt;li&gt;Component Summary Table&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;When you deploy applications to a server, you need a reliable way to know if everything is running smoothly. This is where &lt;strong&gt;observability&lt;/strong&gt; comes in.&lt;/p&gt;

&lt;p&gt;Observability helps you answer simple but essential questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the server running out of CPU or memory?&lt;/li&gt;
&lt;li&gt;How many requests per second is the API handling?&lt;/li&gt;
&lt;li&gt;Why did a user get an error, and what exact line in the logs explains it?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In a modern observability stack, telemetry is primarily divided into two main types of data:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Metrics&lt;/strong&gt;: Numerical values recorded over time (e.g., CPU usage %, memory in MB, request count, response time).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logs&lt;/strong&gt;: Timestamped text messages output by applications or the operating system (e.g., error stack traces, informational events).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To collect, store, and visualize this data, we combine five core open-source components: &lt;strong&gt;Prometheus&lt;/strong&gt;, &lt;strong&gt;Grafana Loki&lt;/strong&gt;, &lt;strong&gt;Grafana&lt;/strong&gt;, &lt;strong&gt;Exporters&lt;/strong&gt;, and &lt;strong&gt;Grafana Alloy&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Observability Components Fit Together
&lt;/h2&gt;

&lt;p&gt;Here is a simple overview of how telemetry data moves from your applications all the way to your browser:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fee01q1m5ux0kaifolild.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fee01q1m5ux0kaifolild.png" alt="Simple Observability Architecture" width="800" height="165"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Simple Data Flow
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Emit Data&lt;/strong&gt;: Applications and system exporters produce metrics and log messages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collect&lt;/strong&gt;: &lt;strong&gt;Grafana Alloy&lt;/strong&gt; runs locally, gathers the metrics and logs, adds useful labels (such as &lt;code&gt;app="my-api"&lt;/code&gt;), and forwards them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Store&lt;/strong&gt;: 

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prometheus&lt;/strong&gt; receives and stores the numerical metrics.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loki&lt;/strong&gt; receives and stores the log entries.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visualize&lt;/strong&gt;: &lt;strong&gt;Grafana&lt;/strong&gt; connects to both Prometheus and Loki, allowing you to view graphs, inspect logs, and set up alerts on a single screen.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Core Stack Components &amp;amp; Their Usages
&lt;/h2&gt;

&lt;p&gt;Let's break down each component and what it is used for.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Prometheus: Metrics Database
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://prometheus.io/" rel="noopener noreferrer"&gt;Prometheus&lt;/a&gt; is an open-source database built specifically for &lt;strong&gt;time-series metrics&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What it does&lt;/strong&gt;: Stores numbers paired with timestamps and labels.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Example Data&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  http_requests_total{app="web", status="200"} 1540
  cpu_usage_percentage{host="server-1"} 42.5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;How you query it&lt;/strong&gt;: Uses &lt;strong&gt;PromQL&lt;/strong&gt; (Prometheus Query Language) to compute rates, averages, and totals over time:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  rate(http_requests_total[5m])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Main Usage&lt;/strong&gt;: Measuring CPU/RAM usage, request throughput, error counts, and response latency.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  2. Grafana Loki: Logs Database
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://grafana.com/oss/loki/" rel="noopener noreferrer"&gt;Grafana Loki&lt;/a&gt; is an open-source log aggregation system designed by Grafana.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What it does&lt;/strong&gt;: Stores text log streams from all your applications and containers in one searchable place.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Key Advantage&lt;/strong&gt;: Unlike traditional search engines that index every single word, Loki indexes only the &lt;strong&gt;labels&lt;/strong&gt; (like &lt;code&gt;app="backend"&lt;/code&gt;, &lt;code&gt;env="prod"&lt;/code&gt;). This makes it fast, lightweight, and very cheap on disk and memory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How you query it&lt;/strong&gt;: Uses &lt;strong&gt;LogQL&lt;/strong&gt; to search and filter log lines:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  {app="backend"} |= "error"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Main Usage&lt;/strong&gt;: Searching application logs, investigating exceptions, and reviewing debug traces.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  3. Grafana: Dashboards &amp;amp; Visualization
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://grafana.com/" rel="noopener noreferrer"&gt;Grafana&lt;/a&gt; is the web UI where you view and interact with all your monitoring data.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What it does&lt;/strong&gt;: Connects to data sources like Prometheus and Loki to render graphs, charts, counters, and log panels.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Key Features&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unified Dashboards&lt;/strong&gt;: Display CPU graphs, database queries, and live error logs side-by-side.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alerting&lt;/strong&gt;: Sends notifications to Slack, Discord, or email when thresholds are exceeded (e.g., when CPU &amp;gt; 90%).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explore Mode&lt;/strong&gt;: Quickly run ad-hoc queries without needing to create a permanent dashboard.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Main Usage&lt;/strong&gt;: The central control room for developers and operators to monitor system health.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  4. Exporters: System Metric Translators
&lt;/h3&gt;

&lt;p&gt;Software like Linux OS, PostgreSQL, or Redis does not natively output Prometheus metrics. &lt;strong&gt;Exporters&lt;/strong&gt; act as translators: they read system stats and expose them as a &lt;code&gt;/metrics&lt;/code&gt; page that Prometheus can read.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Node Exporter&lt;/strong&gt;: Measures host-level stats (OS CPU, RAM, disk space, network traffic).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;cAdvisor (Container Advisor)&lt;/strong&gt;: Measures Docker container stats (per-container CPU, RAM limits, container status).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database Exporters (Postgres / Redis Exporters)&lt;/strong&gt;: Measure active database connections, query throughput, and cache hit rates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Main Usage&lt;/strong&gt;: Providing visibility into servers, containers, and databases without modifying their source code.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  5. Grafana Alloy: Telemetry Collector
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://grafana.com/docs/alloy/latest/" rel="noopener noreferrer"&gt;Grafana Alloy&lt;/a&gt; is Grafana's modern, lightweight telemetry agent.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What it does&lt;/strong&gt;: Runs on the server where your applications live. It gathers &lt;code&gt;/metrics&lt;/code&gt; from apps and exporters, tails log files from Docker containers, and pushes everything to Prometheus and Loki.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why it is useful&lt;/strong&gt;: Instead of installing separate tools for metrics and logs (like older setups with Promtail and Prometheus Agent), Alloy handles both in a single lightweight program.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Main Usage&lt;/strong&gt;: Local scraping, enriching data with common labels, and shipping telemetry to the databases.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Component Summary Table
&lt;/h2&gt;

&lt;p&gt;Here is a quick summary of all five components:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;What It Does&lt;/th&gt;
&lt;th&gt;Telemetry Type&lt;/th&gt;
&lt;th&gt;Query Language&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Prometheus&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Metrics Database&lt;/td&gt;
&lt;td&gt;Stores numerical measurements over time&lt;/td&gt;
&lt;td&gt;Metrics&lt;/td&gt;
&lt;td&gt;PromQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Grafana Loki&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Logs Database&lt;/td&gt;
&lt;td&gt;Stores and indexes log streams&lt;/td&gt;
&lt;td&gt;Logs&lt;/td&gt;
&lt;td&gt;LogQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Grafana&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Visualization UI&lt;/td&gt;
&lt;td&gt;Displays charts, graphs, and sends alerts&lt;/td&gt;
&lt;td&gt;Dashboards &amp;amp; Alerts&lt;/td&gt;
&lt;td&gt;N/A (Uses PromQL &amp;amp; LogQL)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Exporters&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Metric Translators&lt;/td&gt;
&lt;td&gt;Reads OS, Docker, and DB stats and outputs metrics&lt;/td&gt;
&lt;td&gt;Metrics&lt;/td&gt;
&lt;td&gt;N/A (HTTP &lt;code&gt;/metrics&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Grafana Alloy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Telemetry Collector&lt;/td&gt;
&lt;td&gt;Scrapes metrics and tails logs, then pushes to databases&lt;/td&gt;
&lt;td&gt;Metrics &amp;amp; Logs&lt;/td&gt;
&lt;td&gt;N/A (Agent Configuration)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I hope this blog is helpful to you.&lt;br&gt;
Understanding these five fundamental building blocks, you can get started with modern observability. Thank you for reading.&lt;/p&gt;

</description>
      <category>observability</category>
      <category>grafana</category>
      <category>prometheus</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>Overview: Change Data Capture (CDC)</title>
      <dc:creator>longtk26</dc:creator>
      <pubDate>Fri, 17 Apr 2026 14:03:41 +0000</pubDate>
      <link>https://dev.to/longtk26/overview-change-data-capture-cdc-4d42</link>
      <guid>https://dev.to/longtk26/overview-change-data-capture-cdc-4d42</guid>
      <description>&lt;h1&gt;
  
  
  Overview: Change Data Capture (CDC)
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;Welcome! In this post, we'll explore &lt;strong&gt;Change Data Capture (CDC)&lt;/strong&gt; — one of those foundational concepts that can genuinely transform the way you think about data synchronization in distributed systems. Let's dive in!&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;What is Change Data Capture?&lt;/li&gt;
&lt;li&gt;
Use Cases

&lt;ul&gt;
&lt;li&gt;Cache Invalidation&lt;/li&gt;
&lt;li&gt;Data Integration&lt;/li&gt;
&lt;li&gt;Real-Time Analysis&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

Pros and Cons

&lt;ul&gt;
&lt;li&gt;Pros&lt;/li&gt;
&lt;li&gt;Cons&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;When Should You Use CDC?&lt;/li&gt;

&lt;li&gt;When Should You NOT Use CDC?&lt;/li&gt;

&lt;li&gt;Wrapping Up&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  What is Change Data Capture?
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Change Data Capture (CDC)&lt;/strong&gt; is a method used in databases to track and record changes made to data.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;CDC captures modifications like inserts, updates, and deletes, and makes them available for downstream systems to consume — either for analysis, replication, or synchronization purposes. CDC helps maintain data consistency across different systems by recording alterations in real time.&lt;/p&gt;

&lt;p&gt;Think of it like a digital detective sitting quietly inside your database, logging every change that happens and when it occurred — so that other systems never fall behind.&lt;/p&gt;




&lt;h2&gt;
  
  
  Use Cases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Cache Invalidation
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Automatically invalidate entries in a cache as soon as the corresponding record changes or is removed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If your cache (such as Redis, Memcached, or Infinispan) runs as a separate process, the invalidation logic can be placed into a dedicated service — completely decoupled from your main application. In more advanced setups, you can even use the data from the change event itself to update the affected cache entries directly, rather than just evicting them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Integration
&lt;/h3&gt;

&lt;p&gt;Data is often stored in multiple places, especially when used for different purposes or in slightly different shapes. Keeping those multiple systems in sync can quickly become a maintenance nightmare.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt; When a system uses a separate search service like Elasticsearch, it's critical to keep the search index synchronized with the primary database whenever records are created or updated.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;CDC makes this elegant: changes are captured from the database and automatically propagated to the search service. Instead of sprinkling Elasticsearch update calls throughout your business logic, a lightweight event-processing layer handles it asynchronously. This simplifies the application design while improving scalability and maintainability.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real-Time Analysis
&lt;/h3&gt;

&lt;p&gt;CDC powers real-time analytics platforms by continuously feeding data changes into analytical systems. Organizations can then derive insights from fresh data and respond swiftly to changing conditions — all without expensive batch jobs or manual ETL pipelines.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pros and Cons
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Pros
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Decouples Application Logic
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;The main application no longer needs to explicitly publish events or sync data to downstream systems.&lt;/li&gt;
&lt;li&gt;Reduces duplicated logic across services — one source of truth emits events, and consumers react independently.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  2. Near Real-Time Data Synchronization
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Changes from the database are streamed almost instantly.&lt;/li&gt;
&lt;li&gt;Ideal for search indexing, analytics pipelines, and cache management.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  3. Non-Intrusive to Existing Systems
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Works at the database level (via WAL / binlog), so there's usually no need to heavily modify existing application code.&lt;/li&gt;
&lt;li&gt;Particularly useful for integrating with legacy systems where source code changes are risky or costly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  4. Event-Driven Architecture Enablement
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Naturally integrates with streaming platforms like Apache Kafka.&lt;/li&gt;
&lt;li&gt;Makes it easier to build reactive microservices and real-time data pipelines.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  5. Data Consistency (Eventually Consistent)
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Ensures downstream systems gradually converge toward the source of truth over time.&lt;/li&gt;
&lt;li&gt;Change events are durable and ordered, making them reliable for replication.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  6. Supports Multiple Consumers
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;One stream of change events can feed multiple services simultaneously — search indexing, analytics, auditing, cache invalidation, and more.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cons
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Increased System Complexity
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Requires additional infrastructure: a message broker (e.g., Kafka), connectors, and monitoring tooling.&lt;/li&gt;
&lt;li&gt;Debugging issues that span multiple components can be significantly harder than debugging a simple API call.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  2. Schema Evolution Challenges
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Changes in the database schema (adding/removing columns, renaming tables) can break downstream consumers if not handled carefully.&lt;/li&gt;
&lt;li&gt;A solid schema management strategy — such as a schema registry — becomes essential at scale.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  3. Operational Overhead
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Someone needs to manage connectors, track offsets, handle failures, and configure retries.&lt;/li&gt;
&lt;li&gt;This requires a level of DevOps maturity that smaller teams may not yet have.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  4. Learning Curve
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Teams need to invest time in understanding event-driven design principles, CDC concepts, and the tooling involved.&lt;/li&gt;
&lt;li&gt;The mental model shift from "call an API" to "react to events" can take time to internalize.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  5. Data Filtering and Transformation Are Limited at the Source
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Raw database change events may not perfectly match what downstream consumers need.&lt;/li&gt;
&lt;li&gt;An additional transformation or processing layer is often required to shape, filter, or enrich events before they reach their destination.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  When Should You Use CDC?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Use CDC when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You need to sync data to systems like Elasticsearch, a data warehouse, or a cache layer&lt;/li&gt;
&lt;li&gt;You are building an event-driven or microservices architecture&lt;/li&gt;
&lt;li&gt;You want to reduce coupling between services&lt;/li&gt;
&lt;li&gt;You need audit logs or historical change tracking&lt;/li&gt;
&lt;li&gt;You have multiple downstream consumers of the same data&lt;/li&gt;
&lt;li&gt;You want near real-time data propagation without modifying core application logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;** Examples:**&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sync user records from PostgreSQL → Elasticsearch for full-text search&lt;/li&gt;
&lt;li&gt;Stream order events into an analytics pipeline for real-time dashboards&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  When Should You NOT Use CDC?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Avoid CDC when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Your system requires &lt;strong&gt;strong consistency&lt;/strong&gt; — CDC is eventually consistent and introduces some propagation delay&lt;/li&gt;
&lt;li&gt;The system is &lt;strong&gt;simple or monolithic&lt;/strong&gt; and doesn't benefit from event streaming&lt;/li&gt;
&lt;li&gt;You only have &lt;strong&gt;one consumer&lt;/strong&gt; and a direct API call or a database trigger would suffice&lt;/li&gt;
&lt;li&gt;Your team lacks experience in operating distributed systems&lt;/li&gt;
&lt;li&gt;Infrastructure cost and operational complexity are a concern&lt;/li&gt;
&lt;li&gt;Your database is already under heavy load (CDC adds overhead via WAL reading)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Examples:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A simple CRUD app with basic search — just update Elasticsearch directly from the application&lt;/li&gt;
&lt;li&gt;A low-scale system with no real-time data requirements&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;CDC is a powerful pattern, but like any tool, it shines brightest when applied to the right problem. If you are building systems where data needs to flow reliably across multiple components in near real time, CDC is absolutely worth the investment.&lt;/p&gt;

&lt;p&gt;Thank you so much for reading! I hope this overview gave you a clear and practical understanding of Change Data Capture. If you found this helpful, feel free to explore the &lt;a href="https://dev.to/longtk26/debezium-cdc-in-practice-1h1e"&gt;Debezium setup guide&lt;/a&gt; to see how to put these concepts into practice with real infrastructure. Happy building! 🚀&lt;/p&gt;

</description>
      <category>database</category>
      <category>dataengineering</category>
      <category>distributedsystems</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>Debezium: CDC in Practice</title>
      <dc:creator>longtk26</dc:creator>
      <pubDate>Fri, 17 Apr 2026 14:03:35 +0000</pubDate>
      <link>https://dev.to/longtk26/debezium-cdc-in-practice-1h1e</link>
      <guid>https://dev.to/longtk26/debezium-cdc-in-practice-1h1e</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;If you've read the &lt;a href="https://dev.to/longtk26/overview-change-data-capture-cdc-4d42"&gt;Overview CDC&lt;/a&gt; post, you already know &lt;em&gt;why&lt;/em&gt; Change Data Capture is such a compelling pattern. Now it's time to get our hands dirty. In this guide, we'll walk through a complete, working Debezium setup — from standing up Kafka and Debezium Connect with Docker, all the way to watching live database changes flow into Elasticsearch in real time. Let's go! 🛠️&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;What is Debezium?&lt;/li&gt;
&lt;li&gt;
Setup: PostgreSQL Connector for Tracking Database Changes

&lt;ul&gt;
&lt;li&gt;Step 1 – Create the Docker Network&lt;/li&gt;
&lt;li&gt;Step 2 – Prepare the Custom Debezium Connect Image&lt;/li&gt;
&lt;li&gt;Step 3 – Prepare the Docker Compose File&lt;/li&gt;
&lt;li&gt;Step 4 – Start the Services&lt;/li&gt;
&lt;li&gt;Step 5 – Configure the PostgreSQL Connector via Debezium UI&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

Setup: Synchronization from PostgreSQL to Elasticsearch

&lt;ul&gt;
&lt;li&gt;Step 1 – Start Elasticsearch and Kibana&lt;/li&gt;
&lt;li&gt;Step 2 – Register the Connectors&lt;/li&gt;
&lt;li&gt;Step 3 – Verify the Pipeline&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Wrapping Up&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  What is Debezium?
&lt;/h2&gt;

&lt;p&gt;Debezium is an open-source project that provides a &lt;strong&gt;low-latency data streaming platform for Change Data Capture (CDC)&lt;/strong&gt;. It connects to your database, monitors its transaction log (e.g., PostgreSQL's Write-Ahead Log), and emits a stream of change events for every row-level modification.&lt;/p&gt;

&lt;p&gt;Your applications then consume these events and react accordingly — syncing to a search index, invalidating a cache, updating a data warehouse, or triggering downstream workflows.&lt;/p&gt;

&lt;p&gt;Key characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Open source&lt;/strong&gt; under the Apache 2.0 license&lt;/li&gt;
&lt;li&gt;Built on top of &lt;strong&gt;Kafka Connect&lt;/strong&gt; — battle-tested, scalable, and extensible&lt;/li&gt;
&lt;li&gt;Supports many popular databases: &lt;strong&gt;PostgreSQL&lt;/strong&gt;, MySQL, MongoDB, SQL Server, Oracle, and more&lt;/li&gt;
&lt;li&gt;Guarantees &lt;strong&gt;at-least-once delivery&lt;/strong&gt; of change events with configurable offset management&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Setup: PostgreSQL Connector for Tracking Database Changes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1 – Create the Docker Network
&lt;/h3&gt;

&lt;p&gt;All services in this setup share a common Docker bridge network. Create it once before starting any containers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker network create base-network
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2 – Prepare the Custom Debezium Connect Image
&lt;/h3&gt;

&lt;p&gt;We need a custom Debezium Connect image that includes the &lt;strong&gt;Confluent Elasticsearch Sink Connector&lt;/strong&gt; plugin. Create a &lt;code&gt;Dockerfile&lt;/code&gt; inside a folder named &lt;code&gt;connect/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; debezium/connect:3.0.0.Final&lt;/span&gt;

&lt;span class="c"&gt;# Install the Confluent Elasticsearch Sink Connector for Kafka Connect.&lt;/span&gt;

&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; ELASTICSEARCH_SINK_VERSION=15.1.1&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-eux&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;
    curl -fsSL "https://hub-downloads.confluent.io/api/plugins/confluentinc/kafka-connect-elasticsearch/versions/${ELASTICSEARCH_SINK_VERSION}/confluentinc-kafka-connect-elasticsearch-${ELASTICSEARCH_SINK_VERSION}.zip" -o /tmp/es-sink.zip; \

    mkdir -p /kafka/connect/confluentinc-kafka-connect-elasticsearch; \

    unzip -q /tmp/es-sink.zip -d /kafka/connect/confluentinc-kafka-connect-elasticsearch; \

    rm -f /tmp/es-sink.zip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; If you don't need Elasticsearch, skip the custom image and use the official &lt;code&gt;debezium/connect:3.0.0.Final&lt;/code&gt; image directly in your Compose file.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step 3 – Prepare the Docker Compose File
&lt;/h3&gt;

&lt;p&gt;Create a &lt;code&gt;docker-compose.yml&lt;/code&gt; at the project root with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.9'&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;kafka-ui&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kafka-ui&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/kafbat/kafka-ui:latest&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8070:8080"&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;kafka&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;KAFKA_CLUSTERS_0_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;local&lt;/span&gt;
      &lt;span class="na"&gt;KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kafka:9092&lt;/span&gt;
      &lt;span class="na"&gt;DYNAMIC_CONFIG_ENABLED&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;true'&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;base-network&lt;/span&gt;

  &lt;span class="na"&gt;kafka&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apache/kafka:4.0.2&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kafka&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9092:9092"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9094:9094"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

      &lt;span class="c1"&gt;############################################&lt;/span&gt;
      &lt;span class="c1"&gt;# KRaft Metadata &amp;amp; Node Identity&lt;/span&gt;
      &lt;span class="c1"&gt;############################################&lt;/span&gt;

      &lt;span class="c1"&gt;# Unique ID for this Kafka node&lt;/span&gt;
      &lt;span class="na"&gt;KAFKA_NODE_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;

      &lt;span class="c1"&gt;# Unique Kafka cluster identifier (required for KRaft mode)&lt;/span&gt;
      &lt;span class="na"&gt;KAFKA_CLUSTER_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;energy-tracker-cluster-1'&lt;/span&gt;

      &lt;span class="c1"&gt;# Node roles — this instance acts as BOTH a broker and a controller&lt;/span&gt;
      &lt;span class="na"&gt;KAFKA_PROCESS_ROLES&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;broker,controller'&lt;/span&gt;

      &lt;span class="c1"&gt;############################################&lt;/span&gt;
      &lt;span class="c1"&gt;# Controller Quorum Configuration (Raft)&lt;/span&gt;
      &lt;span class="c1"&gt;############################################&lt;/span&gt;

      &lt;span class="c1"&gt;# List of controller nodes that form the Raft quorum&lt;/span&gt;
      &lt;span class="na"&gt;KAFKA_CONTROLLER_QUORUM_VOTERS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1@kafka:9093'&lt;/span&gt;

      &lt;span class="c1"&gt;# Listener name used by controllers for internal Raft communication&lt;/span&gt;
      &lt;span class="na"&gt;KAFKA_CONTROLLER_LISTENER_NAMES&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;CONTROLLER'&lt;/span&gt;

      &lt;span class="c1"&gt;############################################&lt;/span&gt;
      &lt;span class="c1"&gt;# Network Listeners (Internal, External &amp;amp; Controller)&lt;/span&gt;
      &lt;span class="c1"&gt;############################################&lt;/span&gt;

      &lt;span class="c1"&gt;# Define all listener endpoints for this Kafka node&lt;/span&gt;
      &lt;span class="na"&gt;KAFKA_LISTENERS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="s"&gt;PLAINTEXT://0.0.0.0:9092,&lt;/span&gt;
        &lt;span class="s"&gt;EXTERNAL://0.0.0.0:9094,&lt;/span&gt;
        &lt;span class="s"&gt;CONTROLLER://0.0.0.0:9093&lt;/span&gt;

      &lt;span class="c1"&gt;# What each listener advertises to connecting clients&lt;/span&gt;
      &lt;span class="c1"&gt;# Internal (Docker containers): kafka:9092&lt;/span&gt;
      &lt;span class="c1"&gt;# External (host machine): localhost:9094&lt;/span&gt;
      &lt;span class="na"&gt;KAFKA_ADVERTISED_LISTENERS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="s"&gt;PLAINTEXT://kafka:9092,&lt;/span&gt;
        &lt;span class="s"&gt;EXTERNAL://localhost:9094&lt;/span&gt;

      &lt;span class="c1"&gt;# Maps listener names to their security protocols&lt;/span&gt;
      &lt;span class="na"&gt;KAFKA_LISTENER_SECURITY_PROTOCOL_MAP&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="s"&gt;CONTROLLER:PLAINTEXT,&lt;/span&gt;
        &lt;span class="s"&gt;PLAINTEXT:PLAINTEXT,&lt;/span&gt;
        &lt;span class="s"&gt;EXTERNAL:PLAINTEXT&lt;/span&gt;

      &lt;span class="c1"&gt;# Which listener brokers use for internal broker-to-broker communication&lt;/span&gt;
      &lt;span class="na"&gt;KAFKA_INTER_BROKER_LISTENER_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;PLAINTEXT'&lt;/span&gt;

      &lt;span class="c1"&gt;############################################&lt;/span&gt;
      &lt;span class="c1"&gt;# Single-Node Cluster Safety Settings&lt;/span&gt;
      &lt;span class="c1"&gt;# (replication factor &amp;gt; 1 would fail on a single node)&lt;/span&gt;
      &lt;span class="c1"&gt;############################################&lt;/span&gt;
      &lt;span class="na"&gt;KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
      &lt;span class="na"&gt;KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
      &lt;span class="na"&gt;KAFKA_TRANSACTION_STATE_LOG_MIN_ISR&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;

      &lt;span class="c1"&gt;############################################&lt;/span&gt;
      &lt;span class="c1"&gt;# Broker Defaults &amp;amp; Quality-of-Life Settings&lt;/span&gt;
      &lt;span class="c1"&gt;############################################&lt;/span&gt;

      &lt;span class="c1"&gt;# Speed up consumer group startup&lt;/span&gt;
      &lt;span class="na"&gt;KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;

      &lt;span class="c1"&gt;# Auto-create topics if they don't already exist&lt;/span&gt;
      &lt;span class="na"&gt;KAFKA_AUTO_CREATE_TOPICS_ENABLE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;true'&lt;/span&gt;

      &lt;span class="c1"&gt;# Default number of partitions per topic&lt;/span&gt;
      &lt;span class="na"&gt;KAFKA_NUM_PARTITIONS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;

    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;base-network&lt;/span&gt;

  &lt;span class="na"&gt;debezium-connect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./connect&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;debezium-connect&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8083:8083"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;BOOTSTRAP_SERVERS=kafka:9092&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;GROUP_ID=1&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CONFIG_STORAGE_TOPIC=my_connect_configs&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;OFFSET_STORAGE_TOPIC=my_connect_offsets&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;STATUS_STORAGE_TOPIC=my_connect_statuses&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CONFIG_STORAGE_REPLICATION_FACTOR=1&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;OFFSET_STORAGE_REPLICATION_FACTOR=1&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;STATUS_STORAGE_REPLICATION_FACTOR=1&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REST_ADVERTISED_HOST_NAME=debezium-connect&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;KEY_CONVERTER=org.apache.kafka.connect.storage.StringConverter&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;VALUE_CONVERTER=org.apache.kafka.connect.json.JsonConverter&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;VALUE_CONVERTER_SCHEMAS_ENABLE=false&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PLUGIN_PATH=/kafka/connect,/usr/share/java,/usr/share/confluent-hub-components&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;kafka&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;base-network&lt;/span&gt;

  &lt;span class="na"&gt;debezium-ui&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;debezium-ui&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;debezium/debezium-ui&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8080:8080"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;KAFKA_CONNECT_URIS=http://debezium-connect:8083&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;debezium-connect&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;base-network&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;base-network&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Service summary:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;debezium-connect&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Hosts the Kafka Connect worker. Connectors registered here read the PostgreSQL WAL and publish change events to Kafka topics.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;debezium-ui&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A web UI that simplifies connector management by wrapping the Debezium Connect REST API.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kafka&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The message broker that durably stores change event streams. Runs in KRaft mode (no Zookeeper required).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kafka-ui&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A web UI for browsing Kafka topics, messages, and consumer groups — invaluable for debugging.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Step 4 – Start the Services
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the containers are healthy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Debezium UI&lt;/strong&gt; → &lt;code&gt;http://localhost:8080&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kafka UI&lt;/strong&gt; → &lt;code&gt;http://localhost:8070&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 5 – Configure the PostgreSQL Connector via Debezium UI
&lt;/h3&gt;

&lt;p&gt;Before creating the connector, make sure you have a &lt;code&gt;users&lt;/code&gt; table in your PostgreSQL database.&lt;/p&gt;

&lt;p&gt;Open Debezium UI at &lt;code&gt;http://localhost:8080&lt;/code&gt; and follow the steps shown in the screenshots below to create a new PostgreSQL source connector:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;Create Connector&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkzhcp0s5mditu5hrlkhr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkzhcp0s5mditu5hrlkhr.png" alt=" " width="800" height="328"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Choose the &lt;strong&gt;PostgreSQL&lt;/strong&gt; connector type&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw2ndls7uujj80vhy0vmt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw2ndls7uujj80vhy0vmt.png" alt=" " width="800" height="460"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fill in the basic connection settings (hostname, port, database name, credentials)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4oawmehc9qx7vimvftwp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4oawmehc9qx7vimvftwp.png" alt=" " width="800" height="461"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Configure advanced settings (table filter, plugin name: &lt;code&gt;pgoutput&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvvet6v191ec9zxkvlr5j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvvet6v191ec9zxkvlr5j.png" alt=" " width="800" height="631"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Validate the configuration and finish&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw2ebw4me8qtaf6nc2v15.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw2ebw4me8qtaf6nc2v15.png" alt=" " width="800" height="580"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Confirm the connector is running&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg9wabdnd6sq4v9pi5c7r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg9wabdnd6sq4v9pi5c7r.png" alt=" " width="800" height="339"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now try inserting a record into the &lt;code&gt;users&lt;/code&gt; table and open Kafka UI at &lt;code&gt;http://localhost:8070&lt;/code&gt; to see the change event appear on the Kafka topic:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbvjoeiwkaa61gl7d7q8t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbvjoeiwkaa61gl7d7q8t.png" alt=" " width="800" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgl1a2kw6et6679ovitv5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgl1a2kw6et6679ovitv5.png" alt=" " width="800" height="412"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Setup: Synchronization from PostgreSQL to Elasticsearch
&lt;/h2&gt;

&lt;p&gt;With Debezium already streaming changes to Kafka, adding Elasticsearch as a sync target is straightforward — we just need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start an Elasticsearch (+ Kibana) stack&lt;/li&gt;
&lt;li&gt;Register a sink connector that reads from the Kafka topic and writes to an Elasticsearch index&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 1 – Start Elasticsearch and Kibana
&lt;/h3&gt;

&lt;p&gt;Create a separate &lt;code&gt;docker-compose-elk.yml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;elasticsearch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker.elastic.co/elasticsearch/elasticsearch:7.17.15&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;elasticsearch&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9200:9200"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;discovery.type=single-node&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;xpack.security.enabled=false&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ES_JAVA_OPTS=-Xms512m -Xmx512m&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;base-network&lt;/span&gt;

  &lt;span class="na"&gt;kibana&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker.elastic.co/kibana/kibana:7.17.15&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kibana&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5601:5601"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ELASTICSEARCH_HOSTS=http://elasticsearch:9200&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;elasticsearch&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;base-network&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;base-network&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start the stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose-elk.yml up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Elasticsearch&lt;/strong&gt; → &lt;code&gt;http://localhost:9200&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kibana&lt;/strong&gt; → &lt;code&gt;http://localhost:5601&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 2 – Register the Connectors
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Make sure the &lt;code&gt;users&lt;/code&gt; table exists in your PostgreSQL database before registering the connectors.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Use the following &lt;code&gt;curl&lt;/code&gt; commands to register both connectors via the Debezium Connect REST API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Elasticsearch Sink Connector&lt;/strong&gt; — reads the &lt;code&gt;users&lt;/code&gt; Kafka topic and writes to the &lt;code&gt;users&lt;/code&gt; Elasticsearch index:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="s1"&gt;'http://localhost:8083/connectors'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{
  "name": "elasticsearch-sink-connector",
  "config": {
    "connector.class": "io.confluent.connect.elasticsearch.ElasticsearchSinkConnector",
    "topics": "users",
    "connection.url": "http://elasticsearch:9200",

    "key.converter": "org.apache.kafka.connect.json.JsonConverter",
    "key.converter.schemas.enable": "false",
    "value.converter": "org.apache.kafka.connect.json.JsonConverter",
    "value.converter.schemas.enable": "false",

    "schema.ignore": "true",
    "key.ignore": "false",

    "write.method": "upsert",
    "behavior.on.null.values": "delete",

    "transforms": "extractKey,removeDeleted",
    "transforms.extractKey.type": "org.apache.kafka.connect.transforms.ExtractField$Key",
    "transforms.extractKey.field": "id",

    "transforms.removeDeleted.type": "org.apache.kafka.connect.transforms.ReplaceField$Value",
    "transforms.removeDeleted.blacklist": "__deleted",

    "errors.tolerance": "all",
    "errors.deadletterqueue.topic.name": "dlq-users",
    "errors.deadletterqueue.topic.replication.factor": "1"
  }
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;PostgreSQL Source Connector&lt;/strong&gt; — reads WAL changes from PostgreSQL and publishes them to the &lt;code&gt;users&lt;/code&gt; Kafka topic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="s1"&gt;'http://localhost:8083/connectors'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{
  "name": "postgres-source-connector",
  "config": {
    "connector.class": "io.debezium.connector.postgresql.PostgresConnector",
    "database.hostname": "postgresdb",
    "database.port": "5432",
    "database.user": "postgres",
    "database.password": "postgres",
    "database.dbname": "template",
    "topic.prefix": "users_table",
    "table.include.list": "public.users",
    "plugin.name": "pgoutput",
    "key.converter": "org.apache.kafka.connect.json.JsonConverter",
    "key.converter.schemas.enable": "false",
    "value.converter": "org.apache.kafka.connect.json.JsonConverter",
    "value.converter.schemas.enable": "false",
    "transforms": "route,unwrap",
    "transforms.route.type": "org.apache.kafka.connect.transforms.RegexRouter",
    "transforms.route.regex": "users_table.public.users",
    "transforms.route.replacement": "users",
    "transforms.unwrap.type": "io.debezium.transforms.ExtractNewRecordState",
    "transforms.unwrap.drop.tombstones": "false",
    "transforms.unwrap.delete.handling.mode": "rewrite"
  }
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3 – Verify the Pipeline
&lt;/h3&gt;

&lt;p&gt;After registering both connectors, check their status in Debezium UI and verify that a consumer group has been created in Kafka UI:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw443eargrtk4xz5nru4h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw443eargrtk4xz5nru4h.png" alt=" " width="800" height="285"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqyobni6hqw5u4oypjfx0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqyobni6hqw5u4oypjfx0.png" alt=" " width="800" height="167"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now run some INSERT, UPDATE, or DELETE statements on the &lt;code&gt;users&lt;/code&gt; table in PostgreSQL and follow the data through the pipeline:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Run a search in Kibana DevTools before inserting any data:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fco3gv6nr2ueqfd9rfzga.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fco3gv6nr2ueqfd9rfzga.png" alt=" " width="800" height="314"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Insert a new record into the &lt;code&gt;users&lt;/code&gt; table:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F41tvafyql2u0b6d1xn5v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F41tvafyql2u0b6d1xn5v.png" alt=" " width="800" height="504"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Check the &lt;code&gt;users&lt;/code&gt; topic in Kafka UI — the change event should appear:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhb3snzmywz8k2c33llg4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhb3snzmywz8k2c33llg4.png" alt=" " width="800" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Query the &lt;code&gt;users&lt;/code&gt; index in Elasticsearch via Kibana DevTools — the new document should be there:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmq051sgss5xzwjrjleze.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmq051sgss5xzwjrjleze.png" alt=" " width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's the full pipeline working end-to-end: PostgreSQL → Debezium → Kafka → Elasticsearch. 🎉&lt;/p&gt;




&lt;h2&gt;
  
  
  Reference
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Repository: &lt;a href="https://github.com/longtk26/setup-cdc" rel="noopener noreferrer"&gt;Setup CDC&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;You've just set up a fully functional CDC pipeline with Debezium! From a single row update in PostgreSQL, a change event flows through Kafka and lands in Elasticsearch — all automatically, with zero application code involved.&lt;/p&gt;

&lt;p&gt;This is just the beginning. From here you can explore:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adding more sink connectors (e.g., a data warehouse, another database)&lt;/li&gt;
&lt;li&gt;Schema evolution strategies using a schema registry&lt;/li&gt;
&lt;li&gt;Monitoring connector health and lag in production&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thank you so much for following along! I hope this guide saved you hours of trial and error. If you haven't read it yet, the &lt;a href="https://dev.to/longtk26/overview-change-data-capture-cdc-4d42"&gt;Overview CDC&lt;/a&gt; post provides great background context on the concepts behind what we built here. Happy streaming! 🚀&lt;/p&gt;

</description>
      <category>database</category>
      <category>docker</category>
      <category>postgres</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Subtitle to Audio Tutorial</title>
      <dc:creator>longtk26</dc:creator>
      <pubDate>Sun, 18 Jan 2026 16:17:50 +0000</pubDate>
      <link>https://dev.to/longtk26/subtitle-to-audio-tutorial-19go</link>
      <guid>https://dev.to/longtk26/subtitle-to-audio-tutorial-19go</guid>
      <description>&lt;h1&gt;
  
  
  Subtitle to Audio Tutorial
&lt;/h1&gt;

&lt;p&gt;Hi everyone! In this tutorial, we will explore how to convert subtitles into audio using text-to-speech (TTS) technology. This process can be particularly useful for creating audiobooks, podcasts, or enhancing accessibility for video content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
Subtitle to Audio Tutorial

&lt;ul&gt;
&lt;li&gt;Table of Contents&lt;/li&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Workflow Overview&lt;/li&gt;
&lt;li&gt;
Problems with workflow

&lt;ul&gt;
&lt;li&gt;1. Limited execution time of lambda functions&lt;/li&gt;
&lt;li&gt;2. Limited resources within lambda functions&lt;/li&gt;
&lt;li&gt;3. Synchronous time and speed between subtile and audio&lt;/li&gt;
&lt;li&gt;4. Popping noises between audio segments&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

Solutions

&lt;ul&gt;
&lt;li&gt;Algorithm diagram&lt;/li&gt;
&lt;li&gt;Key Optimizations&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Conclusion&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;When working with applications that involve video content and translation, it is often necessary to translate an original subtitle file (e.g., SRT format) into another language.&lt;/li&gt;
&lt;li&gt;To enhance user experience, we can convert these translated subtitles into audio using TTS technology.&lt;/li&gt;
&lt;li&gt;This tutorial will guide you through the process of converting subtitles to audio, addressing common challenges and providing solutions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Workflow Overview
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3rnams87v2d9hejsedhr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3rnams87v2d9hejsedhr.png" alt=" " width="800" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Convert from subtitle to audio is a job that need several steps to complete that why we will handle it asynchronously.&lt;/li&gt;
&lt;li&gt;We choose lamba functions for saving cost and easy in implementation.&lt;/li&gt;
&lt;li&gt;The workflow consist of the following steps:

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Choose a subtitle file from current system&lt;/strong&gt;: Assume our system already show list of subtitle files that user can choose from.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Choose voice settings&lt;/strong&gt;: User can choose voice settings like male or female voice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Request to convert subtitle to audio&lt;/strong&gt;: After user choose subtitle file and voice settings we will send request to backend to start the conversion process.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server request to s3 to get subtitle file&lt;/strong&gt;: Because in real project we usually store files in s3, so our server need to get subtitle file url from s3 first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server request to lambda function to convert subtitle to audio&lt;/strong&gt;: After get subtitle file url server will send request to lambda function to start convert subtitle to audio.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lambda function process subtitle file&lt;/strong&gt;: Lambda function will download subtitle file from s3, parse subtitle file to get text and timing information, then use google text to speech to convert text to audio, finally combine audio segments according to timing information and save the final audio file to s3.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Problems with workflow
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Limited execution time of lambda functions
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;AWS Lambda functions have a maximum execution time limit (currently 15 minutes). If the subtitle file is large or the conversion process takes too long, the lambda function may time out before completing the task.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Limited resources within lambda functions
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Lambda functions have limited CPU and memory resources. If the TTS engine requires more resources or inprogress the size of audio become to large, the conversion process may fail or produce error out of memory.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Synchronous time and speed between subtile and audio
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Ensuring that the audio segments align perfectly with the timing information in the subtitle file can be challenging. Any discrepancies can lead to audio that is out of sync with the original video content.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Popping noises between audio segments
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;When combining multiple audio segments generated from subtitles, popping noises may occur at the boundaries between segments.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Solutions
&lt;/h2&gt;

&lt;p&gt;This section will show you solutions for the problems mentioned above and successully in converting subtitle file more than 30 minutes to audio only in some minutes. Also implement it with at least resources, we only use default lambda function settings (128MB memory) and timeout is 15 minutes. &lt;/p&gt;

&lt;h3&gt;
  
  
  Algorithm diagram
&lt;/h3&gt;

&lt;p&gt;Link algorithm diagram: &lt;a href="https://ibb.co/BVLFC1ms" rel="noopener noreferrer"&gt;Image&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The flowchart above illustrates the complete subtitle-to-audio conversion algorithm with optimized memory management and streaming architecture. Here's how it works:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Initialization Phase&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The process begins by downloading and parsing the SRT subtitle file from the provided URL&lt;/li&gt;
&lt;li&gt;It pre-calculates the total PCM (Pulse Code Modulation) audio size based on subtitle duration and timing information&lt;/li&gt;
&lt;li&gt;An S3 Multipart Upload is initialized to enable streaming upload without holding the entire file in memory&lt;/li&gt;
&lt;li&gt;A WAV header is created and added to the part buffer, initializing tracking variables (part_number=1, content_id index=0, current_part=b"")&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Batch Processing Loop&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instead of processing all subtitles at once, the algorithm processes them in batches of approximately 15 subtitles&lt;/li&gt;
&lt;li&gt;For each batch, it retrieves the next set of subtitle entries to process&lt;/li&gt;
&lt;li&gt;This batching approach prevents memory overflow by keeping only a small subset of subtitles in memory at any time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Audio Generation for Each Subtitle&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For each subtitle in the current batch:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Calculate speech speed&lt;/strong&gt;: Determines the appropriate speaking rate to fit the subtitle text within its designated time window&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generate audio using Google Cloud TTS&lt;/strong&gt;: Converts the subtitle text to audio using the selected voice settings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apply fade effects&lt;/strong&gt;: Adds 250ms fade-in and fade-out to prevent popping noises between audio segments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Calculate and add silence padding&lt;/strong&gt;: Adds appropriate silence gaps to ensure precise alignment with subtitle timing&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. Streaming Upload Mechanism&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;As audio segments are generated, PCM data is appended to the part buffer&lt;/li&gt;
&lt;li&gt;When the buffer reaches 5MB or when processing the last batch, the current part is uploaded to S3 immediately&lt;/li&gt;
&lt;li&gt;After each upload, the buffer is cleared and the part_number is incremented&lt;/li&gt;
&lt;li&gt;This streaming approach ensures that Lambda memory usage remains constant (around 5-10MB) regardless of the total audio file size&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;5. Finalization Phase&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Once all subtitle batches have been processed and all parts uploaded, the algorithm completes the S3 Multipart Upload&lt;/li&gt;
&lt;li&gt;The final audio file is assembled from all uploaded parts and becomes available on S3&lt;/li&gt;
&lt;li&gt;Error handling paths are included to manage upload failures or conversion issues (shown by the red error node)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Key Flow Control&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The algorithm uses two main decision loops:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"More batches to process?"&lt;/strong&gt;: Controls the outer loop for batch processing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"More subtitles in batch?"&lt;/strong&gt;: Controls the inner loop for processing individual subtitles within a batch&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;"Buffer size &amp;gt;= 5MB OR last batch?"&lt;/strong&gt;: Triggers S3 part uploads at optimal intervals&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Key Optimizations
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Batch Processing&lt;/strong&gt;: Subtitles are processed in batches (default: 15 subtitles per batch) instead of loading all at once. This prevents memory overflow in Lambda.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;S3 Multipart Upload&lt;/strong&gt;: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Initialize multipart upload at the start&lt;/li&gt;
&lt;li&gt;Upload parts incrementally when buffer reaches 5MB&lt;/li&gt;
&lt;li&gt;Each part is uploaded immediately and buffer is cleared&lt;/li&gt;
&lt;li&gt;Complete multipart upload at the end&lt;/li&gt;
&lt;li&gt;This approach ensures Lambda never holds the entire audio file in memory&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Streaming Architecture&lt;/strong&gt;: Audio data flows directly from TTS → batch buffer → S3 parts, with minimal memory footprint at any given time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fade Effects&lt;/strong&gt;: 250ms fade-in/out applied to each audio segment to eliminate popping noises between segments.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Memory Management&lt;/strong&gt;: After each part upload, the buffer is reset, keeping memory usage constant regardless of total audio length.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Avoid FFmpeg and audio processing libraries&lt;/strong&gt;: &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;We should avoid using FFmpeg (both the binary and Python wrappers like pydub, ffmpeg-python) in Lambda functions because they significantly increase deployment package size. &lt;/li&gt;
&lt;li&gt;FFmpeg binaries are typically 50-100MB, and audio processing libraries add another 20-50MB, easily exceeding Lambda's package size limits. &lt;/li&gt;
&lt;li&gt;Instead, we should work directly with WAV file format using Python's built-in &lt;code&gt;struct&lt;/code&gt; module and pure Python code to manipulate raw PCM audio bytes.&lt;/li&gt;
&lt;li&gt; This approach keeps the deployment package minimal (&amp;lt;15MB) while maintaining full control over audio processing operations like fade-in/fade-out, silence padding, and concatenation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Thank you for following this tutorial on converting subtitles to audio using TTS technology.&lt;/li&gt;
&lt;li&gt;I hope this information can help you to implement this feature in your own projects efficiently. You could use these ideas and use any AI tools for generating code to implement it.&lt;/li&gt;
&lt;li&gt;This blog is written by my experience in real project that we need to save cost with lambda function, if you have any good ideas or suggestions please feel free to share with me!&lt;/li&gt;
&lt;li&gt;You could find more on my blogs: &lt;a href="https://leon-pham.vercel.app/blogs" rel="noopener noreferrer"&gt;Website&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>lambda</category>
      <category>algorithms</category>
    </item>
    <item>
      <title>Understanding the Factory Method Pattern</title>
      <dc:creator>longtk26</dc:creator>
      <pubDate>Sat, 04 Jan 2025 15:18:34 +0000</pubDate>
      <link>https://dev.to/longtk26/understanding-the-factory-method-pattern-1ljc</link>
      <guid>https://dev.to/longtk26/understanding-the-factory-method-pattern-1ljc</guid>
      <description>&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;Hi everyone, I am writing this post to share my knowledge as I continue learning about design patterns. Today, I will present the Factory Method Pattern, which is a design pattern commonly used in real-world applications. If there are any mistakes in my post, please feel free to comment below, and I will gladly fix and update it.&lt;/p&gt;

&lt;p&gt;Factory method pattern provides an interface for creating objects in a superclass, but allow subclasses to alter the type of objects that will be created.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem
&lt;/h3&gt;

&lt;p&gt;Assume you have a bank application, and you’re building a feature for transferring money through various methods like bank transfer, paypal transfer,…&lt;/p&gt;

&lt;p&gt;Before using the Factory Method pattern, let’s examine the scenario without it.&lt;/p&gt;

&lt;p&gt;I will give an example implemented in Java.&lt;/p&gt;

&lt;p&gt;Situation: Person1 sends money to Person2 using a transfer method (Bank Transfer or PayPal Transfer).&lt;/p&gt;

&lt;p&gt;Folder structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;problem/
├─ BankApp.java
├─ service/
│  ├─ PaypalTransferPayment.java
│  ├─ BankTransferPayment.java
├─ data/
│  ├─ Person.java
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the main application, create two persons with default amounts of money.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;problem&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;problem.data.Person&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BankApp&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Person&lt;/span&gt; &lt;span class="n"&gt;person1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"John"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;Person&lt;/span&gt; &lt;span class="n"&gt;person2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Jane"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create &lt;code&gt;BankTransferPayment&lt;/code&gt;  and &lt;code&gt;PaypalTransferPayment&lt;/code&gt; classes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;problem.service&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;problem.data.Person&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BankTransferPayment&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;processPayment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Person&lt;/span&gt; &lt;span class="n"&gt;fromAccount&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt; &lt;span class="n"&gt;toAccount&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fromAccount&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withdraw&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;toAccount&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;deposit&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Bank transfer payment success."&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;problem.service&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;problem.data.Person&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PaypalPayment&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;processPayment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Person&lt;/span&gt; &lt;span class="n"&gt;fromAccount&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt; &lt;span class="n"&gt;toAccount&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fromAccount&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withdraw&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;toAccount&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;deposit&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Paypal transfer payment success."&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Implement the logic in the main function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;problem&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;problem.data.Person&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;problem.service.BankTransferPayment&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;problem.service.PaypalPayment&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BankApp&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Person&lt;/span&gt; &lt;span class="n"&gt;person1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"John"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;Person&lt;/span&gt; &lt;span class="n"&gt;person2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Jane"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;paymentMethod&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"BANK_TRANSFER"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paymentMethod&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"BANK_TRANSFER"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;BankTransferPayment&lt;/span&gt; &lt;span class="n"&gt;bankTransferPayment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BankTransferPayment&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;bankTransferPayment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;processPayment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;person1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;person2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"===Method bank_transfer==="&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;person1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getName&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" has "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;person1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAmount&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;person2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getName&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" has "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;person2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAmount&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paymentMethod&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PAYPAL"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;PaypalPayment&lt;/span&gt; &lt;span class="n"&gt;paypalPayment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PaypalPayment&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;paypalPayment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;processPayment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;person1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;person2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"===Method paypal==="&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;person1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getName&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" has "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;person1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAmount&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;person2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getName&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;" has "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;person2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAmount&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Problems with the current implementation:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Repetitive code&lt;/strong&gt;: The &lt;code&gt;processPayment&lt;/code&gt; method logic is repeated for every payment method.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tightly coupled code&lt;/strong&gt;: The application needs to create the payment method objects itself, making it hard to extend the application.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalability issues&lt;/strong&gt;: If new payment methods are added, the source code becomes more complex and harder to maintain.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Solution
&lt;/h3&gt;

&lt;p&gt;The solution to the above situation is to use factory method pattern. So, how do we apply it ?&lt;/p&gt;

&lt;p&gt;In the example above:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Each &lt;code&gt;if-else&lt;/code&gt; block calls the &lt;code&gt;processPayment&lt;/code&gt; method, which leads to repetitive code.&lt;/li&gt;
&lt;li&gt;Objects are created based on the payment type condition, making the code messy with excessive &lt;code&gt;if-else&lt;/code&gt; statements.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To solve these issues, the Factory Method pattern will be implemented step by step.&lt;/p&gt;

&lt;p&gt;Folder structure (solution):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;solution&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="err"&gt;├─&lt;/span&gt; &lt;span class="nc"&gt;BankApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;java&lt;/span&gt;
&lt;span class="err"&gt;├─&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;  &lt;span class="err"&gt;├─&lt;/span&gt; &lt;span class="n"&gt;payments&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;  &lt;span class="err"&gt;│&lt;/span&gt;  &lt;span class="err"&gt;├─&lt;/span&gt; &lt;span class="nc"&gt;Payment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;java&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;  &lt;span class="err"&gt;│&lt;/span&gt;  &lt;span class="err"&gt;├─&lt;/span&gt; &lt;span class="nc"&gt;PaymentFactory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;java&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;  &lt;span class="err"&gt;│&lt;/span&gt;  &lt;span class="err"&gt;├─&lt;/span&gt; &lt;span class="nc"&gt;BankTransferPayment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;java&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;  &lt;span class="err"&gt;│&lt;/span&gt;  &lt;span class="err"&gt;├─&lt;/span&gt; &lt;span class="nc"&gt;PaypalTransferPayment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;java&lt;/span&gt;
&lt;span class="err"&gt;├─&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;  &lt;span class="err"&gt;├─&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;java&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 1: Create &lt;code&gt;Payment&lt;/code&gt; interface, declares common method  &lt;code&gt;processPayment&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;solution.service.payments&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;solution.data.Person&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Step 1: Create an interface for the payment&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;Payment&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;processPayment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Person&lt;/span&gt; &lt;span class="n"&gt;fromAccount&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt; &lt;span class="n"&gt;toAccount&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 2: Create &lt;code&gt;BankTransferPayment&lt;/code&gt; and &lt;code&gt;PaypalTransferPayment&lt;/code&gt; classes implement &lt;code&gt;Payment&lt;/code&gt; interface.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;solution.service.payments&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;solution.data.Person&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Step 2: Create a class that implements the Payment interface&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BankTransferPayment&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;Payment&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;processPayment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Person&lt;/span&gt; &lt;span class="n"&gt;fromAccount&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt; &lt;span class="n"&gt;toAccount&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fromAccount&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withdraw&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;toAccount&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;deposit&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Bank transfer payment success."&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;solution.service.payments&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;solution.data.Person&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PaypalPayment&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;Payment&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;processPayment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Person&lt;/span&gt; &lt;span class="n"&gt;fromAccount&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt; &lt;span class="n"&gt;toAccount&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fromAccount&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;withdraw&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;toAccount&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;deposit&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Paypal transfer payment success."&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 3: Create &lt;code&gt;PaymentFactory&lt;/code&gt; class. This class is responsible for creating objects based on payment type condition.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;solution.service.payments&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PaymentFactory&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Payment&lt;/span&gt; &lt;span class="nf"&gt;createPayment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;paymentType&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paymentType&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paymentType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equalsIgnoreCase&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"BANK_TRANSFER"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BankTransferPayment&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paymentType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equalsIgnoreCase&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PAYPAL"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PaypalPayment&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 4: Use the Factory in the Main Application.&lt;/p&gt;

&lt;p&gt;Modify the main function to use the Factory Method pattern.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;solution&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;solution.data.Person&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;solution.service.payments.Payment&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;solution.service.payments.PaymentFactory&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BankApp&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Person&lt;/span&gt; &lt;span class="n"&gt;person1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"John"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;Person&lt;/span&gt; &lt;span class="n"&gt;person2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Jane"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;paymentMethod&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"PAYPAL"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

        &lt;span class="nc"&gt;Payment&lt;/span&gt; &lt;span class="n"&gt;payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PaymentFactory&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;createPayment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paymentMethod&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;processPayment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;person1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;person2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Benefits of Using the Factory Method Pattern
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The code is &lt;strong&gt;cleaner&lt;/strong&gt; and more structured.&lt;/li&gt;
&lt;li&gt;Repetitive calls to &lt;code&gt;processPayment&lt;/code&gt; in multiple &lt;code&gt;if-else&lt;/code&gt; blocks are &lt;strong&gt;eliminated&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Object creation is &lt;strong&gt;delegated to the factory&lt;/strong&gt;, improving maintainability.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Bonus&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;To make the &lt;code&gt;PaymentFactory&lt;/code&gt; class comply with the Open/Closed Principle (from SOLID principles), you can implement a dynamic registration mechanism using the &lt;strong&gt;Strategy Pattern&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Updated PaymentFactory.java:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;solution.service.payments&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.HashMap&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.Map&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PaymentFactory&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Payment&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;paymentMaps&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;PaymentFactory&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;paymentMaps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Payment&lt;/span&gt; &lt;span class="nf"&gt;createPayment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;paymentType&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;paymentMaps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paymentType&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;registerPayment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;paymentType&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Payment&lt;/span&gt; &lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;paymentMaps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paymentType&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;PaymentFactory&lt;/span&gt; &lt;span class="nf"&gt;initializePaymentMethods&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Payment&lt;/span&gt; &lt;span class="n"&gt;bankTransferPayment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BankTransferPayment&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="nc"&gt;Payment&lt;/span&gt; &lt;span class="n"&gt;paypalPayment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PaypalPayment&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;registerPayment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"BANK_TRANSFER"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bankTransferPayment&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;registerPayment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PAYPAL"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;paypalPayment&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using the Updated Factory in the Main Application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;solution&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;solution.data.Person&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;solution.service.payments.Payment&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;solution.service.payments.PaymentFactory&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BankApp&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Person&lt;/span&gt; &lt;span class="n"&gt;person1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"John"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;Person&lt;/span&gt; &lt;span class="n"&gt;person2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Jane"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;paymentMethod&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"BANK_TRANSFER"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

        &lt;span class="nc"&gt;Payment&lt;/span&gt; &lt;span class="n"&gt;payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PaymentFactory&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initializePaymentMethods&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createPayment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paymentMethod&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;processPayment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;person1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;person2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By applying this approach, the code adheres to the &lt;strong&gt;Open/Closed Principle&lt;/strong&gt;, enabling the addition of new payment methods without modifying the &lt;code&gt;PaymentFactory&lt;/code&gt; logic.&lt;/p&gt;

&lt;p&gt;I hope this post will be helpful to you.&lt;/p&gt;

&lt;h3&gt;
  
  
  References:
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://refactoring.guru/design-patterns/factory-method" rel="noopener noreferrer"&gt;guru-design-patterns&lt;/a&gt;&lt;/p&gt;

</description>
      <category>designpatterns</category>
      <category>java</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
