Uncategorized

Elasticsearch Synchronization

Elasticsearch Synchronization: Practical Approaches That Work in Real Systems

1024 1024 Ahmet Onur

Elasticsearch synchronization is one of those topics almost everyone postpones at the beginning. Everything works fine at first. Queries are fast, data looks correct, life is good.

Then data grows.
Updates increase.
And suddenly, Elasticsearch starts to feel… fragile.

If you’ve ever asked yourself:

“How do I reliably keep Elasticsearch in sync with my database?”

you’re in the right place.

In this article, we’ll walk through real-world Elasticsearch synchronization strategies—without buzzwords, and without unnecessary complexity.


Why Elasticsearch Synchronization Is Harder Than It Looks

Let’s clear up a common misconception first:

Elasticsearch is not a primary data store.

In most systems:

  • PostgreSQL, MySQL, or another database is the source of truth
  • Elasticsearch holds a search-optimized copy of that data

The challenge is keeping these two in sync—especially when data changes frequently.


Why Are Updates Expensive in Elasticsearch?

Elasticsearch is built on top of Lucene, and Lucene has a core design principle:
segments are immutable.

Here’s a simple analogy:

Imagine printing a book. If you want to change a single word, you don’t edit the page—you print a new one and mark the old one as obsolete.

That’s exactly what happens in Elasticsearch:

  • The old document is marked as deleted
  • The new document is indexed again
  • All analyzers run from scratch

This leads to:

  • Higher CPU usage
  • More disk I/O
  • Increased segment count

In short:
👉 Frequent updates are expensive in Elasticsearch.

Source:
https://www.elastic.co/blog/found-keeping-elasticsearch-in-sync


Bulk API: The Foundation of Elasticsearch Synchronization

The first rule of Elasticsearch writes is simple:

Never index documents one by one. Always use batches.

The Bulk API:

  • Reduces network overhead
  • Minimizes segment churn
  • Improves overall throughput

But using Bulk API alone isn’t enough.
The real question is when and how you send data to Elasticsearch.


Approach 1: Queue-Based Elasticsearch Synchronization

One of the most common and flexible approaches is queue-based synchronization.

How Queue-Based Sync Works

At a high level:

  1. A record changes in the primary database
  2. A queue entry is created (document ID, index name, etc.)
  3. If the same record changes again shortly after:
    • No duplicate queue entry is created (deduplication)
  4. Workers periodically:
    • Dequeue, for example, 1000 entries
    • Fetch fresh data from the database
    • Index everything using the Bulk API

Why This Approach Works Well

  • Elasticsearch load is isolated from the main application
  • User-facing operations remain fast
  • Write throughput can be controlled via worker count
  • Temporary Elasticsearch outages don’t break the system

This approach is especially effective for domains with frequent updates.

The Trade-off

Queues perform poorly for full reindex operations.
Pushing millions of records through a queue is slow, expensive, and complex.


Approach 2: Range-Based (Time Window) Batch Synchronization

If your data is:

  • Immutable
  • Append-only
  • Log, event, or audit based

you can use a much simpler approach—without queues.

How Range-Based Synchronization Works

The logic is straightforward:

  • A worker runs every X seconds
  • It remembers the last processed timestamp
  • It calculates a new range:
    • last_run_timenow() - 1 second
  • Fetches records in that range
  • Indexes them using the Bulk API
  • Updates last_run_time

Why the 1-Second Delay?

Because:

  • Elasticsearch makes documents searchable after ~1 second by default
  • Slow database transactions need a small buffer to complete

That tiny delay significantly reduces the risk of missing data.


Time-Based Indexing: Small Change, Big Win

If you’re using range-based batching, time-based indices are a natural fit.

For example:

  • logs-2025-01
  • logs-2025-02

Benefits:

  • Queries target only relevant indices
  • Old data can be deleted with a single operation
  • Better search performance overall

This small design decision pays off quickly as data grows.


Error Handling and Idempotency (Often Overlooked)

This is where many systems quietly fail.

All Elasticsearch synchronization processes should be idempotent.

That means:

  • Running the same batch twice should not break anything
  • Queue entries should be removed only after successful indexing
  • Bulk API partial failures must be handled explicitly

When designed this way:

  • Retry logic becomes simple
  • Recovery scenarios are predictable
  • The system remains stable under failure

Conclusion: Choosing the Right Elasticsearch Synchronization Strategy

There is no single “correct” solution—but there is a correct choice for each use case.

  • Frequent updates, complex domains → Queue-based synchronization
  • Immutable or append-only data → Range-based batch synchronization
  • Always → Bulk API + idempotent design

When done right, Elasticsearch synchronization becomes boring—and that’s a good thing.,

Thanks for reading 🙌
If you’ve implemented Elasticsearch synchronization in a different way, or if something here raised questions, feel free to share your thoughts.
Elasticsearch synchronization may look tricky at first, but with the right approach, it’s surprisingly manageable.

Capacitor JDK 21 macOS

Capacitor JDK 21 macOS (aarch64) [solved]

1024 1024 Ahmet Onur

Building your Capacitor project for Android on an Apple Silicon Mac (aarch64) and hit a snag with Capacitor JDK 21 macOS compatibility? If you’re seeing errors like Could not create task ‘:capacitor-geolocation:compileDebugAndroidTestJavaWithJavac’…. Cannot find a Java installation on your machine matching this tasks requirements: {languageVersion=21, vendor=any vendor, implementation=vendor-specific} for MAC_OS on aarch64. No locally installed toolchains match and toolchain download repositories have not been configured, you’re in the right place. This guide helps you solve this common build issue.

Frustrating, right? This error essentially means Gradle, the build tool Android projects use, is looking for a very specific version of Java (JDK 21, for an aarch64 architecture Mac) and simply can’t find it. Your development flow grinds to a halt.

Fear not! This is a common hurdle, especially with the evolving landscape of Java versions and chip architectures. We recently navigated this exact issue, and here’s a step-by-step guide to get you back on track.

Step 1: Diagnosing Your Current Java Setup for Capacitor JDK 21 macOS Issues

First, let’s see what Java versions your Mac is actually aware of. Open your Terminal and type:

/usr/libexec/java_home -V

This command lists all Java Virtual Machines (JVMs) installed on your system. In our case, the initial output might have shown something like:

Matching Java Virtual Machines (1):
    17.0.15 (x86_64) "Homebrew" - "OpenJDK 17.0.15" /usr/local/Cellar/openjdk@17/17.0.15/libexec/openjdk.jdk/Contents/Home
/usr/local/Cellar/openjdk@17/17.0.15/libexec/openjdk.jdk/Contents/Home

Notice two things here if you’re facing the JDK 21 error:

  • Version Mismatch: We have JDK 17, but the project screams for JDK 21.
  • Architecture Mismatch (Potentially): The error specified aarch64 (for Apple Silicon like M1/M2/M3), but an older installation might be x86_64. While Rosetta 2 can handle x86_64 applications on Apple Silicon, it’s best to use a native aarch64 JDK for development.

Step 2: Installing Java 21 for Apple Silicon (aarch64) – The Core Fix

This is the most crucial step for resolving your aarch64 JDK installation needs. You need JDK 21 built specifically for the ARM64/aarch64 architecture of your Mac.

Where to Download:

  • Adoptium Temurin: A popular choice. Go to adoptium.net, select “macOS” and “AArch64” for JDK 21.
  • Oracle JDK: Available from Oracle’s website. Look for the “macOS ARM64” installer for Java 21.
  • Azul Zulu: Another excellent OpenJDK build. Filter for Java 21, macOS, and ARM 64-bit on their downloads page.

Download and install your chosen JDK. Once done, run the diagnostic command from Step 1 again:

Bash

/usr/libexec/java_home -V

You should now see your new JDK 21 (arm64) listed, something like:

Matching Java Virtual Machines (2):
    21.0.7 (arm64) "Eclipse Adoptium" - "OpenJDK 21.0.7" /Library/Java/JavaVirtualMachines/temurin-21.jdk/Contents/Home
    17.0.15 (x86_64) "Homebrew" - "OpenJDK 17.0.15" /usr/local/Cellar/openjdk@17/17.0.15/libexec/openjdk.jdk/Contents/Home
/Library/Java/JavaVirtualMachines/temurin-21.jdk/Contents/Home

Success! The correct JDK is now on your system. The last line usually indicates the default path for the highest version, which is what we want.

Step 3: Setting JAVA_HOME Correctly for JDK 21 aarch64

The JAVA_HOME environment variable tells command-line tools (including Gradle) where to find the Java installation it should use.

Open your shell configuration file. If you’re using Zsh (default on newer macOS), it’s ~/.zshrc. For Bash, it’s ~/.bash_profile.

Bash

nano ~/.zshrc # or nano ~/.bash_profile

Add the following lines. The first line cleverly asks macOS to find the path for JDK 21 arm64. Alternatively, you can use the direct path you saw in the previous step’s output.

Bash

export JAVA_HOME=$(/usr/libexec/java_home -v 21 --arch arm64)
# Or, the direct path:
# export JAVA_HOME="/Library/Java/JavaVirtualMachines/temurin-21.jdk/Contents/Home" # Adjust if your path differs
export PATH=$JAVA_HOME/bin:$PATH

Save the file (Ctrl+X, then Y, then Enter in Nano).

Apply the changes by restarting your terminal or running:

Bash

source ~/.zshrc # or source ~/.bash_profile

Verify: In the new terminal session, check:

Bash

echo $JAVA_HOME
java -version

The first command should output the path to your JDK 21. The second should show “OpenJDK version “21.x.x”…” and mention aarch64.

Step 4: Configuring Android Studio’s Gradle JDK for Java 21

Android Studio sometimes manages its own JDK settings for Gradle, which can override your system’s JAVA_HOME. This is a key step for fixing your Android Studio Gradle JDK setup.

  1. Open your Capacitor project in Android Studio (the android folder).
  2. Go to File > Project Structure.
  3. Navigate to SDK Location (in older versions) or Android Studio > Settings (or Preferences on macOS) > Build, Execution, Deployment > Build Tools > Gradle (in newer versions).
  4. Look for the Gradle JDK (or Gradle JVM) setting.
  5. Ensure it’s set to your newly installed JDK 21 (e.g., temurin-21). If it’s not listed, use the “Add JDK…” option to point to its directory (e.g., /Library/Java/JavaVirtualMachines/temurin-21.jdk/Contents/Home).

(Internal Link Suggestion: If you have a general post about Android Studio setup, you could link it here, e.g., “For more details on configuring Android Studio, see our [Ultimate Guide to Android Studio Settings].”)

Step 5: Aligning Gradle Configuration for Capacitor Java 21 Compatibility

A quick check in your Gradle files can save future headaches and ensure Capacitor Java 21 compatibility:

  • android/gradle.properties: Look for a line starting with org.gradle.java.home. If it exists and points to an old JDK, either update it to your JDK 21 path or comment it out (by adding # at the beginning) to let Gradle use the JAVA_HOME or Android Studio setting.
  • android/app/build.gradle: Ensure your Java compatibility is set correctly if your project requires it explicitly: Gradleandroid { // ... compileOptions { sourceCompatibility JavaVersion.VERSION_21 targetCompatibility JavaVersion.VERSION_21 } // If using Kotlin kotlinOptions { jvmTarget = '21' } }
  • Gradle Version: Ensure your project’s Gradle version (in android/gradle/wrapper/gradle-wrapper.properties via the distributionUrl) is compatible with Java 21. Generally, Gradle 8.5 and newer offer good support for JDK 21. You can check Gradle’s compatibility matrix on the official Gradle documentation site.

Step 6: The Final Sync, Clean, and Rebuild for Your Capacitor Project

Almost there! Let’s give your project a fresh start to apply your Capacitor build error fix:

  1. Sync Capacitor: Bashnpx cap sync android
  2. Clean the Build:
    • In Android Studio: Build > Clean Project.
    • Or from the terminal (inside the android directory): Bash./gradlew clean
  3. Rebuild:
    • In Android Studio: Build > Rebuild Project.
    • Or try your usual build command for Capacitor.

The Sweet Sound of Success!

If all went well, your build should now complete successfully! That wall of red text should be replaced by a comforting “BUILD SUCCESSFUL” message.

Key Takeaways for Capacitor JDK 21 macOS Success

  • JDK Version & Architecture Matter: Always ensure your installed JDK matches the project’s requirements for both version (e.g., 21) and architecture (aarch64 for Apple Silicon).
  • JAVA_HOME is King (for CLI): It’s the primary way your system tells tools where Java lives.
  • Android Studio’s Independence: Be aware of Android Studio’s specific Gradle JDK setting.
  • Consistency is Key: Keep your gradle.properties and build.gradle files aligned with your chosen JDK.

From different topic but I would like to suggest you to have a look “Moving just single commit from branch to another branch” article here.

Build errors can be daunting, but with a systematic approach, you can usually track down the culprit. By following these steps, you can resolve the frustrating Capacitor JDK 21 macOS aarch64 build errors and get back to developing your amazing app. Remember, matching your JDK version and architecture is crucial for a smooth Capacitor workflow on Apple Silicon. Happy coding!

Vue3 defineModel

Vue3 defineModel: Define Your Prop as v-model the Easiest Way

1024 1024 Ahmet Onur

Vue3 brings a host of new features aimed at enhancing your development workflow, and one standout feature is defineModel. This powerful helper function simplifies two-way data binding, reducing boilerplate code and improving readability. Vue3 defineModel will help you define your props as v-model easiest way.

What is defineModel?

defineModel is a utility in Vue 3 designed to streamline the creation of two-way bindings for props. By reducing repetitive code, it makes your components more concise and easier to understand.

How to use Vue3 defineModel?

To leverage defineModel, you simply define it in your script setup. This approach facilitates seamless prop binding and event emission, leading to cleaner code.

Example:
Here’s a straightforward example demonstrating how defineModel can simplify two-way binding for a title prop.

Using Composition API:

Without defineModel:

<template>
  <input :value="title" @input="$emit('update:title', $event.target.value)" />
</template>

<script setup>
defineProps(['title']);
defineEmits(['update:title']);
</script>

With defineModel:

<template>
  <input v-model="title" />
</template>

<script setup>
const title = defineModel('title');
</script>

Using Options API:

Without defineModel:

<template>
  <input :value="title" @input="$emit('update:title', $event.target.value)" />
</template>

<script>
export default {
  props: {
    title: String
  },
  methods: {
    updateTitle(event) {
      this.$emit('update:title', event.target.value);
    }
  }
}
</script>

With defineModel:

<template>
  <input v-model="title" />
</template>

<script>
import { defineComponent } from 'vue';
import { defineModel } from '@vue/composition-api';

export default defineComponent({
  setup() {
    const title = defineModel('title');
    return { title };
  }
});
</script>

In the second examples of both APIs, defineModel automatically manages the binding and event emission, resulting in a cleaner and more readable component.

Benefits of Using defineModel

  • Reduced Boilerplate: Significantly less code to write and maintain.
  • Improved Readability: Cleaner syntax makes it easier to understand your components at a glance.
  • Streamlined Logic: Automatically handles prop binding and event emission, simplifying your logic.

Conclusion

Incorporating Vue3 defineModel can greatly enhance your two-way binding strategy, making your components more concise and easier to maintain. This is particularly advantageous in larger projects where maintaining a clean codebase is crucial. By adopting defineModel, you ensure your Vue 3 applications are both robust and readable.

There is vue3 official page for component v-model

You might be interested in what is difference between async and defer JavaScript. Click to read: