<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[9Cloud]]></title><description><![CDATA[Multi-cloud tutorials, hands-on labs, and real-world insights across AWS, Azure, GCP, and Alibaba Cloud.]]></description><link>https://9cld.com</link><image><url>https://substackcdn.com/image/fetch/$s_!OduC!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b5d48c5-0330-4021-a63f-4bcc8adcddbe_256x256.png</url><title>9Cloud</title><link>https://9cld.com</link></image><generator>Substack</generator><lastBuildDate>Sun, 12 Apr 2026 12:38:56 GMT</lastBuildDate><atom:link href="https://9cld.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Ankit Mehta]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[9cld@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[9cld@substack.com]]></itunes:email><itunes:name><![CDATA[Ankit Mehta]]></itunes:name></itunes:owner><itunes:author><![CDATA[Ankit Mehta]]></itunes:author><googleplay:owner><![CDATA[9cld@substack.com]]></googleplay:owner><googleplay:email><![CDATA[9cld@substack.com]]></googleplay:email><googleplay:author><![CDATA[Ankit Mehta]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Day 11: How OpenSearch Nodes Find Each Other]]></title><description><![CDATA[Understand how OpenSearch nodes discover each other through seed hosts, bootstrap a new cluster, elect a cluster manager using quorum-based voting, and prevent split-brain scenarios. Includes production configuration, fault detection tuning, cluster manager task throttling, and the most common mistakes that cause the "cluster manager not discovered" error.]]></description><link>https://9cld.com/p/opensearch-discovery-cluster-formation-split-brain-prevention</link><guid isPermaLink="false">https://9cld.com/p/opensearch-discovery-cluster-formation-split-brain-prevention</guid><dc:creator><![CDATA[Ankit Mehta]]></dc:creator><pubDate>Tue, 24 Feb 2026 00:25:55 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!YAGW!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0a5219ef-3fd4-40c9-a303-9633ba4e0cc1_1205x631.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!YAGW!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0a5219ef-3fd4-40c9-a303-9633ba4e0cc1_1205x631.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!YAGW!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0a5219ef-3fd4-40c9-a303-9633ba4e0cc1_1205x631.png 424w, https://substackcdn.com/image/fetch/$s_!YAGW!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0a5219ef-3fd4-40c9-a303-9633ba4e0cc1_1205x631.png 848w, https://substackcdn.com/image/fetch/$s_!YAGW!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0a5219ef-3fd4-40c9-a303-9633ba4e0cc1_1205x631.png 1272w, https://substackcdn.com/image/fetch/$s_!YAGW!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0a5219ef-3fd4-40c9-a303-9633ba4e0cc1_1205x631.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!YAGW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0a5219ef-3fd4-40c9-a303-9633ba4e0cc1_1205x631.png" width="1205" height="631" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0a5219ef-3fd4-40c9-a303-9633ba4e0cc1_1205x631.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:631,&quot;width&quot;:1205,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:606716,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://9cld.com/i/188966668?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0a5219ef-3fd4-40c9-a303-9633ba4e0cc1_1205x631.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!YAGW!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0a5219ef-3fd4-40c9-a303-9633ba4e0cc1_1205x631.png 424w, https://substackcdn.com/image/fetch/$s_!YAGW!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0a5219ef-3fd4-40c9-a303-9633ba4e0cc1_1205x631.png 848w, https://substackcdn.com/image/fetch/$s_!YAGW!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0a5219ef-3fd4-40c9-a303-9633ba4e0cc1_1205x631.png 1272w, https://substackcdn.com/image/fetch/$s_!YAGW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0a5219ef-3fd4-40c9-a303-9633ba4e0cc1_1205x631.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>You have been running OpenSearch for ten days. You created indexes. You ran queries. You built chatbots and migrated data.</p><p>But have you ever wondered what happens in the first few seconds after you start a cluster?</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://9cld.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading 9Cloud! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>Before any index exists. Before any query runs. Before your data is safe. There is a process happening that most people never think about until it breaks.</p><p>That process is discovery and cluster formation.</p><p>Get it right and your cluster starts reliably every time, recovers from failures automatically, and scales without drama. Get it wrong and you wake up to a split-brain scenario where two nodes both think they are in charge, writing conflicting data to the same indexes.</p><p>This is not theoretical. This is the kind of failure that corrupts production data.</p><div><hr></div><h2>Why This Matters Before Anything Else</h2><p>Every OpenSearch cluster needs a leader. This leader is called the cluster manager (historically called the &#8220;master&#8221; node in Elasticsearch).</p><p>The cluster manager is responsible for:</p><ul><li><p>Maintaining the definitive cluster state, which includes node membership, index metadata, and shard allocation</p></li><li><p>Publishing state updates to all nodes</p></li><li><p>Coordinating shard allocation and rebalancing</p></li><li><p>Managing cluster-wide settings</p></li></ul><p>Without a cluster manager, nothing works. No index creation. No shard movement. No cluster state updates. Your cluster is frozen.</p><p>So the very first thing OpenSearch must do when nodes start is figure out who else is out there and agree on a leader. That is discovery.</p><div><hr></div><h2>Discovery: How Nodes Find Each Other</h2><p>When an OpenSearch node starts up, it has no idea what the rest of the cluster looks like. It does not know how many other nodes exist. It does not know who the current cluster manager is. It does not even know if a cluster already exists.</p><p>The node needs a starting point. That starting point is called seed hosts.</p><p>Seed hosts are a list of addresses where the node should look for other cluster-manager-eligible nodes. Think of it as a phone book. The node calls each address and asks: &#8220;Is there a cluster here? Who is in charge?&#8221;</p><p>You configure seed hosts in <code>opensearch.yml</code>:</p><pre><code><code>discovery.seed_hosts:
  - 10.0.1.10:9300
  - 10.0.1.11:9300
  - 10.0.1.12:9300</code></code></pre><p>A few things to understand about this list:</p><ul><li><p>These do not need to be all the nodes in the cluster. They just need to be enough to find at least one active cluster-manager-eligible node.</p></li><li><p>If you omit the port, OpenSearch will scan the default range of 9300 to 9400.</p></li><li><p>You can use hostnames instead of IPs. OpenSearch will resolve them via DNS.</p></li><li><p>The seed hosts list is only used during discovery. Once a node joins the cluster, it gets the full picture from the cluster state.</p></li></ul><div><hr></div><h2>Seed Host Providers</h2><p>OpenSearch supports three ways to provide seed host information.</p><p><strong>Settings-based (default).</strong> The list in <code>discovery.seed_hosts</code> in your opensearch.yml file. Simple, static, works for most deployments.</p><p><strong>File-based.</strong> A file called <code>unicast_hosts.txt</code> in the OpenSearch config directory. Useful when your infrastructure updates the file dynamically, like a configuration management tool writing new IPs as nodes spin up.</p><pre><code><code>10.0.1.10:9300
10.0.1.11:9300
10.0.1.12</code></code></pre><p><strong>Plugin-based.</strong> Cloud providers offer plugins that discover nodes through APIs. On AWS, the EC2 discovery plugin can find nodes by tags. On Alibaba Cloud, similar mechanisms exist for ECS instances.</p><pre><code><code>discovery.seed_providers: ec2
discovery.ec2.tag.role: master
discovery.ec2.tag.environment: production</code></code></pre><p>You can combine providers. OpenSearch will merge the results from all configured sources.</p><div><hr></div><h2>Bootstrapping: The First Time is Special</h2><p>There is a critical difference between starting a brand-new cluster and restarting an existing one.</p><p>When nodes have never been part of a cluster before, there is no cluster state. No leader. No history. OpenSearch needs to be told which nodes should participate in the very first election. This process is called bootstrapping.</p><p>You configure it with:</p><pre><code><code>cluster.initial_cluster_manager_nodes:
  - cluster-manager-1
  - cluster-manager-2
  - cluster-manager-3</code></code></pre><p>These values must exactly match the <code>node.name</code> setting on each node. Not the hostname. Not the IP. The node name. This is case-sensitive and character-exact.</p><p>If the name is <code>cluster-manager-1</code>, you cannot write <code>cluster-manager-1.example.com</code> in the bootstrap list. The log will tell you:</p><pre><code><code>cluster manager not discovered yet, this node has not previously
joined a bootstrapped cluster, and this node must discover
cluster-manager-eligible nodes [cluster-manager-1, cluster-manager-2]
to bootstrap a cluster</code></code></pre><p>This is the single most common error people hit when setting up a new cluster.</p><div><hr></div><h2>When Bootstrapping is Required</h2><p>Bootstrapping is only needed once, when the cluster forms for the very first time. After that initial formation, OpenSearch stores the cluster state and never needs the bootstrap configuration again.</p><p>Specifically:</p><ul><li><p><strong>Required:</strong> Starting a brand-new cluster where no node has ever joined a cluster before.</p></li><li><p><strong>Not required:</strong> Nodes joining an existing cluster. They get configuration from the current cluster manager.</p></li><li><p><strong>Not required:</strong> Cluster restarts, including full cluster restarts. The existing cluster state is preserved on disk and used for recovery.</p></li></ul><p>In development mode, if you do not configure any discovery settings, OpenSearch will automatically bootstrap a single-node cluster. This is convenient for local testing but dangerous in production because auto-bootstrapping can lead to split-brain if multiple nodes start independently.</p><pre><code><code># Development only - single node mode
discovery.type: single-node</code></code></pre><p>Never use <code>single-node</code> in production. It suppresses safety mechanisms that exist specifically to prevent data corruption.</p><div><hr></div><h2>Voting and Quorum: How Leaders Are Elected</h2><p>Once nodes have discovered each other, they need to agree on a leader. OpenSearch uses a quorum-based voting mechanism for this.</p><p>What is a quorum? It is the minimum number of cluster-manager-eligible nodes that must agree before an election can succeed. The formula is simple:</p><p><strong>Quorum = (number of voting nodes / 2) + 1</strong></p><p>With three cluster-manager-eligible nodes, the quorum is two. With five, the quorum is three. This majority requirement is what prevents split-brain.</p><p>Why does the majority rule matter? Imagine a network partition splits your three cluster-manager-eligible nodes into two groups: one group with two nodes and one group with one node. </p><p>The group with two nodes has a quorum and can elect a leader. The group with one node cannot. This means exactly one side of the partition can operate, preventing conflicting writes.</p><div><hr></div><h2>The Voting Configuration</h2><p>The voting configuration is the set of cluster-manager-eligible nodes that participate in elections. OpenSearch manages this automatically as nodes join and leave the cluster.</p><p>When a new cluster-manager-eligible node joins, it gets added to the voting configuration. When a node leaves, OpenSearch can automatically shrink the voting configuration to remove it, as long as the configuration still contains at least three nodes.</p><p>This automatic shrinking is controlled by:</p><pre><code><code>cluster.auto_shrink_voting_configuration: true  # default</code></code></pre><p>Why the minimum of three? Because with only two voting nodes, losing one node means losing quorum. The cluster would be stuck. Three is the minimum number that can tolerate a single node failure.</p><p>If you need to explicitly remove a node from the voting configuration (for example, during a planned decommission), you use the voting configuration exclusions API:</p><pre><code><code>POST /_cluster/voting_config_exclusions?node_names=node-to-remove</code></code></pre><p>This tells OpenSearch to remove the specified node from the voting configuration. The cluster will recalculate quorum based on the remaining nodes.</p><p>You can control how many exclusions are tracked with:</p><pre><code><code>cluster.max_voting_config_exclusions: 10  # default</code></code></pre><div><hr></div><h2>What Happens During a Leader Failure</h2><p>When the elected cluster manager goes down, the remaining cluster-manager-eligible nodes detect the failure through a fault detection mechanism. This triggers a new election.</p><p>The process follows a predictable sequence:</p><ul><li><p>Remaining nodes notice the leader has stopped responding.</p></li><li><p>Each node waits a randomized delay before starting an election. This prevents all nodes from trying to become leader simultaneously.</p></li><li><p>A node requests votes from all other voting nodes.</p></li><li><p>If the node receives votes from a quorum, it becomes the new leader.</p></li><li><p>The new leader publishes an updated cluster state to all nodes.</p></li></ul><p>The randomized delay is important. Without it, two nodes might start elections at the same time, split the votes, and force a retry. The randomization is controlled by:</p><pre><code><code>cluster.election.initial_timeout: 100ms    # base delay
cluster.election.back_off_time: 100ms      # linear backoff per failure
cluster.election.max_timeout: 10s          # maximum delay cap</code></code></pre><p>In most healthy clusters, leader failover happens in seconds.</p><div><hr></div><h2>Fault Detection Settings</h2><p>OpenSearch continuously monitors the health of nodes in the cluster. The cluster manager pings followers. Followers ping the cluster manager. These are called fault detection pings.</p><p>Key settings that control this behavior:</p><pre><code><code># How often the leader checks each follower
cluster.fault_detection.leader_check.interval: 1s
cluster.fault_detection.leader_check.timeout: 10s
cluster.fault_detection.leader_check.retry_count: 3

# How often followers check the leader
cluster.fault_detection.follower_check.interval: 1s
cluster.fault_detection.follower_check.timeout: 10s
cluster.fault_detection.follower_check.retry_count: 3</code></code></pre><p>With default settings, a failed node is detected within about 30 seconds (10 second timeout multiplied by 3 retries). You can tighten these values for faster detection, but be careful. On loaded clusters, aggressive timeouts can cause false positives where healthy nodes are removed because they were too busy to respond in time.</p><div><hr></div><h2>Cluster State Publishing</h2><p>When the cluster manager makes a change (new index, shard movement, settings update), it needs to broadcast the new cluster state to all nodes. This happens through a two-phase commit process.</p><p>First, the cluster manager sends the update to all nodes and waits for acknowledgment. Then, once enough nodes have acknowledged, it sends a commit message. This ensures that either all nodes get the update or none do.</p><p>The timeout for this process is:</p><pre><code><code>cluster.publish.timeout: 30s</code></code></pre><p>If the cluster manager cannot publish within this timeout, it steps down and a new election begins. This prevents a situation where a network-isolated cluster manager keeps making decisions that no other node knows about.</p><div><hr></div><h2>Cluster Manager Task Throttling</h2><p>Every change to the cluster state goes through the cluster manager. Creating an index, updating a mapping, starting a shard. All of these generate tasks that enter the cluster manager&#8217;s task queue.</p><p>This queue is unbounded by default. That is a problem.</p><p>If your application suddenly sends thousands of create-index requests, or a bug in your ingestion pipeline floods the cluster manager with put-mapping tasks, the queue grows without limit. </p><p>The cluster manager becomes overloaded trying to process all these tasks. Its performance degrades. Fault detection pings time out because the cluster manager is too busy to respond. </p><p>Other nodes think the leader has failed. A new election starts. But the new leader inherits the same flood of tasks.</p><p>This is how a task flood can take down an entire cluster.</p><p>OpenSearch introduced cluster manager task throttling to prevent this. It works by setting limits on how many pending tasks of each type can sit in the queue. When the limit is reached, new tasks of that type are rejected.</p><p>The rejected node retries with exponential backoff. If retries fail within the timeout period, OpenSearch returns a cluster timeout error.</p><p>The key insight is that throttling works per task type. Rejecting put-mapping tasks does not block create-index tasks. This means one misbehaving workload cannot starve other critical operations.</p><div><hr></div><h2>Configuring Task Throttling</h2><p>You enable throttling through cluster settings:</p><pre><code><code>PUT _cluster/settings
{
  "persistent": {
    "cluster_manager.throttling.thresholds": {
      "put-mapping": {
        "value": 100
      },
      "create-index": {
        "value": 25
      }
    }
  }
}</code></code></pre><p>You can also configure retry behavior:</p><pre><code><code>PUT _cluster/settings
{
  "persistent": {
    "cluster_manager.throttling": {
      "retry": {
        "base.delay": "1s",
        "max.delay": "25s"
      },
      "thresholds": {
        "put-mapping": {
          "value": 100
        }
      }
    }
  }
}</code></code></pre><p>To disable throttling for a specific task type, set its value to -1.</p><p>Supported task types include:</p><ul><li><p><code>create-index</code></p></li><li><p><code>update-settings</code></p></li><li><p><code>cluster-update-settings</code></p></li><li><p><code>auto-create</code></p></li><li><p><code>delete-index</code></p></li><li><p><code>delete-dangling-index</code></p></li><li><p><code>create-data-stream</code></p></li><li><p><code>remove-data-stream</code></p></li><li><p><code>rollover-index</code></p></li><li><p><code>index-aliases</code></p></li><li><p><code>put-mapping</code></p></li><li><p><code>create-index-template</code></p></li><li><p><code>remove-index-template</code></p></li></ul><p>You can monitor throttling stats with:</p><pre><code><code>GET _cluster/stats</code></code></pre><p>Look for the <code>cluster_manager_throttling</code> section in the response, which shows total throttled tasks and a breakdown by task type.</p><div><hr></div><h2>AWS vs Alibaba Cloud: What Changes</h2><p>When you use a managed service, most of the discovery and cluster formation work is handled for you. But understanding the differences matters when things go wrong.</p><p><strong>AWS OpenSearch Service.</strong> Discovery and bootstrapping are fully managed. You do not configure seed hosts or bootstrap nodes. AWS handles cluster manager election, fault detection, and node replacement automatically. </p><p>If a cluster manager node fails, AWS replaces it and the new node joins the cluster without manual intervention. </p><p>Cluster manager task throttling is available starting from engine version 1.3, and AWS has fine-tuned the thresholds per cluster size.</p><p><strong>Alibaba Cloud OpenSearch.</strong> Also handles discovery automatically in its managed offering. Node replacement and leader election are managed by the platform. </p><p>The specifics of throttling configuration may differ from the open-source defaults, so check the Alibaba Cloud documentation for your engine version.</p><p><strong>Self-managed on either cloud.</strong> If you run OpenSearch on EC2 or ECS instances yourself, you handle all of this configuration. </p><p>Use the EC2 discovery plugin on AWS or equivalent discovery mechanisms on Alibaba Cloud to automate seed host discovery instead of hardcoding IPs that change with every instance replacement.</p><p>The critical difference: on managed services, you trade configuration control for operational simplicity. You cannot modify discovery settings, fault detection timeouts, or voting configuration. If you need that level of control, self-managed is your path.</p><div><hr></div><h2>A Complete Production Configuration</h2><p>Here is a complete <code>opensearch.yml</code> configuration for a production cluster with three dedicated cluster manager nodes:</p><pre><code><code># Cluster identification
cluster.name: production-cluster

# Node identity (different on each node)
node.name: cluster-manager-1
node.roles: [cluster_manager]

# Network
network.host: 0.0.0.0
transport.port: 9300

# Discovery
discovery.seed_hosts:
  - cluster-manager-1.example.com:9300
  - cluster-manager-2.example.com:9300
  - cluster-manager-3.example.com:9300

# Bootstrap (ONLY for initial cluster formation, remove after)
cluster.initial_cluster_manager_nodes:
  - cluster-manager-1
  - cluster-manager-2
  - cluster-manager-3

# Voting configuration
cluster.auto_shrink_voting_configuration: true
cluster.max_voting_config_exclusions: 3

# Cluster state publishing
cluster.publish.timeout: 60s
cluster.join.timeout: 120s

# Fault detection
cluster.fault_detection.leader_check.interval: 1s
cluster.fault_detection.leader_check.timeout: 10s
cluster.fault_detection.leader_check.retry_count: 3
cluster.fault_detection.follower_check.interval: 1s
cluster.fault_detection.follower_check.timeout: 10s
cluster.fault_detection.follower_check.retry_count: 3</code></code></pre><p>After the cluster forms successfully for the first time, remove the <code>cluster.initial_cluster_manager_nodes</code> line. Leaving it in place is not dangerous on an already-formed cluster, but removing it makes intent clear and prevents confusion during future troubleshooting.</p><div><hr></div><h2>Common Mistakes</h2><ul><li><p><strong>Mismatched node names in bootstrap.</strong> The most frequent error. The value in <code>cluster.initial_cluster_manager_nodes</code> must exactly match <code>node.name</code> on each node. Not the hostname. Not the FQDN. The node name.</p></li><li><p><strong>Leaving bootstrap config on existing clusters.</strong> It works, but it creates confusion. If someone later reads the config, they might think the cluster has not been bootstrapped yet.</p></li><li><p><strong>Too few cluster-manager-eligible nodes.</strong> One cluster manager means zero fault tolerance. Two cluster managers means you cannot survive even one failure because you lose quorum. Three is the practical minimum. Five gives you tolerance for two simultaneous failures but adds election overhead.</p></li><li><p><strong>Running cluster manager and data roles on the same node.</strong> In production, a heavy indexing or search workload can starve the cluster manager of CPU and memory. Dedicated cluster manager nodes are small (2 vCPU, 4 GB RAM is often enough) and their only job is managing the cluster state.</p></li><li><p><strong>Aggressive fault detection timeouts.</strong> Setting the timeout to 1 second sounds like faster recovery. In practice, it means healthy nodes get removed during garbage collection pauses or temporary network blips. The defaults exist for a reason.</p></li><li><p><strong>Ignoring task throttling.</strong> If your application can programmatically create indexes or update mappings, one bug can generate thousands of tasks in seconds. Set throttling thresholds before you need them, not after the cluster is already overwhelmed.</p></li></ul><div><hr></div><h2>What is Next</h2><p>On Day 12, we go deep into cluster performance tuning. You will learn how indexing speed, refresh intervals, merge policies, and shard allocation interact to determine how fast your cluster can ingest and search data. This is where theoretical knowledge becomes measurable performance gains.</p><div><hr></div><p>Interactive guide: <a href="https://opensearch.9cld.com/day/11-discovery-and-cluster-formation">opensearch.9cld.com/day/11-discovery-and-cluster-formation</a></p><p>All guides: opensearch.9cld.com</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://9cld.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading 9Cloud! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Day 10: Migrate and Upgrade OpenSearch ]]></title><description><![CDATA[Choose the wrong strategy and your data pays the price.]]></description><link>https://9cld.com/p/day-10-migrate-and-upgrade-opensearch</link><guid isPermaLink="false">https://9cld.com/p/day-10-migrate-and-upgrade-opensearch</guid><dc:creator><![CDATA[Ankit Mehta]]></dc:creator><pubDate>Fri, 13 Feb 2026 06:59:11 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!_7BR!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdf71e33-02df-43ec-87d5-9f2882d3a7c3_1202x633.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!_7BR!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdf71e33-02df-43ec-87d5-9f2882d3a7c3_1202x633.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!_7BR!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdf71e33-02df-43ec-87d5-9f2882d3a7c3_1202x633.png 424w, https://substackcdn.com/image/fetch/$s_!_7BR!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdf71e33-02df-43ec-87d5-9f2882d3a7c3_1202x633.png 848w, https://substackcdn.com/image/fetch/$s_!_7BR!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdf71e33-02df-43ec-87d5-9f2882d3a7c3_1202x633.png 1272w, https://substackcdn.com/image/fetch/$s_!_7BR!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdf71e33-02df-43ec-87d5-9f2882d3a7c3_1202x633.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!_7BR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdf71e33-02df-43ec-87d5-9f2882d3a7c3_1202x633.png" width="1202" height="633" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fdf71e33-02df-43ec-87d5-9f2882d3a7c3_1202x633.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:633,&quot;width&quot;:1202,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:598269,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://9cld.com/i/187829201?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdf71e33-02df-43ec-87d5-9f2882d3a7c3_1202x633.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!_7BR!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdf71e33-02df-43ec-87d5-9f2882d3a7c3_1202x633.png 424w, https://substackcdn.com/image/fetch/$s_!_7BR!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdf71e33-02df-43ec-87d5-9f2882d3a7c3_1202x633.png 848w, https://substackcdn.com/image/fetch/$s_!_7BR!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdf71e33-02df-43ec-87d5-9f2882d3a7c3_1202x633.png 1272w, https://substackcdn.com/image/fetch/$s_!_7BR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffdf71e33-02df-43ec-87d5-9f2882d3a7c3_1202x633.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>You are running OpenSearch 1.3 in production. It works. Queries come back. Logs get ingested.</p><p>Then someone asks: &#8220;Should we not upgrade to the latest version?&#8221;</p><p>Suddenly, you are staring at documentation pages about rolling upgrades, snapshot compatibility, Lucene index versions, and something called Migration Assistant. Every page assumes you already know which method to pick.</p><p>The hardest part of migration is not the execution. It is choosing the right strategy before you start.</p><p>Pick wrong and you are looking at data loss, unexpected downtime, or a rollback that takes longer than the migration itself.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://9cld.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading 9Cloud! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h2>The One Rule That Changes Everything</h2><p>OpenSearch nodes cannot be downgraded.</p><p>Once you upgrade a node, there is no rolling back to the previous version. Your only recovery path is restoring from a snapshot you took before the upgrade.</p><p>Did you take that snapshot? If not, your options shrink dramatically.</p><p>This is why every migration starts the same way. Snapshot first. Decide second. Execute third.</p><div><hr></div><h2>Why Upgrades Matter More Than You Think</h2><p>OpenSearch follows semantic versioning. Major versions introduce breaking changes. Minor versions add features. Patch versions fix bugs.</p><p>That sounds clean in theory. In practice, it means:</p><ul><li><p>Skipping versions is not always possible</p></li><li><p>Plugin compatibility can break silently between minor versions</p></li><li><p>Lucene index format changes can make your existing data unreadable without reindexing</p></li></ul><p>Staying on an old version does not just mean missing features. It means accumulating security vulnerabilities, losing community support, and eventually hitting a wall where the upgrade path becomes exponentially harder because you waited too long.</p><div><hr></div><h2>The Four Migration Strategies</h2><p>OpenSearch supports four methods. Each makes different tradeoffs between downtime, complexity, infrastructure cost, and version compatibility.</p><p>Understanding these tradeoffs is the entire game.</p><div><hr></div><h3>Rolling Upgrade</h3><p>You upgrade one node at a time while keeping the cluster operational. Data continues to be ingested. Queries keep running. From the outside, the cluster appears normal.</p><p>The catch? Rolling upgrades only work between minor versions within the same major release, or from the last minor of one major to the first of the next.</p><p>You cannot jump from OpenSearch 1.0 to 2.12 in a single rolling upgrade. You need intermediate hops.</p><p>The process follows a precise sequence:</p><ul><li><p>Verify cluster health is green and all shards are allocated</p></li><li><p>Disable shard allocation so OpenSearch does not try to rebalance while nodes are offline</p></li><li><p>Flush the translog to commit recent operations to Lucene segments</p></li><li><p>Stop one node, upgrade it, start it again</p></li><li><p>Re-enable allocation and wait for green status</p></li><li><p>Repeat for each node &#8212; upgrade the active cluster manager node last</p></li></ul><p>Why last? Because an OpenSearch node cannot join a cluster if the cluster manager is running a newer version. If you accidentally upgrade the manager first, the remaining nodes on the old version cannot rejoin.</p><div><hr></div><h3>Snapshot and Restore</h3><p>This is the safest option for major version jumps or infrastructure changes.</p><p>You take a snapshot of your source cluster, stand up a fresh target cluster on the new version, and restore the snapshot. Complete isolation between source and target. If anything goes wrong, your source cluster is untouched.</p><p>Snapshots are forward-compatible by one major version. A snapshot taken on OpenSearch 1.x can be restored on 2.x. But a snapshot from 1.x cannot be restored directly on a hypothetical 3.x. For larger gaps, you need intermediate steps.</p><p>Snapshots are also incremental. After the first full snapshot, each subsequent one only captures what changed. This makes the process practical even for large clusters.</p><p>When restoring for migration, you have useful options:</p><ul><li><p>Choose specific indexes instead of restoring everything</p></li><li><p>Exclude system indexes like .opendistro_security that might conflict</p></li><li><p>Rename indexes during restore to avoid naming collisions</p></li><li><p>Adjust replica counts and shard allocation to match target capacity</p></li></ul><div><hr></div><h3>Remote Reindexing</h3><p>This pulls data directly from a source cluster into a target cluster using the Reindex API. No snapshots. No intermediate storage.</p><p>The source cluster serves as a live data source while the target indexes fresh. This supports large version jumps because the data is reindexed from scratch. Lucene format compatibility is not a concern.</p><p>The downside? Performance impact. The source cluster serves reindex requests on top of its normal workload. It is also slower than snapshot restore for very large datasets.</p><p>But if you need to restructure mappings, change analyzers, or modify shard counts during migration, reindexing is the only method that lets you transform data in flight.</p><div><hr></div><h3>Migration Assistant</h3><p>The most comprehensive option. An end-to-end solution that handles metadata migration, historical data backfill, and live traffic capture and replay.</p><p>How does it work?</p><ul><li><p>A Capture Proxy sits in front of your source cluster and records all incoming HTTP traffic to Kafka</p></li><li><p>A Reindex-from-Snapshot process backfills historical data into the target cluster</p></li><li><p>Once backfill completes, a Traffic Replayer reads from Kafka and replays captured requests against the target</p></li></ul><p>The result is near-zero downtime migration with the ability to compare source and target behavior before cutting over.</p><p>Migration Assistant is the right choice when you need zero-downtime migration from Elasticsearch to OpenSearch, when you want to validate behavior under real workload, or when you are making a major version jump that other methods do not support cleanly.</p><div><hr></div><h2>Which Strategy Should You Pick?</h2><p>Ask yourself three questions.</p><p><strong>Can you tolerate downtime?</strong> </p><ul><li><p>If not, use Rolling Upgrade for minor versions or Migration Assistant for major jumps. </p></li><li><p>If brief downtime is acceptable, Snapshot and Restore gives you the cleanest path.</p></li></ul><p><strong>How large is the version gap?</strong> </p><ul><li><p>Same major version &#8212; Rolling Upgrade. </p></li><li><p>Adjacent major versions &#8212; Snapshot and Restore. </p></li><li><p>Multiple majors or ES 7.11+ &#8212; Migration Assistant or a Logstash bridge.</p></li></ul><p><strong>Do you need to transform data?</strong> </p><ul><li><p>If no, snapshots preserve everything as-is. </p></li><li><p>If you need new mappings or shard counts, Remote Reindex lets you restructure in flight.</p></li></ul><div><hr></div><h2>The Pre-Migration Checklist</h2><p>Regardless of which strategy you choose, the preparation steps are the same. Skip any of these and you are gambling.</p><ul><li><p><strong>Review breaking changes</strong> between current and target version. These are not suggestions. They are things that will break.</p></li><li><p><strong>Check plugin compatibility.</strong> Plugin major, minor, AND patch versions must match OpenSearch exactly. Plugin 2.3.0.x works only with OpenSearch 2.3.0.</p></li><li><p><strong>Review tools compatibility.</strong> Logstash, Beats, Data Prepper &#8212; they may also need upgrading. An OpenSearch upgrade that breaks your ingestion pipeline is worse than no upgrade.</p></li><li><p><strong>Back up configuration files.</strong> opensearch.yml, plugin configs, TLS certificates. These do not migrate automatically.</p></li><li><p><strong>Take a snapshot.</strong> Non-negotiable. Store in external storage like S3, OSS, or NFS. This is your rollback plan.</p></li><li><p><strong>Stop nonessential indexing.</strong> Reduces in-flight data and simplifies recovery if something goes wrong.</p></li></ul><div><hr></div><h2>Version Compatibility</h2><p>OpenSearch nodes within the same major version are compatible regardless of minor version. OpenSearch 1.1.0 can coexist with 1.3.7 in the same cluster.</p><p>Nodes and indexes are backward-compatible with the previous major version. An OpenSearch 2.x cluster can read indexes created on 1.x. But indexes from Elasticsearch 6.x or earlier must be reindexed or deleted before upgrading to OpenSearch 2.x.</p><p>What really determines index compatibility? The underlying Lucene version. Each OpenSearch version ships with a specific Lucene version. When Lucene makes a breaking change to its index format, older indexes must be reindexed.</p><p>For snapshots, the rule is simple. Forward-compatible by one major version only:</p><ul><li><p>ES 7.x snapshot &#8594; restores on OpenSearch 1.x &#10003;</p></li><li><p>OpenSearch 1.x snapshot &#8594; restores on OpenSearch 2.x &#10003;</p></li><li><p>ES 6.x snapshot &#8594; restores on OpenSearch 2.x &#10007; (needs intermediate 1.x step)</p></li></ul><div><hr></div><h2>Migrating from Elasticsearch</h2><p>This is one of the most common scenarios. The path depends on which Elasticsearch version you are running.</p><ul><li><p><strong>ES OSS 7.10.2 or earlier</strong> &#8212; Smoothest path. Rolling upgrade or cluster restart directly to OpenSearch 1.x. The opensearch-upgrade tool automates config import, keystore migration, and core plugin installation.</p></li><li><p><strong>ES OSS 6.x</strong> &#8212; Upgrade to 6.8 first, then to 7.10.2, then migrate to OpenSearch 1.x. No shortcuts.</p></li><li><p><strong>ES 7.11+ (post-fork)</strong> &#8212; Direct migration is not supported. The codebase diverged. Your options are Logstash as a bridge (use version 7.13.4 or earlier) or Migration Assistant.</p></li><li><p><strong>Open Distro (ODFE)</strong> &#8212; Upgrade to ODFE 1.13, then migrate to OpenSearch 1.x. Cleanest path with the fewest surprises.</p></li></ul><div><hr></div><h2>AWS OpenSearch Service</h2><p>AWS offers in-place upgrades with automated pre-upgrade validation. You initiate from the console, CLI, or API. AWS runs checks first and only proceeds if everything passes.</p><p>AWS takes automated hourly snapshots and retains up to 336 of them for 14 days. But here is the detail that catches teams: automated snapshots can only restore within the same domain.</p><p>For cross-domain migration, you need manual snapshots stored in your own S3 bucket. You register an S3 repository with your domain first, then take manual snapshots.</p><p>Watch out for two things. If the target domain has Multi-AZ with Standby enabled, the restore operation will fail &#8212; disable it first. And exclude .opendistro_security indexes from restores to avoid overwriting the target domain security configuration.</p><div><hr></div><h2>Alibaba Cloud</h2><p>Alibaba Cloud uses standard snapshot and restore for migration. The cross-cloud workflow adds steps that AWS-only teams might not expect.</p><p>Migrating from AWS to Alibaba Cloud follows this path:</p><ul><li><p>Create snapshot repository on AWS backed by S3</p></li><li><p>Take full snapshot</p></li><li><p>Create OSS bucket on Alibaba Cloud and register it as a snapshot repository</p></li><li><p>Use ossimport or Data Online Migration service to transfer snapshot files from S3 to OSS</p></li><li><p>Restore on the Alibaba Cloud cluster</p></li></ul><p>For incremental migration, repeat the snapshot-transfer-restore cycle. The final cutover involves stopping writes, taking a final snapshot, transferring, restoring, then switching traffic.</p><p>Alibaba Cloud also provides Data Transmission Service (DTS) for real-time synchronization between clusters. Useful when you need to run source and target in parallel during migration with minimal data lag.</p><div><hr></div><h2>Mini Project: Snapshot Migration Lab</h2><p>Practice the complete workflow locally with two OpenSearch clusters running different versions.</p><p><strong>Docker Compose &#8212; Two Clusters:</strong></p><pre><code><code>version: '3'
services:
  opensearch-source:
    image: opensearchproject/opensearch:2.11.0
    container_name: os-source
    environment:
      - discovery.type=single-node
      - DISABLE_SECURITY_PLUGIN=true
      - path.repo=/snapshots
    volumes:
      - source-data:/usr/share/opensearch/data
      - snapshot-repo:/snapshots
    ports:
      - "9200:9200"

  opensearch-target:
    image: opensearchproject/opensearch:2.17.0
    container_name: os-target
    environment:
      - discovery.type=single-node
      - DISABLE_SECURITY_PLUGIN=true
      - path.repo=/snapshots
    volumes:
      - target-data:/usr/share/opensearch/data
      - snapshot-repo:/snapshots
    ports:
      - "9201:9200"

volumes:
  source-data:
  target-data:
  snapshot-repo:</code></code></pre><p><strong>Index sample data on the source cluster:</strong></p><pre><code><code>curl -X PUT "localhost:9200/products" -H "Content-Type: application/json" -d '{
  "settings": { "number_of_shards": 1, "number_of_replicas": 0 },
  "mappings": {
    "properties": {
      "name": { "type": "text" },
      "category": { "type": "keyword" },
      "price": { "type": "float" }
    }
  }
}'

curl -X POST "localhost:9200/products/_bulk" -H "Content-Type: application/json" -d '
{"index":{"_id":"1"}}
{"name":"Wireless Headphones","category":"electronics","price":79.99}
{"index":{"_id":"2"}}
{"name":"Running Shoes","category":"footwear","price":129.99}
{"index":{"_id":"3"}}
{"name":"Coffee Grinder","category":"kitchen","price":49.99}
'</code></code></pre><p><strong>Register a snapshot repository on the source:</strong></p><pre><code><code>curl -X PUT "localhost:9200/_snapshot/migration_repo" -H "Content-Type: application/json" -d '{
  "type": "fs",
  "settings": { "location": "/snapshots/migration" }
}'</code></code></pre><p><strong>Take a snapshot:</strong></p><pre><code><code>curl -X PUT "localhost:9200/_snapshot/migration_repo/snapshot_1?wait_for_completion=true" \
  -H "Content-Type: application/json" -d '{
  "indices": "products",
  "ignore_unavailable": true,
  "include_global_state": false
}'</code></code></pre><p><strong>Register the same repository on the target:</strong></p><pre><code><code>curl -X PUT "localhost:9201/_snapshot/migration_repo" -H "Content-Type: application/json" -d '{
  "type": "fs",
  "settings": { "location": "/snapshots/migration" }
}'</code></code></pre><p><strong>Verify the snapshot is visible from the target:</strong></p><pre><code><code>curl -X GET "localhost:9201/_snapshot/migration_repo/_all?pretty"</code></code></pre><p><strong>Restore on the target cluster:</strong></p><pre><code><code>curl -X POST "localhost:9201/_snapshot/migration_repo/snapshot_1/_restore" \
  -H "Content-Type: application/json" -d '{
  "indices": "products",
  "ignore_unavailable": true,
  "include_global_state": false
}'</code></code></pre><p><strong>Verify the migration:</strong></p><pre><code><code># Check index exists
curl -X GET "localhost:9201/_cat/indices?v"

# Verify document count
curl -X GET "localhost:9201/products/_count"

# Confirm data integrity
curl -X GET "localhost:9201/products/_search?pretty" -H "Content-Type: application/json" -d '{
  "query": { "match_all": {} }
}'</code></code></pre><p>All three documents on the target with matching content? Migration succeeded.</p><p>In production, you would add the steps of stopping writes, taking a final incremental snapshot, and switching DNS or load balancer targets.</p><div><hr></div><h2>Common Mistakes</h2><ul><li><p><strong>Not snapshotting before upgrading.</strong> Nodes cannot be downgraded. Without a snapshot, your only recovery is rebuilding from source data.</p></li><li><p><strong>Upgrading the cluster manager first.</strong> Other nodes running older versions cannot rejoin. Always upgrade data nodes first, manager last.</p></li><li><p><strong>Restoring .opendistro_security indexes.</strong> This overwrites the target cluster security setup. Configure security separately.</p></li><li><p><strong>Ignoring plugin versions.</strong> A plugin for 2.3.0 will not load on 2.4.0. Check before upgrading, not after the cluster fails to start.</p></li><li><p><strong>Jumping multiple major versions.</strong> ES 6.x directly to OpenSearch 2.x is not supported. Follow the intermediate steps.</p></li></ul><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://9cld.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading 9Cloud! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h2>What is Next</h2><p>On Day 11, we move into Query DSL mastery. You have been writing basic match queries and term filters. Now it is time to learn the full power of OpenSearch queries, bool compounds, function score, nested queries, scripted fields. This is where OpenSearch stops being a search box and becomes a precision instrument.</p><div><hr></div><p>Interactive guide: https://opensearch.9cld.com/day/10-migrate-upgrade</p><p>All interactive guides: https://opensearch.9cld.com</p>]]></content:encoded></item><item><title><![CDATA[Day 9: Building Chatbots with OpenSearch]]></title><description><![CDATA[Give Your Search Engine a Memory]]></description><link>https://9cld.com/p/day-9-building-chatbots-with-opensearch</link><guid isPermaLink="false">https://9cld.com/p/day-9-building-chatbots-with-opensearch</guid><dc:creator><![CDATA[Ankit Mehta]]></dc:creator><pubDate>Wed, 04 Feb 2026 06:21:18 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!cauf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75593d5b-832a-42f0-b5d1-71256eec0a2a_1197x627.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!cauf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75593d5b-832a-42f0-b5d1-71256eec0a2a_1197x627.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!cauf!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75593d5b-832a-42f0-b5d1-71256eec0a2a_1197x627.png 424w, https://substackcdn.com/image/fetch/$s_!cauf!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75593d5b-832a-42f0-b5d1-71256eec0a2a_1197x627.png 848w, https://substackcdn.com/image/fetch/$s_!cauf!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75593d5b-832a-42f0-b5d1-71256eec0a2a_1197x627.png 1272w, https://substackcdn.com/image/fetch/$s_!cauf!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75593d5b-832a-42f0-b5d1-71256eec0a2a_1197x627.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!cauf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75593d5b-832a-42f0-b5d1-71256eec0a2a_1197x627.png" width="1197" height="627" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/75593d5b-832a-42f0-b5d1-71256eec0a2a_1197x627.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:627,&quot;width&quot;:1197,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:551037,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://9cld.com/i/186828521?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75593d5b-832a-42f0-b5d1-71256eec0a2a_1197x627.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!cauf!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75593d5b-832a-42f0-b5d1-71256eec0a2a_1197x627.png 424w, https://substackcdn.com/image/fetch/$s_!cauf!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75593d5b-832a-42f0-b5d1-71256eec0a2a_1197x627.png 848w, https://substackcdn.com/image/fetch/$s_!cauf!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75593d5b-832a-42f0-b5d1-71256eec0a2a_1197x627.png 1272w, https://substackcdn.com/image/fetch/$s_!cauf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F75593d5b-832a-42f0-b5d1-71256eec0a2a_1197x627.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>The Problem With Stateless Search</h2><p>You built a RAG pipeline. It works. A user asks a question, OpenSearch retrieves relevant documents, and the LLM generates a grounded answer. Beautiful.</p><p>Then the user asks a follow-up.</p><p>&#8220;What is the population of Seattle?&#8221; Great answer. &#8220;How does that compare to Austin?&#8221; The system has no idea what &#8220;that&#8221; refers to. It forgot Seattle the moment it answered.</p><p>This is the wall every search application hits. Real users do not ask one question and leave. They have conversations. Each question builds on the last. &#8220;Which one is growing faster?&#8221; only makes sense if the system remembers we were talking about Seattle and Austin.</p><p>A stateless RAG pipeline cannot handle this. It treats every request like the first one.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://9cld.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading 9Cloud! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h2>What Makes a Chatbot Different from RAG?</h2><p>Two things. Conversation memory and intelligent tool routing.</p><p>On Day 7, we built RAG with OpenSearch. That gave us grounded answers from a single knowledge base. Today, we add memory so the chatbot remembers what was said before. And we add routing so it can pick the right knowledge base automatically when you have more than one.</p><p>The result is not just a search with a chat interface. It is a system that reasons about which data source to query, remembers what it already told you, and builds on previous answers.</p><div><hr></div><h2>How the Architecture Works</h2><p>What happens when a user sends a message to an OpenSearch chatbot?</p><p>The question arrives through the Execute Agent API. If the user includes a memory_id, the agent knows this is a continuation of a previous conversation. It loads the chat history from its memory index.</p><p>Then it decides which tools to run. For a RAG chatbot, that usually means a VectorDBTool performing a semantic search against a knowledge base. But if you have multiple knowledge bases, the agent reads each tool&#8217;s description and picks the one that matches the question.</p><p>The retrieved documents and conversation history get assembled into a prompt. That prompt goes to the LLM. The LLM generates a response grounded in real data, not its training memory. And finally, the question and answer get stored back into the conversation index so future questions have context.</p><p>All of this happens inside OpenSearch. No external orchestration service. No separate vector database. No custom middleware. One API call through the ML Commons plugin.</p><div><hr></div><h2>Three Agent Types, Three Decision Styles</h2><p>OpenSearch gives you three agent types. Choosing the right one determines how your chatbot thinks.</p><p>The conversational_flow agent runs tools in a fixed sequence. Think of it like a recipe. First search the knowledge base, then send results to the LLM. Always that order. </p><p>The output of one tool flows into the next through variable chaining. Predictable. Easy to debug. Perfect when you have one knowledge base and a straightforward search-then-answer pattern.</p><p>The conversational agent lets the LLM decide which tool to call. This is the dynamic option. </p><p>You give it two knowledge bases, one for population data and one for tech news, and it reads the tool descriptions to figure out which one matches the question. </p><p>Ask &#8220;What is Vision Pro?&#8221; and it picks the tech news tool. Ask &#8220;Population of Seattle?&#8221; and it picks the population tool. This is the one you want for multi-domain chatbots.</p><p>The flow agent is the stateless version. Runs tools sequentially but does not store conversation history. Good for one-shot queries where memory is unnecessary.</p><p>Which should you pick? If you have a single knowledge base, start with conversational_flow. If you have multiple data sources, use conversational. If you do not need follow-up questions, flow is enough.</p><div><hr></div><h2>Building a Multi-Knowledge-Base Chatbot</h2><p>Let us build the real thing. Two knowledge bases, one agent, dynamic routing.</p><p>The first knowledge base contains US city population data. We set that up in earlier days using a vector index with an ingest pipeline that generates embeddings automatically. </p><p>The second contains recent tech news about products like Apple Vision Pro, Meta&#8217;s LLaMA models, and Amazon Bedrock.</p><p>Setting up the tech news knowledge base follows the same pattern from previous days. </p><ul><li><p>Create an ingest pipeline that maps the passage field to an embedding field. </p></li><li><p>Create a knn index with cosine similarity. </p></li><li><p>Bulk ingest the articles. </p></li></ul><p>The pipeline handles vector embeddings at ingest time.</p><p>Now the critical part. Registering the conversational agent:</p><pre><code><code>POST _plugins/_ml/agents/_register
{
  "name": "Chat Agent with RAG",
  "type": "conversational",
  "llm": {
    "model_id": "your_llm_model_id",
    "parameters": {
      "max_iteration": 5,
      "response_filter": "$.completion"
    }
  },
  "memory": { "type": "conversation_index" },
  "tools": [
    {
      "type": "VectorDBTool",
      "name": "population_data_knowledge_base",
      "description": "This tool provides population data of US cities.",
      "parameters": {
        "input": "${parameters.question}",
        "index": "test_population_data",
        "source_field": ["population_description"],
        "model_id": "your_text_embedding_model_id",
        "embedding_field": "population_description_embedding",
        "doc_size": 3
      }
    },
    {
      "type": "VectorDBTool",
      "name": "tech_news_knowledge_base",
      "description": "This tool provides recent tech news.",
      "parameters": {
        "input": "${parameters.question}",
        "index": "test_tech_news",
        "source_field": ["passage"],
        "model_id": "your_text_embedding_model_id",
        "embedding_field": "passage_embedding",
        "doc_size": 2
      }
    }
  ],
  "app_type": "chat_with_rag"
}</code></code></pre><p>See the description field in each tool? That is the secret. The LLM reads &#8220;provides population data of US cities&#8221; and &#8220;provides recent tech news&#8221; and decides which tool fits the question. Clear, specific descriptions are the difference between a chatbot that routes correctly and one that searches the wrong knowledge base every time.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://9cld.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading 9Cloud! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h2>Conversation Memory Changes Everything</h2><p>Ask about Seattle&#8217;s population. Get an answer. Now ask &#8220;How does Austin compare?&#8221; with the same memory_id.</p><p>The agent searches the population knowledge base for Austin&#8217;s data. But it also has the previous Seattle answer in its conversation history. The LLM generates a comparison without needing to re-query Seattle. That is how real conversations work.</p><p>How does the memory system organize this? Two levels. </p><ul><li><p>A memory groups the entire conversation, identified by memory_id. </p></li><li><p>Within that memory, each question-answer pair is a message, identified by parent_message_id.</p></li></ul><p>You can inspect any conversation through the Memory APIs. Want to see what the agent did behind the scenes? Retrieve execution traces that show which tools ran, what results they returned, and how the LLM used them. This is invaluable for debugging.</p><div><hr></div><h2>When You Want Full Control: Output Chaining</h2><p>The conversational agent is smart. But sometimes you do not want the LLM deciding things. You want a fixed pipeline.</p><p>That is what the conversational_flow agent gives you. Define the exact tool sequence during registration. The agent follows it every time.</p><p>The key mechanism is output chaining. </p><p>When you write <code>${parameters.population_knowledge_base.output:-}</code> in the MLModelTool prompt, you inject the VectorDBTool output directly into the LLM context. </p><p>The <code>:-</code> suffix is a safe default. If the tool produces no output, it passes an empty string instead of breaking the prompt.</p><p>You can also skip tools at runtime. </p><p>If a user asks &#8220;Translate last answer into Chinese&#8221;, you do not need to search the knowledge base again. </p><p>Pass <code>"selected_tools": ["bedrock_claude_model"]</code> and only the LLM runs.</p><div><hr></div><h2>Beyond VectorDBTool: The Full Toolkit</h2><p>The build-your-own-chatbot tutorial introduces tools that go far beyond vector search.</p><ul><li><p>ListIndexTool returns metadata about all indexes in your cluster. </p></li><li><p>SearchIndexTool lets the agent run arbitrary OpenSearch queries, not just semantic searches. </p></li><li><p>CatIndexTool provides index statistics. </p></li><li><p>PPLTool converts natural language into Piped Processing Language queries and executes them.</p></li></ul><p>PPLTool is particularly interesting. A user asks &#8220;How many orders do I have in last week?&#8221; and the agent translates that into a PPL query, runs it against your eCommerce index, and has the LLM interpret the results in natural language. You just turned OpenSearch into a conversational analytics platform.</p><p>For production chatbots, consider combining multiple VectorDBTools in a single conversational_flow agent. </p><ul><li><p>A product recommendation bot might have one tool for product descriptions and another for product reviews. </p></li><li><p>The MLModelTool prompt references both outputs, giving the LLM comprehensive context to generate well-rounded recommendations.</p></li></ul><div><hr></div><h2>Cloud Platform Differences</h2><p>On AWS OpenSearch Service, the full ML Commons agent framework is available with native Bedrock connectors. You authenticate using Sigv4 signing, and the connector handles credential management. Your chatbot&#8217;s LLM backend runs on Bedrock while retrieval and orchestration happen on OpenSearch Service.</p><p>Alibaba Cloud&#8217;s managed OpenSearch service provides its own intelligent search capabilities that differ from the open-source agent framework. Model Studio offers Qwen models as the LLM backend. If you need the exact ML Commons agent APIs, run a self-managed OpenSearch cluster on Alibaba Cloud ECS instances. Full control over the agent framework, Alibaba Cloud infrastructure for compute and networking.</p><p>The connector configuration differs between platforms. </p><ul><li><p>AWS uses aws_sigv4 protocol with access keys and session tokens. </p></li><li><p>Alibaba Cloud uses AccessKey-based authentication with RAM permissions. </p></li></ul><p>Same underlying agent types, different endpoint URLs and request body formats.</p><div><hr></div><h2>What is Next</h2><p>Tomorrow, we continue building on these chatbot patterns. The agent framework you learned today is the foundation for more advanced workflows involving guardrails, agentic memory, and production deployment considerations.</p><p>The interactive guide: opensearch.9cld.com/day/09-chatbots <br>All previous guides are at opensearch.9cld.com</p>]]></content:encoded></item><item><title><![CDATA[Day 8: Agentic AI ]]></title><description><![CDATA[Teaching OpenSearch to Think, Plan, and Act]]></description><link>https://9cld.com/p/day-8-agentic-ai</link><guid isPermaLink="false">https://9cld.com/p/day-8-agentic-ai</guid><dc:creator><![CDATA[Ankit Mehta]]></dc:creator><pubDate>Fri, 30 Jan 2026 05:30:24 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!8aaX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e2e255f-8e2d-4e8f-99f8-93b9a0147ddb_1202x631.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8aaX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e2e255f-8e2d-4e8f-99f8-93b9a0147ddb_1202x631.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8aaX!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e2e255f-8e2d-4e8f-99f8-93b9a0147ddb_1202x631.png 424w, https://substackcdn.com/image/fetch/$s_!8aaX!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e2e255f-8e2d-4e8f-99f8-93b9a0147ddb_1202x631.png 848w, https://substackcdn.com/image/fetch/$s_!8aaX!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e2e255f-8e2d-4e8f-99f8-93b9a0147ddb_1202x631.png 1272w, https://substackcdn.com/image/fetch/$s_!8aaX!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e2e255f-8e2d-4e8f-99f8-93b9a0147ddb_1202x631.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8aaX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e2e255f-8e2d-4e8f-99f8-93b9a0147ddb_1202x631.png" width="1202" height="631" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4e2e255f-8e2d-4e8f-99f8-93b9a0147ddb_1202x631.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:631,&quot;width&quot;:1202,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:654119,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://9cld.com/i/186273742?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e2e255f-8e2d-4e8f-99f8-93b9a0147ddb_1202x631.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!8aaX!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e2e255f-8e2d-4e8f-99f8-93b9a0147ddb_1202x631.png 424w, https://substackcdn.com/image/fetch/$s_!8aaX!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e2e255f-8e2d-4e8f-99f8-93b9a0147ddb_1202x631.png 848w, https://substackcdn.com/image/fetch/$s_!8aaX!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e2e255f-8e2d-4e8f-99f8-93b9a0147ddb_1202x631.png 1272w, https://substackcdn.com/image/fetch/$s_!8aaX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e2e255f-8e2d-4e8f-99f8-93b9a0147ddb_1202x631.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>You have built a RAG system. It retrieves documents, sends them to an LLM, and generates answers. But what happens when the question requires more than one step? What if your system needs to first understand the schema, then decide which index to query, then execute the search, then analyze the results, and finally synthesize an answer?</p><p>Traditional RAG falls apart. You end up writing custom orchestration logic for every possible scenario. And as complexity grows, so does your codebase.</p><p>OpenSearch 2.13 introduced something different. Agents. These are not just pipelines that run tools in sequence. They are coordinators that use LLMs to reason about problems, decide what actions to take, and adapt their approach based on intermediate results.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://9cld.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading 9Cloud! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>Let&#8217;s explore how OpenSearch turns your search cluster into an intelligent system that can plan, execute, and reflect.</p><div><hr></div><h2>What Is an Agent?</h2><p>An agent is a coordinator that uses a large language model to solve problems. But coordination is the keyword here. The LLM does the thinking. The agent handles everything else.</p><p>When you ask an agent a question, the following sequence unfolds:</p><ol><li><p>The LLM receives your question along with descriptions of available tools</p></li><li><p>The LLM reasons about which tool would help answer the question</p></li><li><p>The agent executes that tool and captures the output</p></li><li><p>The LLM receives the output and decides whether to use another tool or provide a final answer</p></li><li><p>This cycle continues until the LLM has enough information</p></li></ol><p>The agent manages this entire loop. It handles tool execution, captures outputs, formats prompts, and maintains conversation history. The LLM focuses purely on reasoning.</p><p>This separation is powerful. You do not need to embed tool execution logic into your prompts. You do not need to parse LLM responses to figure out which tool to call. The agent framework handles all of that.</p><div><hr></div><h2>The Four Agent Types</h2><p>OpenSearch supports four distinct agent types, each designed for different use cases.</p><h3>Flow Agent</h3><p>A flow agent runs tools sequentially in a fixed order. You define the sequence when you register the agent, and it never deviates.</p><p>Think of it as a simple pipeline. Tool A runs first, its output feeds into Tool B, and Tool B produces the final result.</p><p>This is perfect for RAG. The VectorDBTool retrieves relevant documents. The MLModelTool sends those documents plus the question to an LLM. The LLM generates an answer. Every question follows this exact path.</p><p>Flow agents are fast because there is no reasoning overhead. The LLM is not deciding which tool to use. It only generates the final answer based on retrieved context.</p><h3>Conversational Flow Agent</h3><p>This is a flow agent with memory. The execution pattern is identical. Tools run in sequence. The difference is that conversation history persists across interactions.</p><p>When a user asks a follow-up question, the agent can reference previous exchanges. This enables multi-turn conversations where context accumulates over time.</p><p>Chatbots built on RAG typically use conversational flow agents. The user asks a question, gets an answer, then asks a clarifying question. The second question makes sense only because the agent remembers the first.</p><h3>Conversational Agent</h3><p>This is where things get interesting. A conversational agent does not follow a fixed execution path. Instead, it reasons about which tools to use based on the question.</p><p>You configure the agent with an LLM and a set of available tools. When a question arrives, the LLM evaluates the question against tool descriptions and decides which tool would help. After executing that tool, the LLM evaluates again. Should it use another tool? Should it provide a final answer?</p><p>This iterative reasoning process is called Chain-of-Thought. The LLM thinks step by step, using tools as needed, until it reaches a conclusion.</p><p>Conversational agents are more flexible than flow agents. They can handle questions that require different tool combinations. The same agent might use VectorDBTool for one question and CATIndexTool for another.</p><p>But flexibility comes with cost. Each reasoning step requires an LLM call. Complex questions might trigger five or ten LLM calls before reaching an answer. This adds latency and expense.</p><h3>Plan-Execute-Reflect Agent</h3><p>The plan-execute-reflect agent handles the most complex scenarios. It breaks down multi-step tasks into discrete steps, executes each step, and adapts its plan based on results.</p><p>The process works in three phases:</p><ul><li><p><strong>Planning</strong>: A planner LLM receives the task and generates a step-by-step plan. Each step describes what to do, which tool to use, and what information to gather.</p></li><li><p><strong>Execution</strong>: The agent executes each step using an internal conversational agent. One step might search an index. Another might analyze the schema. Each produces intermediate results.</p></li><li><p><strong>Reflection</strong>: After executing a step, the planner LLM receives the results and re-evaluates the plan. Should the next step change based on what we learned? Should we skip steps? Add new ones?</p></li></ul><p>This iterative refinement is what makes plan-execute-reflect agents so powerful. They adapt to what they discover during execution.</p><p>Consider a troubleshooting scenario. You ask the agent to identify why a service is failing. The initial plan might be: analyze logs, check traces, examine metrics. </p><p>But while analyzing logs, the agent discovers a specific error pattern. It updates the plan to focus on that pattern, skipping unrelated steps and adding new ones to investigate the root cause.</p><p>Plan-execute-reflect agents run asynchronously because they can take significant time. You submit the task, receive a task ID, and poll for completion.</p><div><hr></div><h2>Tools: What Agents Can Actually Do</h2><p>Tools are the actions agents can take. Each tool performs a specific task and returns results that the agent can use for further reasoning.</p><p>OpenSearch provides a comprehensive set of built-in tools:</p><ul><li><p><strong>VectorDBTool</strong> performs semantic search using vector embeddings. You configure it with an embedding model, target index, and embedding field. When executed, it converts the question into a vector and retrieves similar documents.</p></li><li><p><strong>MLModelTool</strong> sends prompts to an LLM and returns the response. This is how agents generate final answers or intermediate analysis. You configure it with a model ID and prompt template.</p></li><li><p><strong>SearchIndexTool</strong> executes DSL queries against OpenSearch indexes. Unlike VectorDBTool, this performs traditional keyword search with full query DSL support.</p></li><li><p><strong>ListIndexTool</strong> returns information about available indexes. Useful when an agent needs to understand what data exists before querying it.</p></li><li><p><strong>IndexMappingTool</strong> retrieves the schema of an index. This helps agents understand field types and structure before constructing queries.</p></li><li><p><strong>CATIndexTool</strong> executes the cat indices API, providing health status, document counts, and storage information for indexes.</p></li><li><p><strong>PPLTool</strong> executes Piped Processing Language queries. PPL is an alternative query syntax that some users find more intuitive than DSL.</p></li><li><p><strong>WebSearchTool</strong> searches the web for external information. This extends agent capabilities beyond your OpenSearch data.</p></li><li><p><strong>QueryPlanningTool</strong> is special. It converts natural language questions into DSL queries. This is the foundation of agentic search, where users ask questions in plain English and the system generates appropriate queries.</p></li></ul><p>You can also create custom tools using the AgentTool, which wraps another agent as a tool. This enables hierarchical agent architectures where a parent agent delegates subtasks to specialized child agents.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://9cld.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading 9Cloud! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h2>Agentic Search: Natural Language to DSL</h2><p>Agentic search is OpenSearch&#8217;s flagship agent application. It lets users ask questions in natural language and receive search results without writing DSL queries.</p><p>The system works like this:</p><ol><li><p>User submits a natural language question</p></li><li><p>QueryPlanningTool analyzes the question and index schema</p></li><li><p>The LLM generates an appropriate DSL query</p></li><li><p>OpenSearch executes the query</p></li><li><p>Results return to the user</p></li></ol><p>Behind the scenes, an agent with QueryPlanningTool orchestrates this flow. The agent can be a simple flow agent for straightforward queries, or a conversational agent for complex scenarios requiring multiple tools.</p><p>Agentic search in practice starts with registering a connector to your LLM:</p><pre><code><code>POST /_plugins/_ml/connectors/_create
{
  "name": "Claude Connector",
  "description": "Connector for Anthropic Claude",
  "version": "1.0",
  "protocol": "aws_sigv4",
  "credential": {
    "access_key": "your_access_key",
    "secret_key": "your_secret_key",
    "session_token": "your_session_token"
  },
  "parameters": {
    "region": "us-east-1",
    "service_name": "bedrock",
    "model": "anthropic.claude-3-sonnet-20240229-v1:0"
  },
  "actions": [
    {
      "action_type": "predict",
      "method": "POST",
      "url": "https://bedrock-runtime.us-east-1.amazonaws.com/model/anthropic.claude-3-sonnet-20240229-v1:0/converse",
      "headers": {
        "Content-Type": "application/json"
      },
      "request_body": "{ \"messages\": [{\"role\": \"user\", \"content\": [{\"text\": \"${parameters.prompt}\"}]}] }"
    }
  ]
}</code></code></pre><p>Then register a model using that connector:</p><pre><code><code>POST /_plugins/_ml/models/_register
{
  "name": "Claude Model for Agentic Search",
  "function_name": "remote",
  "description": "Claude model for query planning",
  "connector_id": "your_connector_id"
}</code></code></pre><p>Deploy the model and register an agent:</p><pre><code><code>POST /_plugins/_ml/agents/_register
{
  "name": "Agentic Search Agent",
  "type": "conversational",
  "description": "Agent for natural language search",
  "llm": {
    "model_id": "your_model_id",
    "parameters": {
      "max_iteration": 10
    }
  },
  "memory": {
    "type": "conversation_index"
  },
  "parameters": {
    "_llm_interface": "bedrock/converse"
  },
  "tools": [
    {
      "type": "QueryPlanningTool"
    }
  ],
  "app_type": "os_chat"
}</code></code></pre><p>Create a search pipeline that uses the agent:</p><pre><code><code>PUT _search/pipeline/agentic-pipeline
{
  "request_processors": [
    {
      "agentic_query_translator": {
        "agent_id": "your_agent_id"
      }
    }
  ]
}</code></code></pre><p>Now you can search with natural language:</p><pre><code><code>GET products/_search?search_pipeline=agentic-pipeline
{
  "query": {
    "agentic": {
      "query_text": "Find me blue shoes under 100 dollars",
      "query_fields": ["product_name", "color", "price"]
    }
  }
}</code></code></pre><p>The agent receives this question, analyzes the products index schema, and generates a DSL query that filters by color and price range. You receive search results without writing a single line of query DSL.</p><div><hr></div><h2>Building a RAG Agent Step by Step</h2><p>Let us build a complete RAG agent using a flow agent architecture. This agent will retrieve documents from a vector index and use an LLM to generate answers.</p><h3>Step 1: Configure Cluster Settings</h3><p>Enable ML Commons features:</p><pre><code><code>PUT _cluster/settings
{
  "persistent": {
    "plugins.ml_commons.only_run_on_ml_node": "false",
    "plugins.ml_commons.memory_feature_enabled": "true"
  }
}</code></code></pre><h3>Step 2: Deploy an Embedding Model</h3><p>Register a text embedding model for vector search:</p><pre><code><code>POST /_plugins/_ml/models/_register?deploy=true
{
  "name": "huggingface/sentence-transformers/all-MiniLM-L12-v2",
  "version": "1.0.2",
  "model_format": "TORCH_SCRIPT"
}</code></code></pre><p>Note the model ID from the response.</p><h3>Step 3: Create a Vector Index</h3><p>Create an index with a k-NN vector field:</p><pre><code><code>PUT knowledge_base
{
  "settings": {
    "index.knn": true
  },
  "mappings": {
    "properties": {
      "content": {
        "type": "text"
      },
      "embedding": {
        "type": "knn_vector",
        "dimension": 384,
        "method": {
          "name": "hnsw",
          "space_type": "l2",
          "engine": "lucene"
        }
      }
    }
  }
}</code></code></pre><h3>Step 4: Create an Ingest Pipeline</h3><p>Set up automatic embedding generation:</p><pre><code><code>PUT _ingest/pipeline/embedding-pipeline
{
  "processors": [
    {
      "text_embedding": {
        "model_id": "your_embedding_model_id",
        "field_map": {
          "content": "embedding"
        }
      }
    }
  ]
}</code></code></pre><h3>Step 5: Index Documents</h3><p>Add knowledge base content:</p><pre><code><code>POST knowledge_base/_doc?pipeline=embedding-pipeline
{
  "content": "OpenSearch is a community-driven, open source search and analytics suite derived from Apache 2.0 licensed Elasticsearch 7.10.2 and Kibana 7.10.2."
}

POST knowledge_base/_doc?pipeline=embedding-pipeline
{
  "content": "OpenSearch supports vector search through the k-NN plugin, enabling semantic search and similarity matching using dense vector embeddings."
}</code></code></pre><h3>Step 6: Set Up the LLM Connector</h3><p>Create a connector to your preferred LLM:</p><pre><code><code>POST /_plugins/_ml/connectors/_create
{
  "name": "Bedrock Claude Connector",
  "description": "Connector for Claude on Bedrock",
  "version": "1.0",
  "protocol": "aws_sigv4",
  "credential": {
    "access_key": "your_access_key",
    "secret_key": "your_secret_key"
  },
  "parameters": {
    "region": "us-east-1",
    "service_name": "bedrock"
  },
  "actions": [
    {
      "action_type": "predict",
      "method": "POST",
      "url": "https://bedrock-runtime.us-east-1.amazonaws.com/model/anthropic.claude-3-sonnet-20240229-v1:0/invoke",
      "headers": {
        "Content-Type": "application/json"
      },
      "request_body": "{ \"anthropic_version\": \"bedrock-2023-05-31\", \"max_tokens\": 1024, \"messages\": [{\"role\": \"user\", \"content\": \"${parameters.prompt}\"}] }",
      "post_process_function": "return params.content[0].text;"
    }
  ]
}</code></code></pre><h3>Step 7: Register and Deploy the LLM</h3><pre><code><code>POST /_plugins/_ml/models/_register
{
  "name": "Claude for RAG",
  "function_name": "remote",
  "description": "Claude model for generating answers",
  "connector_id": "your_connector_id"
}</code></code></pre><p>Deploy the model:</p><pre><code><code>POST /_plugins/_ml/models/your_llm_model_id/_deploy</code></code></pre><h3>Step 8: Register the Flow Agent</h3><p>Now create an agent that combines VectorDBTool and MLModelTool:</p><pre><code><code>POST /_plugins/_ml/agents/_register
{
  "name": "Knowledge Base RAG Agent",
  "type": "flow",
  "description": "RAG agent for knowledge base queries",
  "tools": [
    {
      "type": "VectorDBTool",
      "name": "knowledge_retriever",
      "parameters": {
        "model_id": "your_embedding_model_id",
        "index": "knowledge_base",
        "embedding_field": "embedding",
        "source_field": ["content"],
        "input": "${parameters.question}"
      }
    },
    {
      "type": "MLModelTool",
      "name": "answer_generator",
      "description": "Generates answers based on retrieved context",
      "parameters": {
        "model_id": "your_llm_model_id",
        "prompt": "Human: You are a helpful assistant. Answer the question based only on the provided context. If the context does not contain enough information, say so.\n\nContext:\n${parameters.knowledge_retriever.output}\n\nQuestion: ${parameters.question}\n\nAssistant:"
      }
    }
  ]
}</code></code></pre><h3>Step 9: Execute the Agent</h3><p>Ask the agent a question:</p><pre><code><code>POST /_plugins/_ml/agents/your_agent_id/_execute
{
  "parameters": {
    "question": "What is OpenSearch and does it support vector search?"
  }
}</code></code></pre><p>The agent retrieves relevant documents using vector similarity, passes them to Claude, and returns a contextual answer.</p><div><hr></div><h2>Memory: Enabling Multi-Turn Conversations</h2><p>Agents can maintain conversation history using memory. This enables follow-up questions that reference previous exchanges.</p><p>When registering an agent with memory:</p><pre><code><code>POST /_plugins/_ml/agents/_register
{
  "name": "Conversational Knowledge Agent",
  "type": "conversational_flow",
  "description": "Agent with conversation memory",
  "app_type": "rag",
  "memory": {
    "type": "conversation_index"
  },
  "tools": [
    {
      "type": "VectorDBTool",
      "parameters": {
        "model_id": "your_embedding_model_id",
        "index": "knowledge_base",
        "embedding_field": "embedding",
        "source_field": ["content"],
        "input": "${parameters.question}"
      }
    },
    {
      "type": "MLModelTool",
      "parameters": {
        "model_id": "your_llm_model_id",
        "prompt": "Previous conversation:\n${parameters.chat_history}\n\nContext:\n${parameters.VectorDBTool.output}\n\nQuestion: ${parameters.question}\n\nAnswer:"
      }
    }
  ]
}</code></code></pre><p>The first execution creates a memory ID:</p><pre><code><code>POST /_plugins/_ml/agents/your_agent_id/_execute
{
  "parameters": {
    "question": "What is OpenSearch?"
  }
}</code></code></pre><p>The response includes a <code>memory_id</code>. Use it for follow-up questions:</p><pre><code><code>POST /_plugins/_ml/agents/your_agent_id/_execute
{
  "parameters": {
    "question": "Does it support machine learning?",
    "memory_id": "memory_id_from_previous_response"
  }
}</code></code></pre><p>The agent now has context from the previous exchange. It understands that &#8220;it&#8221; refers to OpenSearch.</p><div><hr></div><h2>Plan-Execute-Reflect in Action</h2><p>For complex tasks requiring multi-step reasoning, use a plan-execute-reflect agent. Setting one up for troubleshooting scenarios looks like this:</p><pre><code><code>POST /_plugins/_ml/agents/_register
{
  "name": "Troubleshooting Agent",
  "type": "plan_execute_and_reflect",
  "description": "Agent for investigating system issues",
  "llm": {
    "model_id": "your_llm_model_id",
    "parameters": {
      "prompt": "${parameters.question}"
    }
  },
  "memory": {
    "type": "conversation_index"
  },
  "parameters": {
    "_llm_interface": "bedrock/converse"
  },
  "tools": [
    {
      "type": "ListIndexTool"
    },
    {
      "type": "IndexMappingTool"
    },
    {
      "type": "SearchIndexTool"
    },
    {
      "type": "VectorDBTool",
      "parameters": {
        "model_id": "your_embedding_model_id",
        "index": "logs",
        "embedding_field": "embedding",
        "source_field": ["message"],
        "input": "${parameters.input}"
      }
    }
  ],
  "app_type": "os_chat"
}</code></code></pre><p>Execute asynchronously:</p><pre><code><code>POST /_plugins/_ml/agents/your_agent_id/_execute?async=true
{
  "parameters": {
    "question": "Why is the checkout service failing? Analyze logs and traces to identify the root cause."
  }
}</code></code></pre><p>The agent will:</p><ol><li><p>Plan steps: list available indexes, understand schemas, search logs, and analyze patterns</p></li><li><p>Execute each step, gathering information</p></li><li><p>Reflect after each step, adjusting the plan based on findings</p></li><li><p>Provide a final analysis with root cause identification</p></li></ol><div><hr></div><h2>MCP: Extending Agent Capabilities</h2><p>Model Context Protocol (MCP) enables agents to connect to external tools and data sources. This is how you extend agent capabilities beyond OpenSearch&#8217;s built-in tools.</p><p>First, enable MCP in cluster settings:</p><pre><code><code>PUT _cluster/settings
{
  "persistent": {
    "plugins.ml_commons.mcp_connector_enabled": "true"
  }
}</code></code></pre><p>Create an MCP connector:</p><pre><code><code>POST /_plugins/_ml/connectors/_create
{
  "name": "Weather MCP Connector",
  "description": "Connects to weather service via MCP",
  "version": "1.0",
  "protocol": "mcp",
  "parameters": {
    "endpoint": "https://weather-service.example.com/mcp"
  }
}</code></code></pre><p>Register an agent that uses the MCP connector:</p><pre><code><code>POST /_plugins/_ml/agents/_register
{
  "name": "Weather-Aware Search Agent",
  "type": "conversational",
  "llm": {
    "model_id": "your_llm_model_id",
    "parameters": {
      "max_iteration": 5
    }
  },
  "memory": {
    "type": "conversation_index"
  },
  "parameters": {
    "_llm_interface": "bedrock/converse",
    "mcp_connectors": [
      {
        "mcp_connector_id": "your_mcp_connector_id",
        "tool_filters": ["^get_weather$", "^get_forecast$"]
      }
    ]
  },
  "tools": [
    {
      "type": "SearchIndexTool"
    }
  ],
  "app_type": "os_chat"
}</code></code></pre><p>Now the agent can fetch weather data from external services while also searching your OpenSearch indexes.</p><div><hr></div><h2>Agent Best Practices</h2><h3>Choose the Right Agent Type</h3><p>Use flow agents when the execution path is predictable. RAG is the classic example. Every question follows the same pattern, retrieve then generate.</p><p>Use conversational agents when questions might require different tool combinations. If some questions need vector search while others need schema inspection, a conversational agent can choose the right approach.</p><p>Use plan-execute-reflect agents for complex, multi-step tasks. Root cause analysis, research queries, and tasks requiring iterative refinement benefit from this architecture.</p><h3>Optimize Tool Descriptions</h3><p>Conversational agents rely on tool descriptions to decide which tool to use. Vague descriptions lead to poor tool selection.</p><p>Bad description: &#8220;A tool for searching&#8221;</p><p>Good description: &#8220;Searches the products index using semantic similarity. Use this when questions involve finding products by description, features, or attributes. Returns the top 5 most similar products with their names, prices, and descriptions.&#8221;</p><h3>Limit Tools Per Agent</h3><p>Each tool adds complexity to the LLM&#8217;s reasoning process. An agent with twenty tools will make more mistakes than one with five.</p><p>Create specialized agents for different domains rather than one agent that does everything. A product search agent, a log analysis agent, and a customer support agent will each perform better than a single omniscient agent.</p><h3>Handle Failures Gracefully</h3><p>LLM calls can fail. Network issues, rate limits, and model errors all happen. Configure retry policies:</p><pre><code><code>PUT /_plugins/_ml/connectors/your_connector_id
{
  "client_config": {
    "max_retry_times": 3,
    "retry_backoff_millis": 500,
    "retry_backoff_policy": "exponential_full_jitter"
  }
}</code></code></pre><p>For plan-execute-reflect agents running long tasks, set max_retry_times to -1 for unlimited retries with backoff.</p><h3>Monitor Agent Performance</h3><p>Use the Get Message Traces API to understand what agents are doing</p><pre><code><code>GET /_plugins/_ml/memory/message/your_message_id/traces</code></code></pre><p>This returns the sequence of tools used, intermediate outputs, and reasoning steps. Essential for debugging when agents produce unexpected results.</p><div><hr></div><h2>Cloud Deployment Considerations</h2><h3>AWS OpenSearch Service</h3><p>Amazon OpenSearch Service supports agents starting with version 2.13. Key considerations:</p><ul><li><p>Use Amazon Bedrock for LLM integration via the built-in connector</p></li><li><p>IAM roles must include bedrock: InvokeModel permissions</p></li><li><p>ML nodes are recommended for production workloads</p></li><li><p>Agentic search with OpenSearch 3.3+ provides additional features</p></li></ul><h3>Alibaba Cloud OpenSearch</h3><p>Alibaba Cloud provides managed OpenSearch with ML capabilities:</p><ul><li><p>Use DashScope for LLM integration with Qwen models</p></li><li><p>RAM roles control access to ML services</p></li><li><p>Dedicated ML nodes available in enhanced editions</p></li><li><p>Consider using PAI-EAS for custom model deployment</p></li></ul><div><hr></div><p>Agents transform OpenSearch from a search engine into an intelligent system. They use LLMs to reason about problems, tools to take actions, and memory to maintain context.</p><p>Flow agents run tools in sequence for predictable workflows like RAG.</p><p>Conversational agents reason about which tools to use and adapt to different questions.</p><p>Plan-execute-reflect agents break down complex tasks, execute them step by step, and adapt based on results.</p><p>Agentic search converts natural language to DSL queries, enabling search without query expertise.</p><p>MCP extends agent capabilities with external tools and data sources.</p><p>Tomorrow we will explore analyzers and tokenizers to understand how OpenSearch processes text before indexing and searching.</p><div><hr></div><p>Interactive guide: <a href="https://opensearch.9cld.com/day/08-agentic-ai">https://opensearch.9cld.com/day/08-agentic-ai</a></p><p>All guides:  https://opensearch.9cld.com/</p>]]></content:encoded></item><item><title><![CDATA[Day 7: RAG with OpenSearch]]></title><description><![CDATA[Your chatbot confidently told a customer the wrong return policy. Again. RAG fixes that.]]></description><link>https://9cld.com/p/rag-with-opensearch</link><guid isPermaLink="false">https://9cld.com/p/rag-with-opensearch</guid><dc:creator><![CDATA[Ankit Mehta]]></dc:creator><pubDate>Tue, 27 Jan 2026 07:49:27 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Fzj6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb07a18f4-e798-47f6-9ab2-715bc24f07b1_1192x628.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Fzj6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb07a18f4-e798-47f6-9ab2-715bc24f07b1_1192x628.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Fzj6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb07a18f4-e798-47f6-9ab2-715bc24f07b1_1192x628.png 424w, https://substackcdn.com/image/fetch/$s_!Fzj6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb07a18f4-e798-47f6-9ab2-715bc24f07b1_1192x628.png 848w, https://substackcdn.com/image/fetch/$s_!Fzj6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb07a18f4-e798-47f6-9ab2-715bc24f07b1_1192x628.png 1272w, https://substackcdn.com/image/fetch/$s_!Fzj6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb07a18f4-e798-47f6-9ab2-715bc24f07b1_1192x628.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Fzj6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb07a18f4-e798-47f6-9ab2-715bc24f07b1_1192x628.png" width="1192" height="628" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b07a18f4-e798-47f6-9ab2-715bc24f07b1_1192x628.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:628,&quot;width&quot;:1192,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:421503,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://9cld.com/i/185935968?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb07a18f4-e798-47f6-9ab2-715bc24f07b1_1192x628.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Fzj6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb07a18f4-e798-47f6-9ab2-715bc24f07b1_1192x628.png 424w, https://substackcdn.com/image/fetch/$s_!Fzj6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb07a18f4-e798-47f6-9ab2-715bc24f07b1_1192x628.png 848w, https://substackcdn.com/image/fetch/$s_!Fzj6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb07a18f4-e798-47f6-9ab2-715bc24f07b1_1192x628.png 1272w, https://substackcdn.com/image/fetch/$s_!Fzj6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb07a18f4-e798-47f6-9ab2-715bc24f07b1_1192x628.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>The Problem Nobody Talks About</h2><p>You built a chatbot. It sounds smart. It responds instantly. Users love it.</p><p>Then someone asks about your company&#8217;s refund policy.</p><p>The LLM confidently invents one. Wrong dates. Wrong percentages. Completely made up.</p><p>You trained it on your data, right? No. You fine-tuned it? Too expensive. You hoped it would just know? That is what we all did.</p><p>The gap between &#8220;AI that sounds smart&#8221; and &#8220;AI that is actually correct&#8221; is where most GenAI projects fail.</p><p>RAG bridges that gap.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://9cld.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading 9Cloud! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p><div><hr></div><h2>What is RAG, Really?</h2><p>RAG stands for Retrieval-Augmented Generation. Fancy name. Simple idea.</p><p>Instead of asking the LLM to remember everything, you give it a search engine.</p><p>User asks a question. You search your knowledge base for relevant documents. You stuff those documents into the LLM prompt. The LLM generates an answer using your actual data.</p><p>The LLM does not hallucinate because it is not guessing. It is reading.</p><p>Think of it like this. You do not memorize every fact in your company handbook. You look things up when someone asks. RAG makes your AI do the same thing.</p><div><hr></div><h2>Why OpenSearch for RAG?</h2><p>You could use any vector database. Pinecone. Weaviate. Chroma. They all store embeddings and find similar vectors.</p><p>So why OpenSearch?</p><p>OpenSearch does more than vector search.</p><p>Most RAG tutorials show you the happy path. Embed query, find similar chunks, done. Real-world RAG is messier.</p><p>What if the user asks &#8220;What was our Q3 revenue?&#8221; A vector search finds documents about revenue, but you also need exact keyword matching to get Q3 and not Q2. </p><p>You need date filtering to get this year and not last year. </p><p>You need aggregations to sum the numbers, not just find them. </p><p>You need metadata filtering to restrict results to the finance department.</p><p>Vector-only databases cannot do this. You end up bolting on a second search system.</p><p>OpenSearch gives you hybrid search out of the box. Vectors plus keywords plus filters plus aggregations. One system.</p><p>OpenSearch also has a native RAG processor.</p><p>Most RAG implementations work like this. </p><ul><li><p>You search OpenSearch. </p></li><li><p>You pull results into your application. </p></li><li><p>You build the prompt in Python. </p></li><li><p>You send it to the LLM. </p></li><li><p>You return the response. </p></li></ul><p>Five steps, lots of code.</p><p>OpenSearch does all of this inside a search pipeline. </p><ul><li><p>Query hits OpenSearch. </p></li><li><p>RAG processor retrieves context, calls LLM, and returns grounded answer. </p></li></ul><p>One API call. Your application stays simple.</p><div><hr></div><h2>The Architecture</h2><p>Before we build, let us understand what we are building.</p><p>The flow starts with your application. User asks something like &#8220;What is our refund policy?&#8221; That query goes to OpenSearch.</p><p>Inside OpenSearch, there are two pipelines. The ingest pipeline handles documents when you add them. </p><p>It chunks long documents into smaller pieces, converts those chunks into vectors using an embedding model, and stores everything in the index.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!TcWs!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59d0c83c-86f1-477c-8dc6-bdef26082039_1598x1194.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!TcWs!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59d0c83c-86f1-477c-8dc6-bdef26082039_1598x1194.png 424w, https://substackcdn.com/image/fetch/$s_!TcWs!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59d0c83c-86f1-477c-8dc6-bdef26082039_1598x1194.png 848w, https://substackcdn.com/image/fetch/$s_!TcWs!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59d0c83c-86f1-477c-8dc6-bdef26082039_1598x1194.png 1272w, https://substackcdn.com/image/fetch/$s_!TcWs!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59d0c83c-86f1-477c-8dc6-bdef26082039_1598x1194.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!TcWs!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59d0c83c-86f1-477c-8dc6-bdef26082039_1598x1194.png" width="1456" height="1088" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/59d0c83c-86f1-477c-8dc6-bdef26082039_1598x1194.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1088,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:144196,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://9cld.com/i/185935968?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59d0c83c-86f1-477c-8dc6-bdef26082039_1598x1194.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!TcWs!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59d0c83c-86f1-477c-8dc6-bdef26082039_1598x1194.png 424w, https://substackcdn.com/image/fetch/$s_!TcWs!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59d0c83c-86f1-477c-8dc6-bdef26082039_1598x1194.png 848w, https://substackcdn.com/image/fetch/$s_!TcWs!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59d0c83c-86f1-477c-8dc6-bdef26082039_1598x1194.png 1272w, https://substackcdn.com/image/fetch/$s_!TcWs!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59d0c83c-86f1-477c-8dc6-bdef26082039_1598x1194.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The search pipeline handles queries. It takes the user&#8217;s question, converts it to a vector using the same embedding model, finds relevant chunks using hybrid search (both vector similarity and keyword matching), and then the RAG processor takes over. </p><p>It grabs the top chunks as context, builds a prompt, calls your LLM (Claude, GPT, DeepSeek, whatever you configured), and returns the grounded answer.</p><p>The key insight is that OpenSearch handles both retrieval and orchestration. The LLM only does generation.</p><div><hr></div><h2>The Components</h2><p>Let us break down each piece before we start building.</p><p><strong>Connectors</strong> are bridges between OpenSearch and external AI services. You need two of them. </p><p>An embedding connector that converts text to vectors, and an LLM connector that generates answers. </p><p>Think of connectors as API configurations. They store endpoints, credentials, and request templates.</p><p><strong>ML Models</strong> wrap connectors in a deployable unit. This sounds redundant, but it gives you versioning so you can roll back bad models, access control so you can restrict who uses what, and resource management for memory limits.</p><p><strong>Ingest Pipelines</strong> process documents before storing them. </p><p>For RAG, you need text chunking to split long documents into smaller pieces, and text embedding to convert those chunks into vectors. Why chunk? LLMs have context limits. </p><p>You cannot stuff a 50-page document into a prompt. Chunking creates bite-sized pieces that fit.</p><p><strong>Search Pipelines</strong> process queries and results. </p><p>For RAG, you need a neural query enricher to convert query text into a vector, and the RAG processor to retrieve context, call the LLM, and return the answer.</p><div><hr></div><h2>Building RAG: Step by Step</h2><p>Now we build. I will explain each step, then show the code.</p><h3>Step 1: Enable ML Commons</h3><p>OpenSearch&#8217;s ML features are disabled by default. You need to enable them.</p><pre><code><code>PUT _cluster/settings
{
  "persistent": {
    "plugins.ml_commons.only_run_on_ml_node": false,
    "plugins.ml_commons.memory_feature_enabled": true,
    "plugins.ml_commons.rag_pipeline_feature_enabled": true,
    "plugins.ml_commons.connector_access_control_enabled": true,
    "plugins.ml_commons.model_access_control_enabled": true
  }
}</code></code></pre><p>Why these settings? The <code>only_run_on_ml_node</code> setting lets you run models on any node, not just dedicated ML nodes. Good for development, not production. </p><p>The <code>memory_feature_enabled</code> setting enables conversation memory for multi-turn chat. </p><p>The <code>rag_pipeline_feature_enabled</code> setting enables the RAG processor in search pipelines. </p><p>The two access control settings restrict who can create connectors and deploy models. Security matters.</p><h3>Step 2: Create a Model Group</h3><p>Model groups control access to models. Think of them as folders with permissions.</p><pre><code><code>POST _plugins/_ml/model_groups/_register
{
  "name": "rag-model-group",
  "description": "Models for RAG pipeline",
  "access_mode": "public"
}</code></code></pre><p>This returns a <code>model_group_id</code>. Save it. You will need it for every model you register.</p><h3>Step 3: Register the Embedding Connector</h3><p>Now we connect to an embedding service. This example uses Amazon Bedrock with Titan Embeddings.</p><pre><code><code>POST _plugins/_ml/connectors/_create
{
  "name": "Amazon Bedrock Titan Embedding Connector",
  "description": "Connector for Titan Embeddings V2",
  "version": 1,
  "protocol": "aws_sigv4",
  "parameters": {
    "region": "us-east-1",
    "service_name": "bedrock",
    "model": "amazon.titan-embed-text-v2:0"
  },
  "credential": {
    "roleArn": "arn:aws:iam::YOUR_ACCOUNT:role/OpenSearchBedrockRole"
  },
  "actions": [
    {
      "action_type": "predict",
      "method": "POST",
      "url": "https://bedrock-runtime.us-east-1.amazonaws.com/model/amazon.titan-embed-text-v2:0/invoke",
      "headers": {
        "content-type": "application/json"
      },
      "request_body": "{ \"inputText\": \"${parameters.inputText}\", \"dimensions\": 1024, \"normalize\": true }",
      "pre_process_function": "connector.pre_process.bedrock.embedding",
      "post_process_function": "connector.post_process.bedrock.embedding"
    }
  ]
}</code></code></pre><p>What is happening here? </p><ul><li><p>The <code>protocol: aws_sigv4</code> uses AWS IAM for authentication. No API keys stored in your cluster. </p></li><li><p>The <code>parameters</code> section defines the AWS region and Bedrock model. </p></li><li><p>The <code>credential.roleArn</code> is the IAM role OpenSearch assumes to call Bedrock. </p></li><li><p>The <code>actions</code> section defines how to call the API. </p></li><li><p>The <code>request_body</code> template injects your text. </p></li><li><p>The pre and post process functions are built-in helpers that format requests and parse responses.</p></li></ul><p>Save the returned <code>connector_id</code>.</p><h3>Step 4: Register and Deploy the Embedding Model</h3><p>Wrap the connector in a model.</p><pre><code><code>POST _plugins/_ml/models/_register
{
  "name": "Titan Embedding Model",
  "function_name": "remote",
  "model_group_id": "YOUR_MODEL_GROUP_ID",
  "description": "Titan Text Embeddings V2 for RAG",
  "connector_id": "YOUR_EMBEDDING_CONNECTOR_ID"
}</code></code></pre><p>This returns a <code>model_id</code> and a <code>task_id</code>. The model registers asynchronously. Check the status with <code>GET _plugins/_ml/tasks/YOUR_TASK_ID</code>.</p><p>Once complete, deploy the model.</p><pre><code><code>POST _plugins/_ml/models/YOUR_EMBEDDING_MODEL_ID/_deploy</code></code></pre><p>Now your embedding model is ready.</p><h3>Step 5: Register the LLM Connector</h3><p>Same process for the LLM. This example uses Claude 3 Sonnet on Bedrock.</p><pre><code><code>POST _plugins/_ml/connectors/_create
{
  "name": "Amazon Bedrock Claude Connector",
  "description": "Connector for Claude 3 Sonnet",
  "version": 1,
  "protocol": "aws_sigv4",
  "parameters": {
    "region": "us-east-1",
    "service_name": "bedrock",
    "model": "anthropic.claude-3-sonnet-20240229-v1:0"
  },
  "credential": {
    "roleArn": "arn:aws:iam::YOUR_ACCOUNT:role/OpenSearchBedrockRole"
  },
  "actions": [
    {
      "action_type": "predict",
      "method": "POST",
      "url": "https://bedrock-runtime.us-east-1.amazonaws.com/model/anthropic.claude-3-sonnet-20240229-v1:0/invoke",
      "headers": {
        "content-type": "application/json"
      },
      "request_body": "{ \"anthropic_version\": \"bedrock-2023-05-31\", \"max_tokens\": 1024, \"messages\": [{\"role\": \"user\", \"content\": \"${parameters.prompt}\"}] }",
      "post_process_function": "\n  StringBuilder sb = new StringBuilder();\n  for (int i=0; i&lt;params.content.length(); i++) {\n    sb.append(params.content[i].text);\n  }\n  return sb.toString();\n  "
    }
  ]
}</code></code></pre><p>The <code>post_process_function</code> is Painless script that extracts the text from Claude&#8217;s response format.</p><h3>Step 6: Register and Deploy the LLM Model</h3><pre><code><code>POST _plugins/_ml/models/_register
{
  "name": "Claude 3 Sonnet Model",
  "function_name": "remote",
  "model_group_id": "YOUR_MODEL_GROUP_ID",
  "description": "Claude 3 Sonnet for RAG generation",
  "connector_id": "YOUR_LLM_CONNECTOR_ID"
}</code></code></pre><p>Wait for registration, then deploy.</p><pre><code><code>POST _plugins/_ml/models/YOUR_LLM_MODEL_ID/_deploy</code></code></pre><h3>Step 7: Create the Ingest Pipeline</h3><p>Now we create the pipeline that processes documents.</p><pre><code><code>PUT _ingest/pipeline/rag-ingest-pipeline
{
  "description": "Pipeline for RAG document processing",
  "processors": [
    {
      "text_chunking": {
        "algorithm": {
          "fixed_token_length": {
            "token_limit": 384,
            "overlap_rate": 0.2,
            "tokenizer": "standard"
          }
        },
        "field_map": {
          "content": "content_chunk"
        }
      }
    },
    {
      "text_embedding": {
        "model_id": "YOUR_EMBEDDING_MODEL_ID",
        "field_map": {
          "content_chunk": "content_embedding"
        }
      }
    }
  ]
}</code></code></pre><p>The text chunking processor splits documents. </p><p>Each chunk is at most 384 tokens, which is about 300 words. The 20% overlap prevents losing context at chunk boundaries. </p><p>The field_map takes the <code>content</code> field and outputs <code>content_chunk</code> as an array of chunks.</p><p>The text embedding processor takes each chunk, calls the embedding model, and stores vectors in <code>content_embedding</code>.</p><p>Why 384 tokens? It is a balance. Too small and you lose context. Too large and retrieval gets fuzzy. 256 to 512 is the sweet spot for most use cases.</p><h3>Step 8: Create the Knowledge Base Index</h3><p>Now create an index that stores your documents.</p><pre><code><code>PUT /knowledge-base
{
  "settings": {
    "index.knn": true,
    "default_pipeline": "rag-ingest-pipeline",
    "number_of_shards": 2,
    "number_of_replicas": 1
  },
  "mappings": {
    "properties": {
      "title": { "type": "text" },
      "content": { "type": "text" },
      "content_chunk": { "type": "text" },
      "content_embedding": {
        "type": "knn_vector",
        "dimension": 1024,
        "method": {
          "name": "hnsw",
          "space_type": "l2",
          "engine": "lucene",
          "parameters": {
            "ef_construction": 128,
            "m": 16
          }
        }
      },
      "source": { "type": "keyword" },
      "last_updated": { "type": "date" }
    }
  }
}</code></code></pre><p>The <code>index.knn: true</code> enables vector search. </p><p>The <code>default_pipeline</code> means every document goes through the ingest pipeline automatically. </p><p>The <code>dimension: 1024</code> must match your embedding model&#8217;s output. </p><p>The <code>method.name: hnsw</code> is the HNSW algorithm for fast approximate nearest neighbor search. </p><p>The <code>ef_construction</code> and <code>m</code> parameters tune the algorithm. Higher values mean better recall but slower indexing.</p><h3>Step 9: Create the Search Pipeline</h3><p>This is where RAG happens.</p><pre><code><code>PUT _search/pipeline/rag-search-pipeline
{
  "description": "RAG search pipeline",
  "request_processors": [
    {
      "neural_query_enricher": {
        "default_model_id": "YOUR_EMBEDDING_MODEL_ID",
        "neural_field_default_id": {
          "content_embedding": "YOUR_EMBEDDING_MODEL_ID"
        }
      }
    }
  ],
  "response_processors": [
    {
      "retrieval_augmented_generation": {
        "model_id": "YOUR_LLM_MODEL_ID",
        "context_field_list": ["content_chunk"],
        "system_prompt": "You are a helpful assistant that answers questions based on the provided context. If the context does not contain enough information to answer the question, say so. Do not make up information.",
        "user_instructions": "Answer the following question based on the context provided:"
      }
    }
  ]
}</code></code></pre><p>Request processors run before the search. The neural query enricher converts your text query into a vector automatically.</p><p>Response processors run after the search. </p><p>The retrieval_augmented_generation processor takes search results, builds a prompt, calls the LLM, and returns the answer. </p><p>The <code>context_field_list</code> specifies which fields to include as context. </p><p>The <code>system_prompt</code> is critical. It tells the LLM to stay grounded. Without it, the LLM might still hallucinate. </p><p>The <code>user_instructions</code> get prepended to the user&#8217;s question.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://9cld.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading 9Cloud! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h3>Step 10: Index Your Documents</h3><p>Now add your knowledge base.</p><pre><code><code>POST /knowledge-base/_doc
{
  "title": "Refund Policy",
  "content": "Our refund policy allows customers to return items within 30 days of purchase. Items must be unused and in original packaging. A valid receipt is required for all returns. Refunds are processed within 5-7 business days after we receive the returned item. Shipping costs are non-refundable unless the return is due to our error.",
  "source": "policies/refund.md",
  "last_updated": "2024-01-15"
}</code></code></pre><p>The ingest pipeline automatically chunks the content, embeds each chunk, and stores everything. Add more documents the same way.</p><h3>Step 11: Query with RAG</h3><p>Finally, ask a question.</p><pre><code><code>POST /knowledge-base/_search?search_pipeline=rag-search-pipeline
{
  "query": {
    "hybrid": {
      "queries": [
        {
          "match": {
            "content_chunk": {
              "query": "What is the refund policy?"
            }
          }
        },
        {
          "neural": {
            "content_embedding": {
              "query_text": "What is the refund policy?",
              "k": 5
            }
          }
        }
      ]
    }
  },
  "size": 3,
  "ext": {
    "generative_qa_parameters": {
      "llm_question": "What is the refund policy?"
    }
  }
}</code></code></pre><p>The hybrid search runs both keyword match and vector search. The neural query enricher already converted text to vector. </p><p>The size of 3 returns the top 3 chunks as context. </p><p>The generative_qa_parameters tells the RAG processor what question to answer.</p><p>The response includes standard search hits (the chunks that matched) and an <code>ext.retrieval_augmented_generation.answer</code> field with the LLM&#8217;s grounded response.</p><div><hr></div><h2>AWS vs Alibaba Cloud</h2><p>If you are deploying to production, here is what changes.</p><p>On AWS OpenSearch Service, you use Amazon Bedrock for both embeddings and LLM. Claude, Titan, Cohere, and DeepSeek-R1 are all available. </p><p>Authentication works via IAM roles, which is recommended, or access keys. Native integration with SageMaker gives you custom models. Expect to pay around $300 per month for 2x r6g.large nodes plus about $15 per million tokens for Claude.</p><p>On Alibaba Cloud OpenSearch, you use Model Studio (DashScope) for LLM access and PAI (Platform for AI) for custom embeddings. </p><p>Qwen models replace Claude. RAM roles replace IAM. The connector API structure is different. Expect around $250 per month for comparable nodes plus about $10 per million tokens for Qwen.</p><p>The OpenSearch APIs are identical. Only the connectors change.</p><div><hr></div><h2>Production Checklist</h2><p>Before you ship, there are things to verify.</p><p>For search quality, use hybrid search, not just vectors. Keywords matter for precision. Include metadata in context like source and date. This helps the LLM cite sources. </p><p>Test with adversarial questions like &#8220;What color is the CEO&#8217;s car?&#8221; The system should say &#8220;I do not know.&#8221; Monitor hallucination rate by tracking questions where the answer is not in the context.</p><p>For performance, add reranking for better precision. Tune chunk size based on your content. Shorter chunks work better for FAQs, longer for documentation. Consider caching frequent queries. Set LLM timeout appropriately, usually 30 to 60 seconds.</p><p>For security, use IAM roles rather than access keys. Enable model access control. Audit who creates connectors. Do not expose RAG endpoints publicly without authentication.</p><p>For cost, remember that embedding calls happen on every document ingest, and LLM calls happen on every query. Monitor token usage. Consider smaller models for simple Q&amp;A.</p><div><hr></div><h2>Common Mistakes</h2><p><strong>Vector-only retrieval.</strong> Vector search finds semantically similar content. But &#8220;Q3&#8221; and &#8220;Q2&#8221; are semantically similar. Keyword search catches exact matches. Use hybrid.</p><p><strong>No system prompt.</strong> Without instructions, the LLM will make things up. Always tell it to stay grounded and admit when it does not know.</p><p><strong>Too many chunks in context.</strong> More context is not always better. Irrelevant chunks confuse the LLM. Start with 3 to 5, tune from there.</p><p><strong>No metadata.</strong> If the LLM cannot cite sources, users do not trust the answer. Include the source and date in every document.</p><div><hr></div><h2>What is Next?</h2><p>You now have a complete RAG pipeline running in OpenSearch. Your chatbot reads your actual data instead of making things up.</p><p>In Day 8, we go deeper into AI agents. Systems that can plan, reason, and take actions autonomously using OpenSearch as their knowledge backbone.</p><p>The foundation is set. Time to build intelligence on top.</p><div><hr></div><p>Interactive Guide: <a href="https://opensearch.9cld.com/day/07-rag">https://opensearch.9cld.com/day/07-rag</a></p><p>Full Series: https://opensearch.9cld.com/</p><div class="captioned-button-wrap" data-attrs="{&quot;url&quot;:&quot;https://9cld.com/p/rag-with-opensearch?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="CaptionedButtonToDOM"><div class="preamble"><p class="cta-caption">Thanks for reading 9Cloud! This post is public so feel free to share it.</p></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://9cld.com/p/rag-with-opensearch?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://9cld.com/p/rag-with-opensearch?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p></div>]]></content:encoded></item><item><title><![CDATA[Talking to OpenSearch]]></title><description><![CDATA[Every search engine needs someone to talk to it. Today that someone is you.]]></description><link>https://9cld.com/p/talking-to-opensearch</link><guid isPermaLink="false">https://9cld.com/p/talking-to-opensearch</guid><dc:creator><![CDATA[Ankit Mehta]]></dc:creator><pubDate>Mon, 26 Jan 2026 09:33:12 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!YDu-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04411f9e-1720-4db8-bf3a-109129c1c03e_1198x628.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!YDu-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04411f9e-1720-4db8-bf3a-109129c1c03e_1198x628.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!YDu-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04411f9e-1720-4db8-bf3a-109129c1c03e_1198x628.png 424w, https://substackcdn.com/image/fetch/$s_!YDu-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04411f9e-1720-4db8-bf3a-109129c1c03e_1198x628.png 848w, https://substackcdn.com/image/fetch/$s_!YDu-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04411f9e-1720-4db8-bf3a-109129c1c03e_1198x628.png 1272w, https://substackcdn.com/image/fetch/$s_!YDu-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04411f9e-1720-4db8-bf3a-109129c1c03e_1198x628.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!YDu-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04411f9e-1720-4db8-bf3a-109129c1c03e_1198x628.png" width="1198" height="628" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/04411f9e-1720-4db8-bf3a-109129c1c03e_1198x628.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:628,&quot;width&quot;:1198,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:514717,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://9cld.com/i/185821423?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04411f9e-1720-4db8-bf3a-109129c1c03e_1198x628.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!YDu-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04411f9e-1720-4db8-bf3a-109129c1c03e_1198x628.png 424w, https://substackcdn.com/image/fetch/$s_!YDu-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04411f9e-1720-4db8-bf3a-109129c1c03e_1198x628.png 848w, https://substackcdn.com/image/fetch/$s_!YDu-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04411f9e-1720-4db8-bf3a-109129c1c03e_1198x628.png 1272w, https://substackcdn.com/image/fetch/$s_!YDu-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04411f9e-1720-4db8-bf3a-109129c1c03e_1198x628.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>The Conversation Nobody Teaches</h2><p>OpenSearch is sitting there. Running. Waiting. Green status. Ready.</p><p>But ready for what?</p><p>Most people get stuck here. They followed a tutorial to install OpenSearch. It worked. Now they stare at a terminal, wondering what to type next.</p><p>The documentation shows hundreds of API endpoints. The examples assume you already know what an index is. The queries look like alien hieroglyphics.</p><p>Here is what nobody tells you. OpenSearch is just a web server. It listens on port 9200. It accepts HTTP requests. It returns JSON responses.</p><p>That is it.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://9cld.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading 9Cloud! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p><p>Every fancy feature. Every complex query. Every advanced operation. It's all just HTTP requests to a web server.</p><p>Once you understand this, everything else clicks into place.</p><div><hr></div><h2>The Shape of Every Request</h2><pre><code><code>curl -X METHOD 'https://localhost:9200/path' -d 'data'</code></code></pre><p>METHOD is what you want to do. GET to read. PUT to create. POST to add or update. DELETE to remove.</p><p>Path is where you want to do it. An index name. A document ID. A special endpoint like _search or _cluster.</p><p>Data is the details. The document you want to store. The query you want to run. The settings you want to apply.</p><p>Every single OpenSearch operation follows this pattern. Learn it once. Use it forever.</p><div><hr></div><h2>Before We Start</h2><p>Make sure OpenSearch is running.</p><pre><code><code>podman ps | grep opensearch</code></code></pre><p>If you see your container listed, you are good. If not, start it.</p><pre><code><code>podman run -d \
  --name opensearch-node \
  -p 9200:9200 \
  -p 9600:9600 \
  -e 'discovery.type=single-node' \
  -e 'OPENSEARCH_INITIAL_ADMIN_PASSWORD=OpenSearch@2024Secure' \
  -e 'OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m' \
  opensearchproject/opensearch:latest</code></code></pre><p>Wait a minute for startup. Then verify.</p><pre><code><code>curl -k -u 'admin:OpenSearch@2024Secure' 'https://localhost:9200'</code></code></pre><p>You should see JSON with cluster information.</p><p>To save typing, create an alias.</p><pre><code><code>alias os='curl -s -k -u admin:OpenSearch@2024Secure'</code></code></pre><p>Now <code>os</code> replaces the long curl command. I will use this alias for the rest of the guide.</p><div><hr></div><h2>Checking Health</h2><p>First question to ask any cluster. Are you okay?</p><pre><code><code>os -X GET 'https://localhost:9200/_cluster/health?pretty'</code></code></pre><p>The underscore before cluster means this is a special system endpoint. The pretty parameter formats the JSON for humans.</p><p>Look at the status field in the response.</p><p>Green means everything is perfect. All data is available and replicated.</p><p>Yellow means data is available but not fully replicated. Normal for single node setups.</p><p>Red means some data is unavailable. Something is wrong.</p><p>For learning on a single node, yellow is expected and fine.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;49d8716e-7166-4790-a0df-ab530a9ae2d8&quot;,&quot;duration&quot;:null}"></div><p></p><div><hr></div><h2>Creating a Place for Data</h2><p>OpenSearch stores data in indices. Think of an index as a container for related documents.</p><p>Before adding data, create an index.</p><pre><code><code>os -X PUT 'https://localhost:9200/products' -H 'Content-Type: application/json' -d '{
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 0
  }
}'</code></code></pre><p>PUT because we are creating something with a specific name. The name is products. Settings configure how the index behaves.</p><p>One shard means all data lives in one place. Zero replicas means no backup copies. Fine for learning. Not for production.</p><p>The response confirms creation.</p><pre><code><code>{
  "acknowledged": true,
  "index": "products"
}</code></code></pre><div><hr></div><h2>Defining Structure</h2><p>OpenSearch can guess the structure of your data. But guessing leads to surprises. Better to define it explicitly.</p><p>Delete the index and recreate it with a mapping.</p><pre><code><code>os -X DELETE 'https://localhost:9200/products'

os -X PUT 'https://localhost:9200/products' -H 'Content-Type: application/json' -d '{
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 0
  },
  "mappings": {
    "properties": {
      "name": { "type": "text" },
      "category": { "type": "keyword" },
      "price": { "type": "float" },
      "description": { "type": "text" },
      "in_stock": { "type": "boolean" }
    }
  }
}'</code></code></pre><p>The mappings section defines what each field contains.</p><p><strong>text</strong> fields are for full text search. OpenSearch breaks them into individual words. Searching for &#8220;wireless&#8221; finds &#8220;Wireless Headphones&#8221; even though the case differs and the word is part of a phrase.</p><p><strong>keyword</strong> fields are for exact values. Categories. Tags. IDs. Status codes. OpenSearch stores them exactly as provided. Searching for &#8220;Electronics&#8221; only finds &#8220;Electronics&#8221; not &#8220;Electronic&#8221; or &#8220;electronics&#8221;.</p><p><strong>float</strong> for decimal numbers. <strong>boolean</strong> for true or false. <strong>integer</strong> for whole numbers. <strong>date</strong> for timestamps.</p><p>Choosing wrong causes problems. A category stored as text matches partial words. A description stored as keyword requires exact phrase matching. Think about how you will search each field.</p><div><hr></div><h2>Adding Documents</h2><p>Now add some data.</p><pre><code><code>os -X POST 'https://localhost:9200/products/_doc/1' -H 'Content-Type: application/json' -d '{
  "name": "Wireless Headphones",
  "category": "Electronics",
  "price": 149.99,
  "description": "Premium noise-canceling wireless headphones with 30-hour battery life",
  "in_stock": true
}'</code></code></pre><p>POST to the _doc endpoint adds a document. The 1 at the end is the document ID. You choose it. If you leave it out, OpenSearch generates a random one.</p><p>Add a few more.</p><pre><code><code>os -X POST 'https://localhost:9200/products/_doc/2' -H 'Content-Type: application/json' -d '{
  "name": "Running Shoes",
  "category": "Sports",
  "price": 89.99,
  "description": "Lightweight running shoes for marathon training",
  "in_stock": true
}'

os -X POST 'https://localhost:9200/products/_doc/3' -H 'Content-Type: application/json' -d '{
  "name": "Coffee Maker",
  "category": "Kitchen",
  "price": 79.99,
  "description": "Automatic drip coffee maker with programmable timer",
  "in_stock": false
}'

os -X POST 'https://localhost:9200/products/_doc/4' -H 'Content-Type: application/json' -d '{
  "name": "Bluetooth Speaker",
  "category": "Electronics",
  "price": 59.99,
  "description": "Portable wireless speaker with deep bass",
  "in_stock": true
}'</code></code></pre><p>Four products. Two electronics. One sports. One kitchen. Different prices. Different stock status. Enough variety to make searches interesting.</p><div><hr></div><h2>Getting Documents Back</h2><p>Retrieve a document by its ID.</p><pre><code><code>os -X GET 'https://localhost:9200/products/_doc/1?pretty'</code></code></pre><p>The response includes your document in the _source field plus metadata like version number and index name.</p><pre><code><code>{
  "_index": "products",
  "_id": "1",
  "_source": {
    "name": "Wireless Headphones",
    "category": "Electronics",
    "price": 149.99,
    "description": "Premium noise-canceling wireless headphones with 30-hour battery life",
    "in_stock": true
  }
}</code></code></pre><p>This is direct retrieval. You know the ID. You get the document. Fast and simple.</p><p>But searching is different.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://9cld.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading 9Cloud! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h2>Searching</h2><p>Search does not require knowing IDs. You describe what you want. OpenSearch finds matches.</p><p>The simplest search returns everything.</p><pre><code><code>os -X GET 'https://localhost:9200/products/_search?pretty' -H 'Content-Type: application/json' -d '{
  "query": {
    "match_all": {}
  }
}'</code></code></pre><p>The _search endpoint accepts a query in the request body. The match_all query matches every document.</p><p>Results come back in the hits array. Each hit includes the document source and a score indicating relevance.</p><pre><code><code>{
  "hits": {
    "total": { "value": 4 },
    "hits": [
      { "_id": "1", "_score": 1.0, "_source": { ... } },
      { "_id": "2", "_score": 1.0, "_source": { ... } }
    ]
  }
}</code></code></pre><p>With match_all, every document scores the same because no criteria distinguish them.</p><div><hr></div><h2>Full Text Search</h2><p>This is why OpenSearch exists. Finding documents by meaning, not exact strings.</p><pre><code><code>os -X GET 'https://localhost:9200/products/_search?pretty' -H 'Content-Type: application/json' -d '{
  "query": {
    "match": {
      "description": "wireless"
    }
  }
}'</code></code></pre><p>The match query searches a text field for a word. This returns two products. The headphones because &#8220;wireless&#8221; appears in their description. The speaker because &#8220;wireless&#8221; also appears in its description.</p><p>Try searching for something not present.</p><pre><code><code>os -X GET 'https://localhost:9200/products/_search?pretty' -H 'Content-Type: application/json' -d '{
  "query": {
    "match": {
      "description": "waterproof"
    }
  }
}'</code></code></pre><p>No results. None of our products mention waterproof.</p><p>Search for multiple words.</p><pre><code><code>os -X GET 'https://localhost:9200/products/_search?pretty' -H 'Content-Type: application/json' -d '{
  "query": {
    "match": {
      "description": "wireless battery"
    }
  }
}'</code></code></pre><p>By default, match finds documents containing any of the words. Both wireless products match. The headphones score higher because their description contains both words.</p><div><hr></div><h2>Exact Matching</h2><p>For keyword fields, use term instead of match.</p><pre><code><code>os -X GET 'https://localhost:9200/products/_search?pretty' -H 'Content-Type: application/json' -d '{
  "query": {
    "term": {
      "category": "Electronics"
    }
  }
}'</code></code></pre><p>Term looks for the exact value. Electronics matches Electronics. Nothing else.</p><p>This distinction matters. If you use match on a keyword field, it usually works but behaves unexpectedly with multiple words. If you use term on a text field, it fails because the stored tokens do not match your exact input.</p><p>Match for text. Term for keywords. Remember this.</p><div><hr></div><h2>Filtering by Numbers</h2><p>Find products in a price range.</p><pre><code><code>os -X GET 'https://localhost:9200/products/_search?pretty' -H 'Content-Type: application/json' -d '{
  "query": {
    "range": {
      "price": {
        "gte": 50,
        "lte": 100
      }
    }
  }
}'</code></code></pre><p>The range query accepts boundaries. gte means greater than or equal. lte means less than or equal. gt and lt are the strict versions without the equal part.</p><p>This finds running shoes and coffee maker. Both priced between 50 and 100.</p><div><hr></div><h2>Combining Conditions</h2><p>Real searches have multiple requirements. Find electronics that are in stock. Find products matching a search term under a certain price.</p><p>The bool query combines conditions.</p><pre><code><code>os -X GET 'https://localhost:9200/products/_search?pretty' -H 'Content-Type: application/json' -d '{
  "query": {
    "bool": {
      "must": [
        { "term": { "category": "Electronics" } }
      ],
      "filter": [
        { "term": { "in_stock": true } }
      ]
    }
  }
}'</code></code></pre><p>The must clause requires conditions to match and affects relevance scoring.</p><p>The filter clause requires conditions to match but does not affect scoring. Use filter for yes or no conditions like stock status or category membership where relevance does not matter.</p><p>A more complex example. Find products mentioning wireless that are in stock and cost less than 100.</p><pre><code><code>os -X GET 'https://localhost:9200/products/_search?pretty' -H 'Content-Type: application/json' -d '{
  "query": {
    "bool": {
      "must": [
        { "match": { "description": "wireless" } }
      ],
      "filter": [
        { "term": { "in_stock": true } },
        { "range": { "price": { "lt": 100 } } }
      ]
    }
  }
}'</code></code></pre><p>Only the Bluetooth speaker matches. The headphones are wireless and in stock but cost more than 100.</p><div><hr></div><h2>Modifying Documents</h2><p>Update a document with new information.</p><pre><code><code>os -X POST 'https://localhost:9200/products/_update/1' -H 'Content-Type: application/json' -d '{
  "doc": {
    "price": 129.99,
    "on_sale": true
  }
}'</code></code></pre><p>The _update endpoint merges your changes with the existing document. Fields you specify are updated or added. Fields you omit remain unchanged.</p><p>Verify the update worked.</p><pre><code><code>os -X GET 'https://localhost:9200/products/_doc/1?pretty'</code></code></pre><p>The headphones now cost 129.99 and have a new on_sale field.</p><div><hr></div><h2>Removing Documents</h2><p>Delete by ID.</p><pre><code><code>os -X DELETE 'https://localhost:9200/products/_doc/4'</code></code></pre><p>The speaker is gone. Permanently. No recycle bin. No undo.</p><p>Verify the count dropped.</p><pre><code><code>os -X GET 'https://localhost:9200/products/_count?pretty'</code></code></pre><p>Three documents remain.</p><div><hr></div><h2>Viewing Indices</h2><p>List all indices in the cluster.</p><pre><code><code>os -X GET 'https://localhost:9200/_cat/indices?v'</code></code></pre><p>The _cat API returns plain text formatted for humans. The v parameter adds column headers.</p><p>You see your products index along with any system indices OpenSearch created automatically.</p><div><hr></div><h2>Cleaning Up</h2><p>Delete an entire index.</p><pre><code><code>os -X DELETE 'https://localhost:9200/products'</code></code></pre><p>Everything gone. All documents. All mappings. All settings. Use carefully.</p><div><hr></div><h2>The Pattern Revealed</h2><p>Every operation we performed followed the same pattern.</p><pre><code>Check health. GET to _cluster/health.</code></pre><pre><code>Create an index. PUT to /indexname.</code></pre><pre><code>Add document. POST to /indexname/_doc/id.</code></pre><pre><code>Get the document. GET to /indexname/_doc/id.</code></pre><pre><code>Search. GET or POST to /indexname/_search with query body.</code></pre><pre><code>Update. POST to /indexname/_update/id.</code></pre><pre><code>Delete document. DELETE to /indexname/_doc/id.</code></pre><pre><code>Delete index. DELETE to /indexname.</code></pre><p>The endpoint tells OpenSearch what you want. The method tells it what to do. The body provides details.</p><p>This pattern scales to every feature OpenSearch offers. Aggregations for analytics. Vector search for AI applications. Semantic search for natural language. All of them use the same structure.</p><p>You now speak OpenSearch.</p><div><hr></div><p>Terminal recording at https://asciinema.org/a/772970</p><p>Interactive guide at <a href="https://opensearch.9cld.com/day/03-first-conversation">https://opensearch.9cld.com/day/03-first-conversation</a></p><p></p>]]></content:encoded></item><item><title><![CDATA[Running OpenSearch with Podman]]></title><description><![CDATA[Docker without the daemon. Same commands. Better security.]]></description><link>https://9cld.com/p/running-opensearch-with-podman</link><guid isPermaLink="false">https://9cld.com/p/running-opensearch-with-podman</guid><dc:creator><![CDATA[Ankit Mehta]]></dc:creator><pubDate>Sun, 25 Jan 2026 10:20:26 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!2X01!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39067e43-9738-4b2f-804e-50825b7fd9ce_1194x628.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2X01!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39067e43-9738-4b2f-804e-50825b7fd9ce_1194x628.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2X01!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39067e43-9738-4b2f-804e-50825b7fd9ce_1194x628.png 424w, https://substackcdn.com/image/fetch/$s_!2X01!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39067e43-9738-4b2f-804e-50825b7fd9ce_1194x628.png 848w, https://substackcdn.com/image/fetch/$s_!2X01!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39067e43-9738-4b2f-804e-50825b7fd9ce_1194x628.png 1272w, https://substackcdn.com/image/fetch/$s_!2X01!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39067e43-9738-4b2f-804e-50825b7fd9ce_1194x628.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2X01!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39067e43-9738-4b2f-804e-50825b7fd9ce_1194x628.png" width="1194" height="628" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/39067e43-9738-4b2f-804e-50825b7fd9ce_1194x628.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:628,&quot;width&quot;:1194,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:536233,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://9cld.com/i/185712949?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39067e43-9738-4b2f-804e-50825b7fd9ce_1194x628.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!2X01!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39067e43-9738-4b2f-804e-50825b7fd9ce_1194x628.png 424w, https://substackcdn.com/image/fetch/$s_!2X01!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39067e43-9738-4b2f-804e-50825b7fd9ce_1194x628.png 848w, https://substackcdn.com/image/fetch/$s_!2X01!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39067e43-9738-4b2f-804e-50825b7fd9ce_1194x628.png 1272w, https://substackcdn.com/image/fetch/$s_!2X01!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F39067e43-9738-4b2f-804e-50825b7fd9ce_1194x628.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Why This Guide Exists</h2><p>Most OpenSearch tutorials assume Docker. They give you commands that work. But what if you are on RHEL, Fedora, or CentOS? What if your organization banned Docker? What if you simply prefer rootless containers?</p><p>Podman is your answer.</p><p>This guide covers everything you need to run OpenSearch with Podman. Not just the commands. The reasoning behind them. The gotchas that will waste your afternoon. The password rules that reject seemingly strong passwords. The bash escaping that breaks your terminal.</p><p>All of it. From first principles.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://9cld.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading 9Cloud! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h2>What is Podman</h2><p>Podman is a container engine developed by Red Hat. It runs containers the same way Docker does but without a central daemon process.</p><p>Every Docker command works with Podman. Replace <code>docker</code> with <code>podman</code> and everything runs the same way.</p><p>The differences that matter for OpenSearch.</p><p><strong>No daemon means no single point of failure.</strong> Docker requires a background service running at all times. If dockerd crashes, all containers stop. Podman containers are regular processes. If one crashes, others keep running.</p><p><strong>Rootless by default.</strong> Docker traditionally required root privileges. Podman runs containers as your regular user. This is better for security and better for shared servers where you do not have sudo access.</p><p><strong>Systemd integration.</strong> Podman generates systemd unit files from containers. You can manage OpenSearch as a proper system service with start, stop, enable, and disable commands.</p><p><strong>Drop in replacement.</strong> The podman command accepts the same flags as docker. You do not need to learn new syntax.</p><div><hr></div><h2>Prerequisites</h2><p>Before we start, verify Podman is installed.</p><pre><code><code>podman --version</code></code></pre><p>You should see output like <code>podman version 4.9.4</code> or similar. If not, install it.</p><p>For Fedora and RHEL</p><pre><code><code>sudo dnf install podman</code></code></pre><p>For Ubuntu and Debian</p><pre><code><code>sudo apt install podman</code></code></pre><p>For macOS</p><pre><code><code>brew install podman
podman machine init
podman machine start</code></code></pre><div><hr></div><h2>Starting OpenSearch</h2><p>Here is the command that works.</p><pre><code><code>podman run -d \
  --name opensearch-node \
  -p 9200:9200 \
  -p 9600:9600 \
  -e 'discovery.type=single-node' \
  -e 'OPENSEARCH_INITIAL_ADMIN_PASSWORD=OpenSearch@2024Secure' \
  -e 'OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m' \
  opensearchproject/opensearch:latest</code></code></pre><p>Let me explain every part.</p><p><strong>podman run</strong> creates and starts a new container.</p><p><strong>-d</strong> runs it in detached mode. The container runs in the background and returns control to your terminal.</p><p><strong>--name opensearch-node</strong> gives the container a name you can remember. Without this, Podman generates random names like <code>quirky_einstein</code> which are fun but not helpful when you have multiple containers.</p><p><strong>-p 9200:9200</strong> maps port 9200 from the container to your host machine. This is the REST API port. All your queries go through here.</p><p><strong>-p 9600:9600</strong> maps the performance analyzer port. Optional for learning but useful for monitoring.</p><p><strong>-e &#8216;discovery.type=single-node&#8217;</strong> tells OpenSearch to run as a single node cluster. Without this, OpenSearch waits forever looking for other nodes to form a cluster and never fully starts.</p><p><strong>-e &#8216;OPENSEARCH_INITIAL_ADMIN_PASSWORD=...&#8217;</strong> sets the admin password. This is mandatory since OpenSearch 2.12. The password must meet strict requirements which I will explain shortly.</p><p><strong>-e &#8216;OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m&#8217;</strong> controls JVM heap size. 512 megabytes is enough for learning. Production needs more.</p><p><strong>opensearchproject/opensearch:latest</strong> specifies the container image. The latest tag gives you the newest version.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;669265d3-f6f7-4ba7-8d74-d7cc97748ba3&quot;,&quot;duration&quot;:null}"></div><p>Live Link: https://asciinema.org/a/772348</p><div><hr></div><h2>The Password Problem</h2><p>OpenSearch 3.x introduced strict password validation. This is where tutorials written for older versions fail you.</p><p>Your password must have all of these.</p><p>At least 8 characters.</p><p>At least one uppercase letter.</p><p>At least one lowercase letter.</p><p>At least one digit.</p><p>At least one special character.</p><p>Passes the zxcvbn strength algorithm.</p><p>That last requirement trips up most people. The zxcvbn algorithm detects common patterns and rejects passwords that look strong but are actually predictable.</p><p>These passwords fail validation.</p><p><code>Admin123!</code> fails because Admin is a common word and 123 is a sequential pattern.</p><p><code>Password1@</code> fails because Password is literally a dictionary word.</p><p><code>Secure#2024</code> fails because secure is a dictionary word.</p><p><code>MyStr0ng!Pass</code> fails because it uses predictable letter to number substitutions.</p><p>These passwords work.</p><p><code>OpenSearch@2024Secure</code> works because the combination is random enough.</p><p><code>BlueTiger#Lamp42</code> works because random word combinations are strong.</p><p><code>Kx9$mPq2#vL8nR</code> works because it is truly random.</p><div><hr></div><h2>The Bash Escaping Problem</h2><p>Even with a valid password, bash can break your command. The exclamation mark is the culprit.</p><p>Try this command.</p><pre><code><code>podman run -e "OPENSEARCH_INITIAL_ADMIN_PASSWORD=MyStr0ng!Pass"</code></code></pre><p>Bash returns <code>event not found</code> and refuses to run anything.</p><p>Why? Bash interprets <code>!</code> as history expansion. When you type <code>!Pass</code>, bash tries to find a previous command starting with Pass and substitute it.</p><p>The fix is simple. Use single quotes instead of double quotes.</p><pre><code><code>podman run -e 'OPENSEARCH_INITIAL_ADMIN_PASSWORD=OpenSearch@2024Secure'</code></code></pre><p>Single quotes tell bash to treat everything inside literally. No expansion. No interpretation. Just the text.</p><p>This applies to any password containing these characters.</p><p><code>!</code> triggers history expansion.</p><p><code>$</code> triggers variable expansion.</p><p><code>`</code> triggers command substitution.</p><p><code>#</code> starts a comment if at certain positions.</p><p>Always use single quotes for passwords in shell commands.</p><div><hr></div><h2>Waiting for Startup</h2><p>After running the podman command, OpenSearch needs time to initialize. This is not instant.</p><p>OpenSearch 3.x takes 60 to 90 seconds to fully start. The security plugin configures itself. Internal indices are created. The cluster state stabilizes.</p><p>Wait for it.</p><pre><code><code>sleep 60</code></code></pre><p>Then check container status.</p><pre><code><code>podman ps</code></code></pre><p>You should see output showing your container is up and running with ports mapped.</p><pre><code><code>CONTAINER ID  IMAGE                                          STATUS        PORTS
85c47eeb262f  opensearchproject/opensearch:latest            Up 2 minutes  0.0.0.0:9200-&gt;9200/tcp</code></code></pre><p>If the STATUS column shows <code>Exited</code> instead of <code>Up</code>, something went wrong. Check the logs.</p><pre><code><code>podman logs opensearch-node</code></code></pre><div><hr></div><h2>Testing the Connection</h2><p>Now verify OpenSearch responds to requests.</p><pre><code><code>curl -k -u 'admin:OpenSearch@2024Secure' https://localhost:9200</code></code></pre><p>The flags explained.</p><p><strong>-k</strong> tells curl to skip SSL certificate verification. OpenSearch uses self signed demo certificates. Curl would reject them without this flag.</p><p><strong>-u &#8216;admin:password&#8217;</strong> provides HTTP Basic authentication credentials.</p><p>https://localhost:9200</p><p> is the endpoint. Note HTTPS not HTTP. OpenSearch enables SSL by default.</p><p>Expected response.</p><pre><code><code>{
  "name" : "opensearch-node",
  "cluster_name" : "docker-cluster",
  "cluster_uuid" : "abc123...",
  "version" : {
    "distribution" : "opensearch",
    "number" : "3.0.0"
  },
  "tagline" : "The OpenSearch Project: https://opensearch.org/"
}</code></code></pre><p>Check cluster health.</p><pre><code><code>curl -k -u 'admin:OpenSearch@2024Secure' https://localhost:9200/_cluster/health?pretty</code></code></pre><p>Look for the status field. Green means healthy. Yellow means functional but replicas are unassigned. Red means problems.</p><p>Yellow is normal for single node setups. Replicas cannot be placed on the same node as primaries so they remain unassigned. This is expected behavior not an error.</p><div><hr></div><h2>Troubleshooting</h2><p>Things will go wrong. Here is how to fix the common issues.</p><p><strong>SSL_ERROR_SYSCALL when connecting</strong></p><p>This error means OpenSearch is not ready yet or crashed during startup.</p><pre><code><code>curl: (35) OpenSSL SSL_connect: SSL_ERROR_SYSCALL in connection to localhost:9200</code></code></pre><p>Check if the container is running.</p><pre><code><code>podman ps -a</code></code></pre><p>If status shows Exited, check the logs.</p><pre><code><code>podman logs opensearch-node</code></code></pre><p>If status shows Up, wait longer. OpenSearch 3.x can take 90 seconds on slower machines.</p><pre><code><code>sleep 30
curl -k -u 'admin:OpenSearch@2024Secure' https://localhost:9200</code></code></pre><p><strong>Container exits immediately</strong></p><p>The container starts and stops within seconds. Check logs for the reason.</p><pre><code><code>podman logs opensearch-node</code></code></pre><p>Common causes.</p><p>Weak password. Look for &#8220;Weak password&#8221; in the logs. Choose a stronger password following the rules above.</p><p>Not enough memory. OpenSearch needs at least 2GB free RAM. Check with <code>free -h</code> and close other applications.</p><p>Port already in use. Another process is using port 9200. Check with <code>ss -tlnp | grep 9200</code> and stop the conflicting service.</p><p><strong>Permission denied errors</strong></p><p>Podman runs rootless by default. Some systems need adjustments.</p><pre><code><code>podman unshare chown -R 1000:1000 /path/to/data</code></code></pre><p>Or run with the user namespace disabled for testing.</p><pre><code><code>podman run --userns=keep-id ...</code></code></pre><div><hr></div><h2>Adding Dashboards</h2><p>OpenSearch Dashboards provides a web interface for visualization and management. Running it alongside OpenSearch requires a shared network.</p><p>Create a network.</p><pre><code><code>podman network create opensearch-net</code></code></pre><p>Start OpenSearch on this network.</p><pre><code><code>podman run -d \
  --name opensearch-node \
  --network opensearch-net \
  -p 9200:9200 \
  -p 9600:9600 \
  -e 'discovery.type=single-node' \
  -e 'OPENSEARCH_INITIAL_ADMIN_PASSWORD=OpenSearch@2024Secure' \
  -e 'OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m' \
  opensearchproject/opensearch:latest</code></code></pre><p>Wait for OpenSearch to start.</p><pre><code><code>sleep 60</code></code></pre><p>Start Dashboards on the same network.</p><pre><code><code>podman run -d \
  --name opensearch-dashboards \
  --network opensearch-net \
  -p 5601:5601 \
  -e 'OPENSEARCH_HOSTS=["https://opensearch-node:9200"]' \
  opensearchproject/opensearch-dashboards:latest</code></code></pre><p>Wait for Dashboards to start.</p><pre><code><code>sleep 30</code></code></pre><p>Open http://localhost:5601 in your browser. Login with admin and your password.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://9cld.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading 9Cloud! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h2>Using Podman Compose</h2><p>For repeatable setups, use Podman Compose. It reads the same YAML files as Docker Compose.</p><p>Install it.</p><pre><code><code>pip install podman-compose</code></code></pre><p>Create a directory for your project.</p><pre><code><code>mkdir ~/opensearch-tutorial
cd ~/opensearch-tutorial</code></code></pre><p>Create a file named <code>podman-compose.yml</code> with this content.</p><pre><code><code>version: '3'
services:
  opensearch-node:
    image: opensearchproject/opensearch:latest
    container_name: opensearch-node
    environment:
      - discovery.type=single-node
      - OPENSEARCH_INITIAL_ADMIN_PASSWORD=OpenSearch@2024Secure
      - OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m
      - bootstrap.memory_lock=true
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 65536
        hard: 65536
    ports:
      - 9200:9200
      - 9600:9600
    networks:
      - opensearch-net

  opensearch-dashboards:
    image: opensearchproject/opensearch-dashboards:latest
    container_name: opensearch-dashboards
    environment:
      - OPENSEARCH_HOSTS=["https://opensearch-node:9200"]
    ports:
      - 5601:5601
    networks:
      - opensearch-net
    depends_on:
      - opensearch-node

networks:
  opensearch-net:</code></code></pre><p>Start everything.</p><pre><code><code>podman-compose up -d</code></code></pre><p>Check status.</p><pre><code><code>podman-compose ps</code></code></pre><p>Stop everything.</p><pre><code><code>podman-compose down</code></code></pre><div><hr></div><h2>Performance Tuning</h2><p>The defaults work for learning. Production needs tuning.</p><p><strong>Heap size</strong> controls how much memory the JVM uses. Set it to 50% of available RAM but never more than 32GB. Always set minimum and maximum to the same value.</p><p>For 8GB RAM machine use 4GB heap.</p><pre><code><code>-e 'OPENSEARCH_JAVA_OPTS=-Xms4g -Xmx4g'</code></code></pre><p>For 16GB RAM machine use 8GB heap.</p><pre><code><code>-e 'OPENSEARCH_JAVA_OPTS=-Xms8g -Xmx8g'</code></code></pre><p><strong>Memory lock</strong> prevents swapping. Swapping destroys search performance. Enable it.</p><pre><code><code>-e 'bootstrap.memory_lock=true' \
--ulimit memlock=-1:-1</code></code></pre><p><strong>File descriptors</strong> limit how many files OpenSearch can open. The default is too low.</p><pre><code><code>--ulimit nofile=65536:65536</code></code></pre><p>Complete production ready command.</p><pre><code><code>podman run -d \
  --name opensearch-node \
  -p 9200:9200 \
  -p 9600:9600 \
  -e 'discovery.type=single-node' \
  -e 'OPENSEARCH_INITIAL_ADMIN_PASSWORD=OpenSearch@2024Secure' \
  -e 'OPENSEARCH_JAVA_OPTS=-Xms4g -Xmx4g' \
  -e 'bootstrap.memory_lock=true' \
  --ulimit memlock=-1:-1 \
  --ulimit nofile=65536:65536 \
  opensearchproject/opensearch:latest</code></code></pre><div><hr></div><h2>Cleanup</h2><p>When you are done experimenting.</p><p>Stop containers.</p><pre><code><code>podman stop opensearch-node opensearch-dashboards</code></code></pre><p>Remove containers.</p><pre><code><code>podman rm opensearch-node opensearch-dashboards</code></code></pre><p>Or with compose.</p><pre><code><code>podman-compose down</code></code></pre><p>Remove unused images to free disk space.</p><pre><code><code>podman image prune</code></code></pre><p>Remove unused volumes.</p><pre><code><code>podman volume prune</code></code></pre><div><hr></div><p>Interactive guide available at https://opensearch.9cld.com</p><p></p>]]></content:encoded></item><item><title><![CDATA[Fix Alibaba Cloud ECS Boot Problems Step by Step]]></title><description><![CDATA[Easy steps to repair the system disk and boot your server again]]></description><link>https://9cld.com/p/your-alibaba-cloud-ecs-wont-boot</link><guid isPermaLink="false">https://9cld.com/p/your-alibaba-cloud-ecs-wont-boot</guid><dc:creator><![CDATA[Bhavesh Panchal]]></dc:creator><pubDate>Fri, 23 Jan 2026 08:20:42 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!s-5t!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cdad2c3-91b8-4712-b1d6-f06d0ff67398_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!s-5t!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cdad2c3-91b8-4712-b1d6-f06d0ff67398_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!s-5t!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cdad2c3-91b8-4712-b1d6-f06d0ff67398_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!s-5t!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cdad2c3-91b8-4712-b1d6-f06d0ff67398_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!s-5t!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cdad2c3-91b8-4712-b1d6-f06d0ff67398_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!s-5t!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cdad2c3-91b8-4712-b1d6-f06d0ff67398_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!s-5t!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cdad2c3-91b8-4712-b1d6-f06d0ff67398_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1cdad2c3-91b8-4712-b1d6-f06d0ff67398_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1708684,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://9cld.com/i/185264452?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cdad2c3-91b8-4712-b1d6-f06d0ff67398_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!s-5t!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cdad2c3-91b8-4712-b1d6-f06d0ff67398_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!s-5t!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cdad2c3-91b8-4712-b1d6-f06d0ff67398_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!s-5t!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cdad2c3-91b8-4712-b1d6-f06d0ff67398_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!s-5t!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cdad2c3-91b8-4712-b1d6-f06d0ff67398_1536x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>When an Alibaba Cloud ECS instance fails to boot, do not panic. This problem is very common. In most cases, the server is not broken. Only the system startup failed.</p><p>You may see the instance as running in the console. SSH does not connect. The VNC screen is empty or stuck. This usually means the operating system could not start.</p><p>The most important thing to know is this. Your data is usually safe. We can fix the problem by repairing the system disk.</p><p>Follow the steps below slowly and carefully.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://9cld.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading 9Cloud! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h3>Step 1: Stop the ECS instance</h3><p>&#8226; Open the Alibaba Cloud console<br>&#8226; Stop the ECS instance<br>&#8226; Do not restart it again and again<br>&#8226; Wait until the status shows stopped</p><div><hr></div><h3>Step 2: Detach the system disk</h3><p>&#8226; Open the disk section of the instance<br>&#8226; Find the system disk<br>&#8226; Detach the disk<br>&#8226; Do not delete the disk</p><div><hr></div><h3>Step 3: Create a rescue ECS</h3><p>&#8226; Create a new ECS in the same region and zone<br>&#8226; Use a basic Linux image<br>&#8226; This server is only for fixing the disk</p><div><hr></div><h3>Step 4: Attach the broken disk</h3><p>&#8226; Attach the system disk to the rescue ECS<br>&#8226; Attach it as a data disk<br>&#8226; Start the rescue ECS<br>&#8226; Log in using SSH</p><div><hr></div><h3>Step 5: Find the disk</h3><p>Check the disks.</p><pre><code><code>lsblk
</code></code></pre><p>You will see a new disk. It is often named vdb. This is the broken system disk.</p><div><hr></div><h3>Step 6: Mount the disk</h3><p>Create a folder.</p><pre><code><code>mkdir /mnt/rescue
</code></code></pre><p>Mount the disk.</p><pre><code><code>mount /dev/vdb1 /mnt/rescue
</code></code></pre><p>If vdb1 does not work, check lsblk again and adjust the name.</p><div><hr></div><h3>Step 7: Check the fstab file</h3><p>This file is a very common cause of boot failure.</p><p>&#8226; Open the file<br>&#8226; Look for disks or UUID values that do not exist<br>&#8226; Comment out the broken lines</p><pre><code><code>cat /mnt/rescue/etc/fstab
vi /mnt/rescue/etc/fstab
</code></code></pre><p>If you are not sure about a line, comment it out and test later.</p><div><hr></div><h3>Step 8: Check boot files</h3><p>Make sure the boot folder is not empty.</p><pre><code><code>ls /mnt/rescue/boot
</code></code></pre><p>If this folder is empty or missing files, the system cannot boot.</p><div><hr></div><h3>Step 9: Fix the bootloader</h3><p>Prepare the environment.</p><pre><code><code>mount --bind /dev /mnt/rescue/dev
mount --bind /proc /mnt/rescue/proc
mount --bind /sys /mnt/rescue/sys
</code></code></pre><p>Enter the disk system.</p><pre><code><code>chroot /mnt/rescue
</code></code></pre><p>Reinstall the bootloader.</p><pre><code><code>grub-install /dev/vdb
grub-mkconfig -o /boot/grub/grub.cfg
</code></code></pre><p>Exit.</p><pre><code><code>exit
</code></code></pre><div><hr></div><h3>Step 10: Check logs if needed</h3><p>Logs can show what failed.</p><pre><code><code>cat /mnt/rescue/var/log/boot.log
cat /mnt/rescue/var/log/messages
</code></code></pre><p>If you see clear errors, fix them before continuing.</p><div><hr></div><h3>Step 11: Unmount the disk</h3><p>Unmount everything.</p><pre><code><code>umount /mnt/rescue/dev
umount /mnt/rescue/proc
umount /mnt/rescue/sys
umount /mnt/rescue
</code></code></pre><p>Detach the disk from the rescue ECS.</p><div><hr></div><h3>Step 12: Boot the original ECS</h3><p>&#8226; Attach the disk back as the system disk<br>&#8226; Start the ECS instance<br>&#8226; Try SSH again</p><p>If SSH works, the recovery is complete.</p><div><hr></div><h3>If it still does not work</h3><p>&#8226; Go back and recheck fstab<br>&#8226; Check boot files again<br>&#8226; If needed, rebuild the system and copy data from the disk</p><div><hr></div><h3>Simple rules to avoid this problem</h3><p>&#8226; Always take snapshots before changes<br>&#8226; Be very careful with fstab<br>&#8226; Do not rush disk or boot changes<br>&#8226; Test updates on a test server first</p><p>Alibaba Cloud ECS boot problems look scary, but they are usually easy to fix. If you stay calm and follow the steps, you can recover most servers without losing data.</p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://9cld.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading 9Cloud! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Day 6: Reranking Search Results ]]></title><description><![CDATA[The Secret Weapon for Search Relevance]]></description><link>https://9cld.com/p/day-6-reranking-search-results</link><guid isPermaLink="false">https://9cld.com/p/day-6-reranking-search-results</guid><dc:creator><![CDATA[Ankit Mehta]]></dc:creator><pubDate>Fri, 23 Jan 2026 04:10:25 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!p24C!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7362ac82-590f-4981-b8e2-b66e8b1880f3_1199x638.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!p24C!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7362ac82-590f-4981-b8e2-b66e8b1880f3_1199x638.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!p24C!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7362ac82-590f-4981-b8e2-b66e8b1880f3_1199x638.png 424w, https://substackcdn.com/image/fetch/$s_!p24C!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7362ac82-590f-4981-b8e2-b66e8b1880f3_1199x638.png 848w, https://substackcdn.com/image/fetch/$s_!p24C!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7362ac82-590f-4981-b8e2-b66e8b1880f3_1199x638.png 1272w, https://substackcdn.com/image/fetch/$s_!p24C!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7362ac82-590f-4981-b8e2-b66e8b1880f3_1199x638.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!p24C!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7362ac82-590f-4981-b8e2-b66e8b1880f3_1199x638.png" width="1199" height="638" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7362ac82-590f-4981-b8e2-b66e8b1880f3_1199x638.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:638,&quot;width&quot;:1199,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:551259,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://9cld.com/i/185497519?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7362ac82-590f-4981-b8e2-b66e8b1880f3_1199x638.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!p24C!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7362ac82-590f-4981-b8e2-b66e8b1880f3_1199x638.png 424w, https://substackcdn.com/image/fetch/$s_!p24C!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7362ac82-590f-4981-b8e2-b66e8b1880f3_1199x638.png 848w, https://substackcdn.com/image/fetch/$s_!p24C!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7362ac82-590f-4981-b8e2-b66e8b1880f3_1199x638.png 1272w, https://substackcdn.com/image/fetch/$s_!p24C!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7362ac82-590f-4981-b8e2-b66e8b1880f3_1199x638.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>You have built a semantic search. Your vectors are flowing. Documents are being retrieved.</p><p>But here is the uncomfortable truth, the best answer to your user&#8217;s question might be sitting at position #7 in your results. Or #15. Or buried somewhere in the middle where nobody will ever find it.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://9cld.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading 9Cloud! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>This is the gap that separates &#8220;working search&#8221; from &#8220;great search.&#8221;</p><div><hr></div><h2>The Restaurant Problem</h2><p>Imagine you walk into a restaurant and ask the waiter, &#8220;What is good here?&#8221;</p><p>The waiter disappears into the kitchen and returns with 20 dishes. They are all technically from the menu. They all contain food. But some are appetizers when you wanted a main course. Some are vegetarian when you eat meat. Some are simply not what the chef would recommend for someone like you.</p><p>This is what standard search does. It retrieves documents that match your query. But &#8220;matching&#8221; is not the same as &#8220;best answer.&#8221;</p><p>Now imagine the head chef comes out. She looks at the 20 dishes, considers your preferences, and says: &#8220;For you, start with these five.&#8221;</p><p>That is reranking.</p><div><hr></div><h2>What Reranking Actually Does</h2><p>Reranking is a two-stage retrieval technique. Here is how it works,</p><p><strong>Stage 1 (Retrieval):</strong> A fast search method (keyword, vector, or hybrid) retrieves candidate documents. This stage prioritizes recall. It casts a wide net to ensure the best answer is somewhere in the results.</p><p><strong>Stage 2 (Reranking):</strong> A more powerful model evaluates each candidate for relevance to the specific query. This stage prioritizes precision. It surfaces the truly relevant documents to the top.</p><p>The key insight is that these two stages have different strengths. Fast retrieval methods like BM25 or vector search can process millions of documents in milliseconds, but they make some mistakes. Cross-encoder reranking models are much more accurate, but they are too slow to run on your entire document collection.</p><p>Combine them, and you get the best of both worlds.</p><div><hr></div><h2>The Real Difference: Bi-Encoders vs Cross-Encoders</h2><p>To understand why reranking works so well, you need to understand the difference between bi-encoders and cross-encoders.</p><p><strong>Bi-Encoders (What You Use for Semantic Search)</strong></p><p>A bi-encoder processes the query and each document separately. It creates an embedding for the query and an embedding for each document. Then it compares them using cosine similarity.</p><p>This is fast because you can pre-compute document embeddings. When a query comes in, you only need to compute one embedding and compare it against your stored vectors.</p><p>But here is the problem, by processing the query and document separately, the model loses context. It cannot see how specific words in the query relate to specific words in the document.</p><p><strong>Cross-Encoders (What Rerankers Use)</strong></p><p>A cross-encoder processes the query and document together as a single input. The model sees &#8220;[QUERY] What is the capital of the United States? [DOC] Washington D.C. is the capital of the United States.&#8221;</p><p>Because it processes both together, it can understand relationships and context that bi-encoders miss. It can recognize that &#8220;capital&#8221; in the query means &#8220;seat of government,&#8221; not &#8220;uppercase letter&#8221; or &#8220;financial assets.&#8221;</p><p>The tradeoff is speed. You cannot pre-compute anything. Every query requires running the model on every candidate document.</p><div><hr></div><h2>A Concrete Example</h2><p>Let us say you search for: &#8220;What is the capital of the United States?&#8221;</p><p><strong>Without Reranking (Standard Search Results):</strong></p><ul><li><p>#1: &#8220;Carson City is the capital city of the American state of Nevada.&#8221;</p></li><li><p>#2: &#8220;The Commonwealth of the Northern Mariana Islands is a group of islands in the Pacific Ocean. Its capital is Saipan.&#8221;</p></li><li><p>#3: &#8220;Washington, D.C. (also known as simply Washington or D.C., and officially as the District of Columbia) is the capital of the United States.&#8221;</p></li><li><p>#4: &#8220;Capital punishment (the death penalty) has existed in the United States since before the United States was a country.&#8221;</p></li></ul><p>The correct answer is at position #3. The search engine got confused by documents that contain both &#8220;capital&#8221; and state names or &#8220;United States.&#8221;</p><p><strong>With Reranking:</strong></p><ul><li><p>#1 (score: 0.98): &#8220;Washington, D.C... is the capital of the United States.&#8221;</p></li><li><p>#2 (score: 0.28): &#8220;Capital punishment has existed in the United States...&#8221;</p></li><li><p>#3 (score: 0.10): &#8220;Carson City is the capital city of Nevada.&#8221;</p></li><li><p>#4 (score: 0.07): &#8220;Northern Mariana Islands... capital is Saipan.&#8221;</p></li></ul><p>The cross-encoder understood that the query is asking about a specific capital (the country&#8217;s), not just any capital, and not capital punishment. It pushed the correct answer to position #1 with 98% confidence.</p><div><hr></div><h2>Why This Matters for RAG</h2><p>If you are building retrieval-augmented generation (RAG) applications, reranking is not optional. It is essential.</p><p>When you send documents to an LLM as context, you are paying for tokens. More importantly, LLMs have limited attention. If you send 10 documents and the relevant one is at position #7, the LLM might miss it or give it less weight.</p><p>Reranking ensures that when you send 3-5 documents to your LLM, they are the RIGHT 3-5 documents. This improves answer quality and reduces costs.</p><div><hr></div><h2>The Reranking Providers</h2><p>OpenSearch supports multiple reranking approaches. Here is when to use each:</p><p><strong>Cohere Rerank</strong></p><p>Cohere offers one of the best reranking APIs available. It supports 100+ languages, handles structured data well, and integrates easily with OpenSearch through a connector.</p><p>Use Cohere when you want the best accuracy with minimal setup, and you are comfortable with external API calls. It is ideal for production systems where accuracy justifies the per-request cost.</p><p><strong>Amazon Bedrock</strong></p><p>Bedrock provides access to multiple reranking models, including Cohere Rerank, through AWS-native integration. Authentication uses IAM roles instead of API keys.</p><p>Use Bedrock when you are already in the AWS ecosystem and want native integration with IAM security. Regional availability varies, so check that your preferred model is available in your region.</p><p><strong>Amazon SageMaker</strong></p><p>SageMaker lets you deploy open-source reranking models like ms-marco-MiniLM or BGE-reranker-v2-m3 on your own infrastructure. You control the hardware, the model, and the data flow.</p><p>Use SageMaker when you have high query volumes (where per-request pricing becomes expensive), strict data residency requirements, or you want to use custom fine-tuned models.</p><p><strong>Cross-Encoder (Local)</strong></p><p>You can also deploy cross-encoder models directly within OpenSearch. This eliminates external API calls entirely but requires more infrastructure management.</p><p>Use local cross-encoders in air-gapped environments or when latency is absolutely critical.</p><div><hr></div><h2>Setting Up Reranking with Cohere</h2><p>Here is the complete setup flow for Cohere Rerank in OpenSearch:</p><p><strong>Step 1: Create the Connector</strong></p><p>json</p><pre><code><code>POST /_plugins/_ml/connectors/_create
{
  "name": "cohere-rerank",
  "description": "Connector to Cohere Rerank model",
  "version": "1",
  "protocol": "http",
  "credential": {
    "cohere_key": "your_cohere_api_key"
  },
  "parameters": {
    "model": "rerank-english-v3.0"
  },
  "actions": [
    {
      "action_type": "predict",
      "method": "POST",
      "url": "https://api.cohere.ai/v1/rerank",
      "headers": {
        "Authorization": "Bearer ${credential.cohere_key}"
      },
      "request_body": "{ \"documents\": ${parameters.documents}, \"query\": \"${parameters.query}\", \"model\": \"${parameters.model}\", \"top_n\": ${parameters.top_n} }",
      "pre_process_function": "connector.pre_process.cohere.rerank",
      "post_process_function": "connector.post_process.cohere.rerank"
    }
  ]
}</code></code></pre><p>Save the connector_id from the response.</p><p><strong>Step 2: Register and Deploy the Model</strong></p><p>json</p><pre><code><code>POST /_plugins/_ml/models/_register?deploy=true
{
  "name": "cohere rerank model",
  "function_name": "remote",
  "description": "Cohere Rerank for search relevance",
  "connector_id": "your_connector_id"
}</code></code></pre><p>Save the model_id from the response.</p><p><strong>Step 3: Create a Search Pipeline</strong></p><p>json</p><pre><code><code>PUT /_search/pipeline/rerank_pipeline
{
  "description": "Pipeline for reranking with Cohere",
  "response_processors": [
    {
      "rerank": {
        "ml_opensearch": {
          "model_id": "your_model_id"
        },
        "context": {
          "document_fields": ["passage_text"]
        }
      }
    }
  ]
}</code></code></pre><p>The document_fields parameter tells the reranker which fields to consider when scoring relevance. If you specify multiple fields, their values are concatenated before reranking.</p><p><strong>Step 4: Search with Reranking</strong></p><p>json</p><pre><code><code>GET my-index/_search?search_pipeline=rerank_pipeline
{
  "query": {
    "match": {
      "passage_text": "What is the capital of the United States?"
    }
  },
  "size": 10,
  "ext": {
    "rerank": {
      "query_context": {
        "query_text": "What is the capital of the United States?"
      }
    }
  }
}</code></code></pre><p>The ext.rerank.query_context.query_text is what the reranker uses to score documents. In most cases, this matches your search query, but it can be different if needed.</p><div><hr></div><h2>Reranking by Field</h2><p>Sometimes you already have relevance scores in your documents. Maybe a previous ML model computed them during indexing, or you have user ratings, or business priority scores.</p><p>OpenSearch supports field-based reranking that reorders results by a document field without calling an external model:</p><p>json</p><pre><code><code>PUT /_search/pipeline/rerank_by_stars
{
  "response_processors": [
    {
      "rerank": {
        "by_field": {
          "target_field": "reviews.stars",
          "keep_previous_score": true
        }
      }
    }
  ]
}</code></code></pre><p>This is useful for scenarios where you want to combine search relevance with business logic, like boosting highly-rated products or recent content.</p><p>You can also chain ML inference with field-based reranking. First, an ML model writes a relevance score to each document, then the field-based reranker sorts by that score.</p><div><hr></div><h2>AWS vs Alibaba Cloud</h2><p>Both AWS and Alibaba Cloud support reranking in their managed OpenSearch services, but the implementations differ:</p><p><strong>AWS OpenSearch Service</strong></p><ul><li><p>Native Bedrock integration for Cohere Rerank and other models</p></li><li><p>SageMaker endpoints for custom rerankers</p></li><li><p>IAM role-based authentication with SigV4 signing</p></li><li><p>Strong regional availability in the Americas and Europe</p></li></ul><p><strong>Alibaba Cloud OpenSearch</strong></p><ul><li><p>Model Studio integration for Qwen-based reranking</p></li><li><p>PAI-EAS for custom model deployment</p></li><li><p>RAM role-based authentication</p></li><li><p>Strong regional availability in Asia-Pacific, especially China</p></li></ul><p>The connector patterns are similar, but authentication differs. AWS uses IAM roles with SigV4 request signing. Alibaba uses RAM roles with AccessKey/SecretKey pairs.</p><div><hr></div><h2>Performance Considerations</h2><p>Reranking adds latency to your search pipeline. A typical cross-encoder takes 10-50ms to score a batch of documents. Here is how to manage this:</p><p><strong>Limit candidate documents:</strong> Only rerank the top 100-200 results from your first-stage retrieval. Reranking 1000 documents is unnecessarily expensive.</p><p><strong>Set appropriate size:</strong> The size parameter controls how many documents you return to the user. The reranker scores all candidates, but you only return the top N.</p><p><strong>Consider hybrid queries:</strong> If you use hybrid search (keyword + vector), reranking happens after the normalization processor. The combined scores are normalized first, then reranked.</p><p><strong>Monitor costs:</strong> External APIs like Cohere charge per request. High-volume applications should consider SageMaker or local deployment.</p><div><hr></div><h2>What is Next</h2><p>Tomorrow, we dive into RAG and conversational search. With reranking in your toolkit, you are ready to build AI applications that not only retrieve documents but also synthesize them into coherent answers.</p><div><hr></div><p><strong>Interactive Guide:</strong> opensearch.9cld.com/day/06-reranking</p><p><strong>Day 6 of 60 - From Zero to OpenSearch Hero</strong></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://9cld.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading 9Cloud! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Day 5: Semantic Search - From Vectors to Meaning]]></title><description><![CDATA[From Zero to OpenSearch Hero - Day 5 of 60]]></description><link>https://9cld.com/p/day-5-semantic-search-from-vectors</link><guid isPermaLink="false">https://9cld.com/p/day-5-semantic-search-from-vectors</guid><dc:creator><![CDATA[Ankit Mehta]]></dc:creator><pubDate>Wed, 21 Jan 2026 06:18:20 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!AKvu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f0dd18e-912c-4001-a188-45a8365a13b2_1205x628.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!AKvu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f0dd18e-912c-4001-a188-45a8365a13b2_1205x628.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!AKvu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f0dd18e-912c-4001-a188-45a8365a13b2_1205x628.png 424w, https://substackcdn.com/image/fetch/$s_!AKvu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f0dd18e-912c-4001-a188-45a8365a13b2_1205x628.png 848w, https://substackcdn.com/image/fetch/$s_!AKvu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f0dd18e-912c-4001-a188-45a8365a13b2_1205x628.png 1272w, https://substackcdn.com/image/fetch/$s_!AKvu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f0dd18e-912c-4001-a188-45a8365a13b2_1205x628.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!AKvu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f0dd18e-912c-4001-a188-45a8365a13b2_1205x628.png" width="1205" height="628" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5f0dd18e-912c-4001-a188-45a8365a13b2_1205x628.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:628,&quot;width&quot;:1205,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:538084,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://9cld.com/i/185266434?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f0dd18e-912c-4001-a188-45a8365a13b2_1205x628.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!AKvu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f0dd18e-912c-4001-a188-45a8365a13b2_1205x628.png 424w, https://substackcdn.com/image/fetch/$s_!AKvu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f0dd18e-912c-4001-a188-45a8365a13b2_1205x628.png 848w, https://substackcdn.com/image/fetch/$s_!AKvu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f0dd18e-912c-4001-a188-45a8365a13b2_1205x628.png 1272w, https://substackcdn.com/image/fetch/$s_!AKvu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f0dd18e-912c-4001-a188-45a8365a13b2_1205x628.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>The Missing Piece from Day 4</h2><p>Yesterday, we learned about vector search, the mechanism for finding similar vectors using k-NN algorithms. We indexed coordinates, computed distances, and retrieved nearest neighbors.</p><p>But there was a gap.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://9cld.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading 9Cloud! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>We talked about &#8220;embeddings&#8221; without explaining where they come from. We assumed vectors existed without showing how text becomes numbers. We skipped the most important question:</p><p><strong>How does &#8220;portable computer for travel&#8221; become a vector that sits near &#8220;laptop&#8221; in mathematical space?</strong></p><p>That is what Day 5 answers. Today, we connect the dots between raw text and the vectors that power semantic search.</p><div><hr></div><h2>What Is Semantic Search?</h2><p>Semantic search understands meaning, not just matching tokens.</p><p>When you search &#8220;portable computer for travel,&#8221; semantic search knows you probably mean laptops. It returns MacBook Airs, Dell XPS 13s, and ThinkPad X1 Carbons, even though none of those documents contain the exact phrase &#8220;portable computer.&#8221;</p><p>The magic happens in the <strong>embedding model</strong>, a neural network trained on billions of text examples that learned to place similar meanings close together in vector space.</p><div><hr></div><h2>What is the difference between Vector Search and Semantic Search?</h2><p>Before we go further, let me clarify something that confuses many people.</p><p><strong>Vector Search</strong> is the underlying mechanism, the &#8220;how.&#8221; It finds documents by comparing numerical vectors using distance metrics like cosine similarity or Euclidean distance. Vector search does not care what the vectors represent. They could be image embeddings, user preference vectors, GPS coordinates, or random numbers.</p><p><strong>Semantic Search</strong> is a specific application, the &#8220;what for.&#8221; It uses vector search to find documents based on meaning. The key ingredient is the embedding model that converts text into vectors, where similar meanings produce similar numbers.</p><p>Think of it this way:</p><ul><li><p><strong>Vector search</strong> = &#8220;Find me vectors closest to this vector.&#8221;</p></li><li><p><strong>Semantic search</strong> = &#8220;Find me documents with similar meaning to this text&#8221; (which uses vector search under the hood)</p></li></ul><p>The relationship:</p><pre><code><code>Vector Search (mechanism)
    &#9500;&#9472;&#9472; Semantic Search (text application)
    &#9500;&#9472;&#9472; Image Search (image application)  
    &#9500;&#9472;&#9472; Recommendation Systems (user/item application)
    &#9492;&#9472;&#9472; Any similarity-based retrieval</code></code></pre><p>Semantic search IS vector search, but vector search is not always semantic search.</p><p><strong>When to use which terminology:</strong></p><ul><li><p>Say &#8220;vector search&#8221; when working with pre-computed vectors, images, recommendations, or any non-text similarity matching</p></li><li><p>Say &#8220;semantic search&#8221; when the goal is matching text by meaning using embedding models</p></li><li><p>Day 4 covered vector search fundamentals (k-NN, HNSW, bringing your own vectors)</p></li><li><p>Day 5 (this post) covers semantic search - the text-meaning application</p></li></ul><div><hr></div><h2>How Semantic Search Works</h2><p>Here is how it works at a high level.</p><p><strong>Step 1: Text becomes numbers.</strong> An embedding model converts text into a dense vector, a list of floating-point numbers (typically 384 to 3072 values) that captures the semantic meaning. Similar meanings produce similar numbers.</p><p><strong>Step 2: Store in a vector index.</strong> OpenSearch stores these vectors in a k-NN index. During ingestion, a pipeline automatically generates embeddings from your text fields.</p><p><strong>Step 3: Search by meaning.</strong> Your query is also converted to a vector. OpenSearch finds documents with vectors closest to your query vector using approximate nearest neighbor algorithms.</p><p>The key insight: &#8220;portable computer&#8221; and &#8220;laptop&#8221; end up close together in vector space because the embedding model learned they mean similar things from training on billions of text examples.</p><div><hr></div><h2>Keyword vs Semantic: When to Use Which</h2><p>This is not an either/or decision. Both have their place.</p><p><strong>Keyword search (BM25) excels at:</strong></p><ul><li><p>Product SKUs and exact identifiers</p></li><li><p>Names, titles, and proper nouns</p></li><li><p>Technical terms users type precisely</p></li><li><p>Cases where an exact match is required</p></li></ul><p><strong>Semantic search excels at:</strong></p><ul><li><p>Natural language questions</p></li><li><p>Conceptual queries (&#8221;something for headaches&#8221;)</p></li><li><p>FAQ and knowledge base matching</p></li><li><p>RAG applications feeding LLMs</p></li><li><p>Cross-language understanding</p></li></ul><p><strong>Hybrid search combines both.</strong> You run keyword and semantic search in parallel, normalize the scores, and merge the results. This gives you exact matches when they exist, and semantically similar results when keywords don't match.</p><p>Most production systems use hybrid search.</p><div><hr></div><h2>The Embedding Provider Decision</h2><p>Before you can do semantic search, you need an embedding model. OpenSearch connects to external models through connectors. Your options:</p><p><strong>OpenAI</strong> - The industry standard. Their text-embedding-ada-002 produces 1536-dimensional vectors. Easy to set up, well-documented, but your data leaves your network, and you pay per token.</p><p><strong>Cohere</strong> - Strong multilingual support and input type optimization (different handling for queries vs documents). Available directly via API or through Amazon Bedrock. Supports byte-quantized vectors for memory efficiency.</p><p><strong>Amazon Bedrock Titan</strong> - Runs entirely within AWS. Your data never leaves the AWS network. Uses IAM for authentication instead of API keys. Ideal for regulated industries and compliance requirements.</p><p><strong>Amazon SageMaker</strong> - Deploy any model you want. Use Hugging Face sentence transformers, fine-tuned models, or custom architectures. Maximum flexibility, but you manage the infrastructure.</p><p><strong>Local Models</strong> - Run sentence-transformer models directly on OpenSearch ML nodes. Zero external dependencies, complete data privacy. Best for air-gapped environments, but uses cluster resources and is limited to smaller models.</p><p>Here is a quick decision guide:</p><ul><li><p>Need multilingual? <strong>Cohere</strong></p></li><li><p>Data cannot leave AWS? <strong>Bedrock Titan</strong></p></li><li><p>Custom/fine-tuned model? <strong>SageMaker</strong></p></li><li><p>Air-gapped environment? <strong>Local Models</strong></p></li><li><p>Quick prototype? <strong>OpenAI</strong></p></li></ul><div><hr></div><h2>Setting Up Semantic Search: The Complete Flow</h2><p>Let me walk through the entire setup using Amazon Bedrock Titan as the embedding provider. The same pattern applies to other providers with minor connector changes.</p><h3>1. Create the Connector</h3><p>A connector tells OpenSearch how to communicate with the embedding model. For Bedrock, you need an IAM role that allows the connector to invoke the model.</p><p>json</p><pre><code><code>POST /_plugins/_ml/connectors/_create
{
  "name": "Amazon Bedrock Connector: titan embedding v1",
  "description": "Connector to Bedrock Titan embedding model",
  "version": 1,
  "protocol": "aws_sigv4",
  "parameters": {
    "region": "us-east-1",
    "service_name": "bedrock"
  },
  "credential": {
    "roleArn": "arn:aws:iam::YOUR_ACCOUNT:role/bedrock-invoke-role"
  },
  "actions": [
    {
      "action_type": "predict",
      "method": "POST",
      "url": "https://bedrock-runtime.${parameters.region}.amazonaws.com/model/amazon.titan-embed-text-v1/invoke",
      "headers": {
        "content-type": "application/json",
        "x-amz-content-sha256": "required"
      },
      "request_body": "{ \"inputText\": \"${parameters.inputText}\" }",
      "pre_process_function": "connector.pre_process.bedrock.embedding",
      "post_process_function": "connector.post_process.bedrock.embedding"
    }
  ]
}</code></code></pre><p>Save the connector_id from the response.</p><h3>2. Register and Deploy the Model</h3><p>json</p><pre><code><code>POST /_plugins/_ml/models/_register
{
  "name": "bedrock titan embedding model v1",
  "function_name": "remote",
  "description": "Bedrock Titan text embedding model",
  "connector_id": "YOUR_CONNECTOR_ID"
}</code></code></pre><p>Then deploy it:</p><p>json</p><pre><code><code>POST /_plugins/_ml/models/YOUR_MODEL_ID/_deploy</code></code></pre><p>Test that it works:</p><p>json</p><pre><code><code>POST /_plugins/_ml/models/YOUR_MODEL_ID/_predict
{
  "parameters": {
    "inputText": "hello world"
  }
}</code></code></pre><p>You should get back a vector with 1536 floating-point numbers.</p><h3>3. Create the Ingest Pipeline</h3><p>The ingest pipeline automatically generates embeddings when documents are indexed:</p><p>json</p><pre><code><code>PUT /_ingest/pipeline/my_embedding_pipeline
{
  "description": "Text embedding pipeline for semantic search",
  "processors": [
    {
      "text_embedding": {
        "model_id": "YOUR_MODEL_ID",
        "field_map": {
          "text": "text_embedding"
        }
      }
    }
  ]
}</code></code></pre><p>This says: take the <code>text</code> field, run it through the model, store the result in <code>text_embedding</code>.</p><h3>4. Create the Vector Index</h3><p>json</p><pre><code><code>PUT /semantic-search-index
{
  "settings": {
    "index": {
      "knn": true,
      "knn.space_type": "cosinesimil",
      "default_pipeline": "my_embedding_pipeline"
    }
  },
  "mappings": {
    "properties": {
      "text": {
        "type": "text"
      },
      "text_embedding": {
        "type": "knn_vector",
        "dimension": 1536
      }
    }
  }
}</code></code></pre><p>The dimension must match your embedding model output. Titan produces 1536. Cohere produces 1024. Local models vary.</p><h3>5. Index and Search</h3><p>Index a document (embeddings are generated automatically):</p><p>json</p><pre><code><code>POST /semantic-search-index/_doc
{
  "text": "OpenSearch is a distributed search and analytics engine"
}</code></code></pre><p>Run a semantic search:</p><p>json</p><pre><code><code>GET /semantic-search-index/_search
{
  "_source": {
    "excludes": ["text_embedding"]
  },
  "query": {
    "neural": {
      "text_embedding": {
        "query_text": "distributed database for logs",
        "model_id": "YOUR_MODEL_ID",
        "k": 10
      }
    }
  }
}</code></code></pre><p>Notice how the query &#8220;distributed database for logs&#8221; can match the document about &#8220;distributed search and analytics engine&#8221; even though the exact words differ. The embedding model understands the semantic similarity.</p><div><hr></div><h2>Asymmetric Search: Queries Are Not Documents</h2><p>Here is something most tutorials skip.</p><p>In real search, queries and documents are fundamentally different. A query is short: &#8220;best laptop for coding.&#8221; A document is long: a 500-word product review.</p><p><strong>Symmetric models</strong> treat both the same way. They work okay when query and document are similar length. They struggle when a 5-word query must match a 500-word document.</p><p><strong>Asymmetric models</strong> use different encoding strategies. Queries get expanded to capture intent. Documents get compressed to capture essence.</p><p>Models that support asymmetric search include E5 models (use &#8220;query:&#8221; and &#8220;passage:&#8221; prefixes), Cohere Embed v3 (use input_type: &#8220;search_query&#8221; vs &#8220;search_document&#8221;), and BGE models.</p><p>To implement asymmetric search in OpenSearch, you need separate pipelines for ingestion and search:</p><p><strong>Ingest pipeline (for documents):</strong></p><p>json</p><pre><code><code>PUT /_ingest/pipeline/asymmetric_embedding_ingest_pipeline
{
  "processors": [
    {
      "ml_inference": {
        "model_id": "YOUR_MODEL_ID",
        "input_map": [
          { "inputText": "passage: ${description}" }
        ],
        "output_map": [
          { "fact_embedding": "$.embedding" }
        ]
      }
    }
  ]
}</code></code></pre><p><strong>Search pipeline (for queries):</strong></p><p>json</p><pre><code><code>PUT /_search/pipeline/asymmetric_embedding_search_pipeline
{
  "request_processors": [
    {
      "ml_inference": {
        "model_id": "YOUR_MODEL_ID",
        "input_map": [
          { "inputText": "query: ${ext.ml_inference.params.query}" }
        ],
        "output_map": [
          { "ext.ml_inference.params.vector": "$.embedding" }
        ]
      }
    }
  ]
}</code></code></pre><p>The &#8220;passage:&#8221; and &#8220;query:&#8221; prefixes tell the model to encode the text differently based on its purpose.</p><div><hr></div><h2>Handling Long Documents: Text Chunking</h2><p>Most embedding models have token limits. Titan V2 supports 8,192 tokens maximum. A 50,000-word technical manual will not fit.</p><p>Even if truncation worked, a single embedding for a long document loses detail. The first section dominates the vector; everything else is barely represented.</p><p><strong>Text chunking</strong> splits documents into searchable passages. Each chunk gets its own embedding. When you search, you find the best-matching chunk, and return its parent document.</p><p>Here is a chunking pipeline:</p><p>json</p><pre><code><code>PUT _ingest/pipeline/chunking-embedding-pipeline
{
  "processors": [
    {
      "text_chunking": {
        "algorithm": {
          "fixed_token_length": {
            "token_limit": 100,
            "overlap_rate": 0.2,
            "tokenizer": "standard"
          }
        },
        "field_map": {
          "passage_text": "passage_chunk"
        }
      }
    },
    {
      "foreach": {
        "field": "passage_chunk",
        "processor": {
          "ml_inference": {
            "model_id": "YOUR_MODEL_ID",
            "input_map": [
              { "inputText": "_ingest._value.text" }
            ],
            "output_map": [
              { "_ingest._value.embedding": "embedding" }
            ]
          }
        }
      }
    }
  ]
}</code></code></pre><p>Chunks are stored as nested objects. Each document has multiple chunk embeddings. When searching, use a nested query with <code>score_mode: "max"</code> so the parent document is scored by its best-matching chunk.</p><div><hr></div><h2>Semantic Highlighting</h2><p>Traditional highlighting finds keyword matches and wraps them in tags. Semantic highlighting uses ML models to find the most relevant sentences - even when exact keywords are absent.</p><p>Query: &#8220;heart treatment&#8221;</p><p>Traditional highlighting: &#8220;...<strong>heart</strong> disease requires <strong>treatment</strong>...&#8221;</p><p>Semantic highlighting: &#8220;<strong>Cardiovascular therapy options include medication and surgical procedures</strong>...&#8221;</p><p>The second result found a semantically relevant sentence even though the words &#8220;heart&#8221; and &#8220;treatment&#8221; do not appear.</p><p>To use semantic highlighting, your field must be of type <code>semantic</code> and you need a sentence highlighting model deployed separately from your embedding model:</p><p>json</p><pre><code><code>GET /my-index/_search
{
  "query": {
    "neural": {
      "text_embedding": {
        "query_text": "treatments for neurodegenerative diseases",
        "model_id": "YOUR_EMBEDDING_MODEL_ID",
        "k": 5
      }
    }
  },
  "highlight": {
    "fields": {
      "text": {
        "type": "semantic",
        "number_of_fragments": 2
      }
    },
    "options": {
      "model_id": "YOUR_SENTENCE_HIGHLIGHTING_MODEL_ID"
    }
  }
}</code></code></pre><div><hr></div><h2>AWS vs Alibaba Cloud: Semantic Search Comparison</h2><p>Both clouds offer managed OpenSearch with semantic search capabilities. The approaches differ.</p><p><strong>Amazon OpenSearch Service</strong> integrates with Bedrock (Titan, Cohere, Claude), SageMaker (any model), and external APIs. Uses IAM roles for authentication. CloudFormation templates automate the setup. Strong in Americas and Europe.</p><p><strong>Alibaba Cloud OpenSearch</strong> integrates with Model Studio (Qwen, custom models) and PAI-EAS for self-hosted models. Uses RAM roles instead of IAM. Resource Orchestration Service (ROS) for automation. Strong in APAC and China.</p><p>Key differences:</p><ul><li><p><strong>Authentication:</strong> AWS uses IAM roles; Alibaba uses RAM roles</p></li><li><p><strong>Foundation models:</strong> AWS has Bedrock; Alibaba has Model Studio</p></li><li><p><strong>Automation:</strong> AWS CloudFormation; Alibaba ROS templates</p></li><li><p><strong>Regional strength:</strong> AWS better in Americas/Europe; Alibaba better in APAC</p></li></ul><p>Choose based on where your users are, what ecosystem you are already using, and which foundation models matter to you.</p><div><hr></div><h2>What We Covered</h2><p>Day 5 was dense. Here is the recap:</p><ul><li><p><strong>Vector vs Semantic search</strong> - Vector search is the mechanism (comparing vectors). Semantic search is a text application that uses vector search with embedding models to match meaning.</p></li><li><p><strong>Semantic search</strong> matches meaning, not keywords. It uses embedding models to convert text to vectors and k-NN search to find similar documents.</p></li><li><p><strong>Multiple embedding providers</strong> exist - OpenAI, Cohere, Bedrock Titan, SageMaker, local models. Choose based on privacy requirements, cost, and infrastructure.</p></li><li><p><strong>The setup flow:</strong> Create connector &#8594; Register model &#8594; Create ingest pipeline &#8594; Create vector index &#8594; Index documents &#8594; Search.</p></li><li><p><strong>Asymmetric search</strong> handles the mismatch between short queries and long documents by encoding them differently.</p></li><li><p><strong>Text chunking</strong> splits long documents into searchable passages, each with its own embedding.</p></li><li><p><strong>Semantic highlighting</strong> finds relevant passages using meaning, not keyword matching.</p></li><li><p><strong>AWS and Alibaba Cloud</strong> both support semantic search with different foundation model and authentication approaches.</p></li></ul><p>Tomorrow we go deeper into hybrid search - combining keyword and semantic approaches for the best of both worlds.</p><div><hr></div><p><strong>Interactive guide:</strong> <a href="https://opensearch.9cld.com/day/05-semantic-search">https://opensearch.9cld.com/day/05-semantic-search</a></p><p><strong>All guides:</strong> </p><p>https://opensearch.9cld.com/</p><div><hr></div><p><em>Building in public. Learning out loud.</em></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://9cld.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading 9Cloud! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Day 3: Your First Conversation with OpenSearch]]></title><description><![CDATA[Build your first searchable database with real data and real queries]]></description><link>https://9cld.com/p/day-3-your-first-conversation-with</link><guid isPermaLink="false">https://9cld.com/p/day-3-your-first-conversation-with</guid><dc:creator><![CDATA[Ankit Mehta]]></dc:creator><pubDate>Wed, 14 Jan 2026 05:08:17 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!N-3q!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffee9c2c7-73c8-4d0c-a47f-9a51cd5e9e39_1200x630.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!N-3q!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffee9c2c7-73c8-4d0c-a47f-9a51cd5e9e39_1200x630.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!N-3q!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffee9c2c7-73c8-4d0c-a47f-9a51cd5e9e39_1200x630.png 424w, https://substackcdn.com/image/fetch/$s_!N-3q!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffee9c2c7-73c8-4d0c-a47f-9a51cd5e9e39_1200x630.png 848w, https://substackcdn.com/image/fetch/$s_!N-3q!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffee9c2c7-73c8-4d0c-a47f-9a51cd5e9e39_1200x630.png 1272w, https://substackcdn.com/image/fetch/$s_!N-3q!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffee9c2c7-73c8-4d0c-a47f-9a51cd5e9e39_1200x630.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!N-3q!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffee9c2c7-73c8-4d0c-a47f-9a51cd5e9e39_1200x630.png" width="1200" height="630" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fee9c2c7-73c8-4d0c-a47f-9a51cd5e9e39_1200x630.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:630,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:312512,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://9cld.com/i/184512203?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffee9c2c7-73c8-4d0c-a47f-9a51cd5e9e39_1200x630.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!N-3q!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffee9c2c7-73c8-4d0c-a47f-9a51cd5e9e39_1200x630.png 424w, https://substackcdn.com/image/fetch/$s_!N-3q!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffee9c2c7-73c8-4d0c-a47f-9a51cd5e9e39_1200x630.png 848w, https://substackcdn.com/image/fetch/$s_!N-3q!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffee9c2c7-73c8-4d0c-a47f-9a51cd5e9e39_1200x630.png 1272w, https://substackcdn.com/image/fetch/$s_!N-3q!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffee9c2c7-73c8-4d0c-a47f-9a51cd5e9e39_1200x630.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Yesterday, we got OpenSearch running in Docker. Today, we learn to actually talk to it.</p><div><hr></div><h2>Why This Matters</h2><p>Here&#8217;s what most tutorials get wrong, they throw API commands at you without explaining the conversation happening under the hood.</p><p>OpenSearch isn&#8217;t magic. It is a service that speaks REST. Every operation,  indexing a document, running a search, checking cluster health, is just an HTTP request.</p><p>Today you will understand:</p><ul><li><p><strong>How</strong> to communicate with OpenSearch (the mechanics)</p></li><li><p><strong>Why</strong> each method exists (the reasoning)</p></li><li><p><strong>What</strong> happens internally (the architecture)</p></li></ul><p>By the end, you won&#8217;t just know <em>what</em> commands to run. You will understand <em>why</em> they work.</p><div><hr></div><h2>The REST API: Your Language to OpenSearch</h2><p>OpenSearch speaks HTTP. That&#8217;s it. No proprietary protocol, no special client required.</p><p>Every operation follows this pattern:</p><pre><code><code>HTTP_METHOD host:port/index/operation</code></code></pre><h3>The Five HTTP Methods</h3><p><strong>GET</strong> - Retrieve information</p><pre><code><code>GET /_cluster/health          # Cluster status
GET /products/_search         # Search documents
GET /products/_doc/123        # Get specific document</code></code></pre><p><strong>PUT</strong> - Create or replace (idempotent)</p><pre><code><code>PUT /products                 # Create index
PUT /products/_doc/123        # Create/replace document with ID</code></code></pre><p><strong>POST</strong> - Create or update (not idempotent)</p><pre><code><code>POST /products/_doc           # Create document (auto-generate ID)
POST /products/_search        # Search (yes, POST for complex queries)</code></code></pre><p><strong>DELETE</strong> - Remove resources</p><pre><code><code>DELETE /products/_doc/123     # Delete document
DELETE /products              # Delete entire index</code></code></pre><p><strong>HEAD</strong> - Check existence (no body returned)</p><pre><code><code>HEAD /products                # Does index exist?</code></code></pre><h3>The Anatomy of a Request</h3><p>Let&#8217;s dissect a real request:</p><p>bash</p><pre><code><code>curl -X POST "https://localhost:9200/products/_doc" \
  -H "Content-Type: application/json" \
  -u admin:YourPassword \
  -k -d '{
    "name": "Wireless Headphones",
    "price": 149.99
  }'</code></code></pre><p>Breaking it down:</p><ul><li><p><code>-X POST</code> - HTTP method (create)</p></li><li><p><code>localhost:9200</code> - Your cluster endpoint</p></li><li><p><code>/products</code> - Target index name</p></li><li><p><code>/_doc</code> - Document API endpoint</p></li><li><p><code>-H "Content-Type: application/json"</code> - We&#8217;re sending JSON</p></li><li><p><code>-u admin:YourPassword</code> - Basic auth credentials</p></li><li><p><code>-k</code> - Skip SSL verification (dev only!)</p></li><li><p><code>-d '{...}'</code> - The JSON document body</p></li></ul><div><hr></div><h2>Step 1: Check Cluster Health</h2><p>Before doing anything, verify your cluster is healthy:</p><pre><code><code>curl -X GET "https://localhost:9200/_cluster/health?pretty" \
  -u admin:YourPassword -k</code></code></pre><p>Response:</p><pre><code><code>{
  "cluster_name": "opensearch-cluster",
  "status": "green",
  "number_of_nodes": 1,
  "number_of_data_nodes": 1,
  "active_primary_shards": 5,
  "active_shards": 5
}</code></code></pre><h3>Understanding Status Colors</h3><p><strong>green</strong> - All shards assigned. You&#8217;re good!</p><p><strong>yellow</strong> - Primary shards OK, some replicas unassigned. Normal for a single node.</p><p><strong>red</strong> - Some primary shards are missing. Data unavailable, investigate!</p><p><strong>Why yellow is OK for development:</strong> With one node, OpenSearch can&#8217;t place replica shards on a different node. It is smart enough to not put the backup on the same machine as the original.</p><div><hr></div><h2>Understanding &#8220;Tables&#8221; in OpenSearch</h2><p>If you are coming from a relational database background, you might be wondering: &#8220;Where are the tables?&#8221;</p><p>OpenSearch doesn&#8217;t have tables. Instead, it has <strong>indexes</strong>.</p><p><strong>Database vs OpenSearch terminology:</strong></p><ul><li><p>Database &#8594; Cluster</p></li><li><p>Table &#8594; Index</p></li><li><p>Row &#8594; Document</p></li><li><p>Column &#8594; Field</p></li><li><p>Schema &#8594; Mapping</p></li></ul><p>So when someone says &#8220;create a table,&#8221; in OpenSearch we say &#8220;create an index.&#8221;</p><p>The key difference? </p><p>In a database, you query rows. </p><p>In OpenSearch, you search documents. </p><p>The entire system is optimized for finding things fast, not for transactions or joins.</p><div><hr></div><h2>Step 2: Create an Index</h2><p>An index is where your documents live. Think of it as a database table, but optimized for search.</p><h3>Basic Index Creation</h3><p>The simplest way to create an index:</p><pre><code><code>curl -X PUT "https://localhost:9200/products" \
  -u admin:YourPassword -k</code></code></pre><p>That&#8217;s it. OpenSearch will create an index with default settings. But you will almost always want to define settings and mappings upfront.</p><h3>Index with Settings and Mappings</h3><pre><code><code>curl -X PUT "https://localhost:9200/products" \
  -H "Content-Type: application/json" \
  -u admin:YourPassword -k -d '{
    "settings": {
      "number_of_shards": 1,
      "number_of_replicas": 1
    },
    "mappings": {
      "properties": {
        "name": { "type": "text" },
        "price": { "type": "float" },
        "category": { "type": "keyword" },
        "description": { "type": "text" },
        "in_stock": { "type": "boolean" },
        "created_at": { "type": "date" }
      }
    }
  }'</code></code></pre><h3>Verify Your Index Was Created</h3><pre><code><code>curl -X GET "https://localhost:9200/products?pretty" \
  -u admin:YourPassword -k</code></code></pre><h3>List All Indexes</h3><pre><code><code>curl -X GET "https://localhost:9200/_cat/indices?v" \
  -u admin:YourPassword -k</code></code></pre><p>Response:</p><pre><code><code>health status index    uuid                   pri rep docs.count store.size
green  open   products abc123xyz...           1   1   0          230b</code></code></pre><h3>Delete an Index</h3><pre><code><code>curl -X DELETE "https://localhost:9200/products" \
  -u admin:YourPassword -k</code></code></pre><p><strong>Warning:</strong> This deletes all data in the index. There&#8217;s no &#8220;are you sure?&#8221; prompt.</p><h3>Understanding the Settings</h3><p><strong>number_of_shards: 1</strong></p><ul><li><p>How many pieces to split the index into</p></li><li><p>More shards = better parallelism for large datasets</p></li><li><p>Can&#8217;t change after creation!</p></li><li><p>Rule of thumb: 1 shard can handle ~30GB</p></li></ul><p><strong>number_of_replicas: 1</strong></p><ul><li><p>Copies of each shard for redundancy</p></li><li><p>More replicas = better read throughput + fault tolerance</p></li><li><p>Can change anytime</p></li></ul><h3>Understanding the Mapping</h3><p>The mapping defines how each field is stored and indexed:</p><p><strong>text</strong> - For full-text content. Analyzed word-by-word for search.</p><p><strong>keyword</strong> - For exact values like categories or IDs. Exact match only.</p><p><strong>float</strong> - For decimal numbers. Supports range queries.</p><p><strong>boolean</strong> - For true/false values. Used in filter queries.</p><p><strong>date</strong> - For timestamps. Supports date math queries.</p><p><strong>Critical distinction:</strong></p><ul><li><p><code>text</code> fields are <em>analyzed</em> - &#8220;Wireless Headphones&#8221; becomes tokens [&#8221;wireless&#8221;, &#8220;headphones&#8221;]</p></li><li><p><code>keyword</code> fields are <em>exact</em> - &#8220;electronics&#8221; stays &#8220;electronics&#8221;</p></li></ul><div><hr></div><h2>Step 3: Index Documents</h2><h3>Single Document</h3><p>bash</p><pre><code><code>curl -X POST "https://localhost:9200/products/_doc" \
  -H "Content-Type: application/json" \
  -u admin:YourPassword -k -d '{
    "name": "Wireless Headphones",
    "price": 149.99,
    "category": "electronics",
    "description": "Premium noise-canceling wireless headphones with 30-hour battery",
    "in_stock": true,
    "created_at": "2025-01-14"
  }'</code></code></pre><p>Response:</p><pre><code><code>{
  "_index": "products",
  "_id": "abc123xyz",    // Auto-generated
  "_version": 1,
  "result": "created",
  "_shards": {
    "total": 2,
    "successful": 1
  }
}</code></code></pre><h3>Bulk Ingestion (10-100x Faster)</h3><p>For multiple documents, use the <code>_bulk</code> API:</p><pre><code><code>curl -X POST "https://localhost:9200/_bulk" \
  -H "Content-Type: application/x-ndjson" \
  -u admin:YourPassword -k -d '
{"index": {"_index": "products"}}
{"name": "Laptop Stand", "price": 49.99, "category": "accessories"}
{"index": {"_index": "products"}}
{"name": "USB-C Hub", "price": 79.99, "category": "accessories"}
{"index": {"_index": "products"}}
{"name": "Mechanical Keyboard", "price": 159.99, "category": "electronics"}
'</code></code></pre><p><strong>Why bulk is faster:</strong></p><ul><li><p>Single network round-trip instead of N</p></li><li><p>Batches writes to Lucene</p></li><li><p>Reduces per-request overhead</p></li></ul><p><strong>Best practices:</strong></p><ul><li><p>Batch 1,000-5,000 documents per request</p></li><li><p>Don&#8217;t exceed ~100MB per request</p></li><li><p>Use newline-delimited JSON (note the trailing newline!)</p></li></ul><div><hr></div><h2>Step 4: Search Your Data</h2><p>Now the fun part - actually searching.</p><h3>Basic Match Query</h3><pre><code><code>curl -X GET "https://localhost:9200/products/_search" \
  -H "Content-Type: application/json" \
  -u admin:YourPassword -k -d '{
    "query": {
      "match": {
        "description": "wireless headphones"
      }
    }
  }'</code></code></pre><p>This searches the <code>description</code> field for documents containing &#8220;wireless&#8221; OR &#8220;headphones&#8221; (or both).</p><h3>Understanding the Response</h3><pre><code><code>{
  "took": 5,                          // Query time in milliseconds
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1
  },
  "hits": {
    "total": {
      "value": 1,
      "relation": "eq"                // "eq" = exact, "gte" = at least
    },
    "max_score": 2.8567,              // Highest relevance score
    "hits": [
      {
        "_index": "products",
        "_id": "abc123",
        "_score": 2.8567,             // This document's relevance
        "_source": {                   // The actual document
          "name": "Wireless Headphones",
          "price": 149.99,
          "description": "Premium noise-canceling wireless headphones"
        }
      }
    ]
  }
}</code></code></pre><h3>Query Types Cheat Sheet</h3><p><strong>Match Query</strong> - Full-text search with analysis</p><pre><code><code>{"query": {"match": {"description": "wireless bluetooth"}}}</code></code></pre><p>Use for: Product descriptions, article content, user-generated text</p><p><strong>Term Query</strong> - Exact value matching (no analysis)</p><pre><code><code>{"query": {"term": {"category": "electronics"}}}</code></code></pre><p>Use for: Categories, status fields, IDs</p><p><strong>Range Query</strong> - Numeric/date ranges</p><pre><code><code>{"query": {"range": {"price": {"gte": 50, "lte": 200}}}}</code></code></pre><p>Use for: Price filters, date ranges</p><p><strong>Bool Query</strong> - Combine multiple conditions</p><pre><code><code>{
  "query": {
    "bool": {
      "must": [
        {"match": {"description": "wireless"}}
      ],
      "filter": [
        {"term": {"category": "electronics"}},
        {"range": {"price": {"lte": 200}}}
      ]
    }
  }
}</code></code></pre><p><strong>Important:</strong> Use <code>filter</code> for conditions that don&#8217;t affect relevance score - they&#8217;re cached and much faster!</p><div><hr></div><h2>Key Concepts Explained</h2><h3>What is a Cluster?</h3><p>A <strong>cluster</strong> is a collection of nodes working together. It:</p><ul><li><p>Distributes data across nodes automatically</p></li><li><p>Provides fault tolerance (if one node fails, others continue)</p></li><li><p>Scales horizontally by adding more nodes</p></li></ul><h3>What is a Node?</h3><p>A <strong>node</strong> is a single OpenSearch server instance. Types:</p><ul><li><p><strong>Cluster Manager</strong>: Coordinates the cluster</p></li><li><p><strong>Data Node</strong>: Stores data, executes searches</p></li><li><p><strong>Ingest Node</strong>: Pre-processes documents</p></li><li><p><strong>Coordinating Node</strong>: Routes requests (all nodes can do this)</p></li></ul><h3>What is an Index?</h3><p>An <strong>index</strong> is a collection of documents with similar characteristics. It:</p><ul><li><p>Has a mapping defining field types</p></li><li><p>Is split into shards for distribution</p></li><li><p>Can have aliases for easier management</p></li></ul><h3>What is a Document?</h3><p>A <strong>document</strong> is a JSON object - the basic unit of information:</p><pre><code><code>{
  "name": "Wireless Headphones",
  "price": 149.99,
  "_id": "abc123"      // Metadata
}</code></code></pre><h3>What is a Shard?</h3><p>A <strong>shard</strong> is a subset of an index:</p><ul><li><p><strong>Primary shard</strong>: Original data</p></li><li><p><strong>Replica shard</strong>: Copy for redundancy</p></li></ul><p>Shards enable:</p><ul><li><p>Parallel processing across nodes</p></li><li><p>High availability (replicas on different nodes)</p></li><li><p>Horizontal scaling</p></li></ul><h3>What is a Mapping?</h3><p>A <strong>mapping</strong> defines how fields are stored and indexed:</p><pre><code><code>{
  "properties": {
    "name": {"type": "text"},        // Analyzed for full-text search
    "category": {"type": "keyword"}  // Not analyzed, exact match
  }
}</code></code></pre><div><hr></div><h2>Public Datasets to Practice With</h2><p>Want to practice with real data? Here are excellent options:</p><h3>1. OpenSearch Sample Data (Easiest)</h3><p>Built into OpenSearch Dashboards. Go to Home &#8594; Add sample data.</p><ul><li><p>eCommerce orders</p></li><li><p>Flight data</p></li><li><p>Web logs</p></li></ul><h3>2. Kaggle E-commerce Dataset</h3><p>500K+ retail transactions. Perfect for:</p><ul><li><p>Product search</p></li><li><p>Customer analytics</p></li><li><p>Aggregations practice</p></li></ul><h3>3. Wikipedia Dumps</h3><p>Full-text articles in various sizes. Perfect for:</p><ul><li><p>Large-scale indexing</p></li><li><p>Full-text search testing</p></li><li><p>NLP experiments</p></li></ul><h3>4. NYC Taxi Data</h3><p>Trip records with geo coordinates. Perfect for:</p><ul><li><p>Geo-spatial queries</p></li><li><p>Time-series analysis</p></li><li><p>Dashboard building</p></li></ul><div><hr></div><h2>Cloud Considerations</h2><h3>AWS OpenSearch Service</h3><p>When you create a domain in AWS:</p><ul><li><p>Endpoint: </p></li></ul><p>https://your-domain.region.es.amazonaws.com</p><ul><li><p>Auth: IAM roles or fine-grained access control</p></li><li><p>VPC: Can be in VPC or public (with IP restrictions)</p></li></ul><pre><code><code># With IAM auth (need AWS CLI configured)
aws opensearch-serverless query \
  --endpoint https://your-domain.region.es.amazonaws.com \
  --query '{"query": {"match_all": {}}}'</code></code></pre><h3>Alibaba Cloud OpenSearch</h3><p>Similar managed service with:</p><ul><li><p>Auto-scaling capabilities</p></li><li><p>Built-in machine learning features</p></li><li><p>Different pricing model (pay per query unit)</p></li></ul><div><hr></div><h2>Today&#8217;s Mini-Project: Product Catalog Search</h2><ol><li><p>Create a <code>products</code> index with proper mapping</p></li><li><p>Bulk index at least 10 products</p></li><li><p>Implement these searches:</p><ul><li><p>Full-text search on product names/descriptions</p></li><li><p>Filter by category (keyword)</p></li><li><p>Price range filter</p></li><li><p>Combined search with bool query</p></li></ul></li></ol><p></p><div><hr></div><p><strong>Interactive Visual Guide:</strong> <a href="https://opensearch.9cld.com/day/03-first-conversation">opensearch.9cld.com/day/03-first-conversation</a></p><div><hr></div><h2>What&#8217;s Next (Day 4)</h2><p>Tomorrow we&#8217;ll dive into <strong>mappings and analyzers</strong> - the secret sauce behind great search. You&#8217;ll learn:</p><ul><li><p>Why does the same search return different results with different mappings</p></li><li><p>How analyzers transform text into searchable tokens</p></li><li><p>Building custom analyzers for your use case</p><p></p></li></ul><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://9cld.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://9cld.com/subscribe?"><span>Subscribe now</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[Day 2: Running OpenSearch Locally]]></title><description><![CDATA[From &#8220;docker run&#8221; to production-ready configuration in one guide]]></description><link>https://9cld.com/p/day-2-running-opensearch-locally</link><guid isPermaLink="false">https://9cld.com/p/day-2-running-opensearch-locally</guid><dc:creator><![CDATA[Ankit Mehta]]></dc:creator><pubDate>Sun, 11 Jan 2026 21:40:16 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!gnSc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4443d69a-3744-4d04-a8f3-735edf9943ad_1200x630.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gnSc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4443d69a-3744-4d04-a8f3-735edf9943ad_1200x630.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gnSc!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4443d69a-3744-4d04-a8f3-735edf9943ad_1200x630.png 424w, https://substackcdn.com/image/fetch/$s_!gnSc!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4443d69a-3744-4d04-a8f3-735edf9943ad_1200x630.png 848w, https://substackcdn.com/image/fetch/$s_!gnSc!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4443d69a-3744-4d04-a8f3-735edf9943ad_1200x630.png 1272w, https://substackcdn.com/image/fetch/$s_!gnSc!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4443d69a-3744-4d04-a8f3-735edf9943ad_1200x630.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gnSc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4443d69a-3744-4d04-a8f3-735edf9943ad_1200x630.png" width="1200" height="630" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4443d69a-3744-4d04-a8f3-735edf9943ad_1200x630.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:630,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:318461,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://9cld.com/i/184248512?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4443d69a-3744-4d04-a8f3-735edf9943ad_1200x630.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!gnSc!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4443d69a-3744-4d04-a8f3-735edf9943ad_1200x630.png 424w, https://substackcdn.com/image/fetch/$s_!gnSc!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4443d69a-3744-4d04-a8f3-735edf9943ad_1200x630.png 848w, https://substackcdn.com/image/fetch/$s_!gnSc!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4443d69a-3744-4d04-a8f3-735edf9943ad_1200x630.png 1272w, https://substackcdn.com/image/fetch/$s_!gnSc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4443d69a-3744-4d04-a8f3-735edf9943ad_1200x630.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><h2>The Problem Nobody Warns You About</h2><p>You found a Docker command online. You ran it. OpenSearch started. Victory?</p><p>Not quite.</p><p>Three days later, you&#8217;re debugging why your cluster won&#8217;t accept connections, why security is disabled (or worse, why it&#8217;s enabled and you can&#8217;t authenticate), and why your laptop sounds like a jet engine.</p><p>The gap between &#8220;OpenSearch is running&#8221; and &#8220;OpenSearch is running correctly&#8221; is where most beginners get stuck.</p><p>Today, we bridge that gap.</p><div><hr></div><h2>What We are Building</h2><p>By the end of this guide, you will have:</p><ul><li><p>A single-node OpenSearch 3.0.0 cluster running in Docker</p></li><li><p>A multi-container setup with Docker Compose (OpenSearch + Dashboards)</p></li><li><p>Deep understanding of security parameters (and when to disable them safely)</p></li><li><p>Performance tuning that won&#8217;t melt your laptop</p></li><li><p>A reusable configuration you can version control</p></li></ul><p>Let&#8217;s start from first principles.</p><div><hr></div><h2>Part 1: Docker Basics for OpenSearch</h2><h3>Why Docker?</h3><p>Before we run commands, let&#8217;s understand why Docker matters for OpenSearch:</p><p><strong>Without Docker:</strong></p><ul><li><p>Install Java, set JAVA_HOME</p></li><li><p>Download, extract, configure manually</p></li><li><p>Manage multiple versions manually</p></li><li><p>&#8220;Works on my machine&#8221; problems</p></li><li><p>Cleanup is messy</p></li></ul><p><strong>With Docker:</strong></p><ul><li><p>One command to run</p></li><li><p>Switch versions with a tag</p></li><li><p>Identical everywhere</p></li><li><p><code>docker rm</code> and done</p></li></ul><p>Docker gives you <strong>isolation</strong> and <strong>reproducibility</strong>. Your OpenSearch environment is the same whether you&#8217;re on macOS, Windows, or Linux.</p><h3>The Simplest Possible Command</h3><p>Here&#8217;s the absolute minimum to get OpenSearch 3.0.0 running:</p><pre><code><code>docker run -d \
  --name opensearch-node \
  -p 9200:9200 \
  -p 9600:9600 \
  -e "discovery.type=single-node" \
  -e "OPENSEARCH_INITIAL_ADMIN_PASSWORD=MyStr0ng#Pass!" \
  opensearchproject/opensearch:latest
</code></code></pre><p>Let&#8217;s decode every piece:</p><p>-d Run in background (detached mode)</p><p>--name opensearch-node Give the container a memorable name</p><p>-p 9200:9200 Expose REST API port (host:container)</p><p>-p 9600:9600 Expose Performance Analyzer port</p><p>discovery.type=single-node Tell OpenSearch this is a solo node, not a cluster</p><p>OPENSEARCH_INITIAL_ADMIN_PASSWORD Set the admin password (required since OpenSearch 2.12)</p><h3>Verify It&#8217;s Working</h3><pre><code><code># Check container is running
docker ps

# Check OpenSearch is responding
curl -k -u admin:MyStr0ng#Pass! https://localhost:9200
</code></code></pre><p>The <code>-k</code> flag tells curl to accept the self-signed SSL certificate.</p><p>Expected response:</p><pre><code><code>{
  "name" : "opensearch-node",
  "cluster_name" : "docker-cluster",
  "cluster_uuid" : "...",
  "version" : {
    "distribution" : "opensearch",
    "number" : "3.4.0",
    ...
  },
  "tagline" : "The OpenSearch Project: https://opensearch.org/"
}
</code></code></pre><p><strong>Congratulations &#8212; OpenSearch 3.4.0 is running.</strong></p><p>But we are just getting started.</p><div><hr></div><h2>Part 2: Understanding Security Parameters</h2><p>OpenSearch&#8217;s security plugin is powerful but can be confusing for beginners. Let&#8217;s demystify it.</p><h3>The Security Plugin: What It Does</h3><p>The security plugin handles:</p><ul><li><p><strong>Authentication</strong>: Who are you? (username/password, certificates, SAML, OIDC)</p></li><li><p><strong>Authorization</strong>: What can you do? (roles, permissions, tenants)</p></li><li><p><strong>Encryption</strong>: TLS/SSL for data in transit</p></li><li><p><strong>Audit logging</strong>: Who did what, when?</p></li></ul><p>By default, security is <strong>enabled</strong>. This is good for production. For local development, it can be friction.</p><h3>Option A: Keep Security Enabled (Recommended)</h3><p>For local development with security:</p><pre><code><code>docker run -d \
  --name opensearch-secure \
  -p 9200:9200 \
  -e "discovery.type=single-node" \
  -e "OPENSEARCH_INITIAL_ADMIN_PASSWORD=Dev#Pass123!" \
  opensearchproject/opensearch:latest
</code></code></pre><p><strong>Password Requirements (OpenSearch 2.12+):</strong></p><ul><li><p>Minimum 8 characters</p></li><li><p>At least one uppercase letter</p></li><li><p>At least one lowercase letter</p></li><li><p>At least one digit</p></li><li><p>At least one special character</p></li></ul><p><strong>Connecting with security enabled:</strong></p><pre><code><code># With curl
curl -k -u admin:Dev#Pass123! https://localhost:9200

# From Python
from opensearchpy import OpenSearch

client = OpenSearch(
    hosts=[{'host': 'localhost', 'port': 9200}],
    http_auth=('admin', 'Dev#Pass123!'),
    use_ssl=True,
    verify_certs=False  # For self-signed certs
)
</code></code></pre><h3>Option B: Disable Security (Development Only)</h3><p>For rapid prototyping, where you need zero friction:</p><pre><code><code>docker run -d \
  --name opensearch-insecure \
  -p 9200:9200 \
  -e "discovery.type=single-node" \
  -e "DISABLE_SECURITY_PLUGIN=true" \
  opensearchproject/opensearch:latest
</code></code></pre><p><strong>Connecting without security:</strong></p><pre><code><code># Simple curl - no auth, no SSL
curl http://localhost:9200

# From Python
client = OpenSearch(
    hosts=[{'host': 'localhost', 'port': 9200}],
    use_ssl=False
)
</code></code></pre><h3>When to Disable Security</h3><p><strong>OK to Disable:</strong></p><ul><li><p>Quick local experiments</p></li><li><p>Learning and tutorials</p></li><li><p>Throwaway demos</p></li><li><p>CI/CD pipeline tests</p></li></ul><p><strong>Keep Security ON:</strong></p><ul><li><p>Any shared environment</p></li><li><p>Development servers others access</p></li><li><p>Pre-production testing</p></li><li><p>Anything with real data</p></li></ul><p><strong>The Rule</strong>: If anyone other than you can access it, security stays ON.</p><h3>Security Environment Variables Reference</h3><p><code>OPENSEARCH_INITIAL_ADMIN_PASSWORD</code> Sets admin password on first run. Required if security is enabled.</p><p><code>DISABLE_SECURITY_PLUGIN</code> Turns off security entirely. Default is <code>false</code>.</p><p><code>DISABLE_INSTALL_DEMO_CONFIG</code> Skip demo certificates. Default is <code>false</code>.</p><p><code>plugins.security.ssl.http.enabled</code> Enable HTTPS. Default is <code>true</code>.</p><p><code>plugins.security.disabled</code> Another way to disable security. Default is <code>false</code>.</p><div><hr></div><h2>Part 3: Performance Parameters That Actually Matter</h2><p>Running OpenSearch on a laptop is different from running it in production. Here&#8217;s how to tune it for local development.</p><h3>The JVM Heap: Most Important Setting</h3><p>OpenSearch runs on the JVM. The heap size determines how much memory it can use.</p><p><strong>The Rule</strong>: Set heap to <strong>50% of available RAM</strong>, but <strong>never more than 32GB</strong> (due to compressed pointers).</p><p>For local development on a 16GB laptop:</p><pre><code><code>docker run -d \
  --name opensearch-tuned \
  -p 9200:9200 \
  -e "discovery.type=single-node" \
  -e "OPENSEARCH_INITIAL_ADMIN_PASSWORD=Dev#Pass123!" \
  -e "OPENSEARCH_JAVA_OPTS=-Xms2g -Xmx2g" \
  opensearchproject/opensearch:latest
</code></code></pre><p><strong>Recommended heap sizes by laptop RAM:</strong></p><p><strong>8GB RAM</strong> Recommended heap: 1-2GB Setting: <code>-Xms1g -Xmx1g</code></p><p><strong>16GB RAM</strong> Recommended heap: 2-4GB Setting: <code>-Xms2g -Xmx2g</code></p><p><strong>32GB RAM</strong> Recommended heap: 4-8GB Setting: <code>-Xms4g -Xmx4g</code></p><p><strong>Critical</strong>: Always set <code>-Xms</code> and <code>-Xmx</code> to the <strong>same value</strong>. This prevents expensive heap resizing at runtime.</p><h3>Memory Lock: Prevent Swapping</h3><p>Swapping is death for search performance. Lock the heap in RAM:</p><pre><code><code>docker run -d \
  --name opensearch-locked \
  -p 9200:9200 \
  --ulimit memlock=-1:-1 \
  -e "discovery.type=single-node" \
  -e "OPENSEARCH_INITIAL_ADMIN_PASSWORD=Dev#Pass123!" \
  -e "bootstrap.memory_lock=true" \
  opensearchproject/opensearch:latest
</code></code></pre><h3>Virtual Memory: The mmap Issue</h3><p>OpenSearch uses memory-mapped files. Linux has a default limit that&#8217;s too low.</p><p>On your <strong>host machine</strong> (not in Docker), run:</p><pre><code><code># Temporary (until reboot)
sudo sysctl -w vm.max_map_count=262144

# Permanent (add to /etc/sysctl.conf)
echo "vm.max_map_count=262144" | sudo tee -a /etc/sysctl.conf
</code></code></pre><p><strong>On macOS</strong>: Docker Desktop handles this automatically.</p><p><strong>On Windows with WSL2</strong>: Run in WSL terminal:</p><pre><code><code>wsl -d docker-desktop
sysctl -w vm.max_map_count=262144
</code></code></pre><h3>Performance Environment Variables Reference</h3><p><code>OPENSEARCH_JAVA_OPTS</code> JVM heap settings. Recommended local value: <code>-Xms2g -Xmx2g</code></p><p><code>bootstrap.memory_lock</code> Prevent swapping. Recommended: <code>true</code></p><p><code>cluster.routing.allocation.disk.threshold_enabled</code> Disk watermarks. Default: <code>true</code></p><p><code>indices.memory.index_buffer_size</code> Indexing buffer. Default: <code>10%</code></p><div><hr></div><h2>Part 4: Docker Compose &#8212; The Right Way</h2><p>Running multiple containers manually is tedious. Docker Compose defines your entire stack in one file.</p><h3>Basic docker-compose.yml</h3><pre><code><code>version: '3.8'

services:
  opensearch:
    image: opensearchproject/opensearch:latest
    container_name: opensearch
    environment:
      - discovery.type=single-node
      - bootstrap.memory_lock=true
      - OPENSEARCH_JAVA_OPTS=-Xms2g -Xmx2g
      - OPENSEARCH_INITIAL_ADMIN_PASSWORD=Dev#Pass123!
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 65536
        hard: 65536
    volumes:
      - opensearch-data:/usr/share/opensearch/data
    ports:
      - "9200:9200"
      - "9600:9600"
    networks:
      - opensearch-net

  dashboards:
    image: opensearchproject/opensearch-dashboards:latest
    container_name: opensearch-dashboards
    environment:
      - OPENSEARCH_HOSTS=["https://opensearch:9200"]
    ports:
      - "5601:5601"
    networks:
      - opensearch-net
    depends_on:
      - opensearch

volumes:
  opensearch-data:

networks:
  opensearch-net:
</code></code></pre><h3>What Each Section Does</h3><p><strong>services</strong> Defines each container &#8212; opensearch is the search engine, dashboards is the web UI (like Kibana)</p><p><strong>environment</strong> Configuration via environment variables</p><p><strong>ulimits</strong> System limits for memory lock and file descriptors</p><p><strong>volumes</strong> Persist data across container restarts &#8212; without this, you lose all data when the container stops</p><p><strong>ports</strong> Expose to host machine</p><p><strong>networks</strong> Containers on the same network can talk to each other by name</p><p><strong>depends_on</strong> Start order &#8212; dashboards waits for opensearch</p><h3>Running Docker Compose</h3><pre><code><code># Start everything
docker compose up -d

# View logs
docker compose logs -f

# Stop everything
docker compose down

# Stop and delete data
docker compose down -v
</code></code></pre><h3>Development vs Production Compose Files</h3><p>For development without security:</p><pre><code><code># docker-compose.dev.yml
version: '3.8'

services:
  opensearch:
    image: opensearchproject/opensearch:latest
    container_name: opensearch-dev
    environment:
      - discovery.type=single-node
      - bootstrap.memory_lock=true
      - OPENSEARCH_JAVA_OPTS=-Xms1g -Xmx1g
      - DISABLE_SECURITY_PLUGIN=true
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - opensearch-data:/usr/share/opensearch/data
    ports:
      - "9200:9200"
    networks:
      - opensearch-net

  dashboards:
    image: opensearchproject/opensearch-dashboards:latest
    container_name: dashboards-dev
    environment:
      - OPENSEARCH_HOSTS=["http://opensearch:9200"]
      - DISABLE_SECURITY_DASHBOARDS_PLUGIN=true
    ports:
      - "5601:5601"
    networks:
      - opensearch-net
    depends_on:
      - opensearch

volumes:
  opensearch-data:

networks:
  opensearch-net:
</code></code></pre><p>Run with:</p><pre><code><code>docker compose -f docker-compose.dev.yml up -d
</code></code></pre><div><hr></div><h2>Part 5: Multi-Node Cluster (Bonus)</h2><p>For testing cluster behavior locally:</p><pre><code><code># docker-compose.cluster.yml
version: '3.8'

services:
  opensearch-node1:
    image: opensearchproject/opensearch:latest
    container_name: opensearch-node1
    environment:
      - cluster.name=opensearch-cluster
      - node.name=opensearch-node1
      - discovery.seed_hosts=opensearch-node1,opensearch-node2
      - cluster.initial_cluster_manager_nodes=opensearch-node1,opensearch-node2
      - bootstrap.memory_lock=true
      - OPENSEARCH_JAVA_OPTS=-Xms1g -Xmx1g
      - OPENSEARCH_INITIAL_ADMIN_PASSWORD=Cluster#Pass123!
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - opensearch-data1:/usr/share/opensearch/data
    ports:
      - "9200:9200"
      - "9600:9600"
    networks:
      - opensearch-net

  opensearch-node2:
    image: opensearchproject/opensearch:latest
    container_name: opensearch-node2
    environment:
      - cluster.name=opensearch-cluster
      - node.name=opensearch-node2
      - discovery.seed_hosts=opensearch-node1,opensearch-node2
      - cluster.initial_cluster_manager_nodes=opensearch-node1,opensearch-node2
      - bootstrap.memory_lock=true
      - OPENSEARCH_JAVA_OPTS=-Xms1g -Xmx1g
      - OPENSEARCH_INITIAL_ADMIN_PASSWORD=Cluster#Pass123!
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - opensearch-data2:/usr/share/opensearch/data
    networks:
      - opensearch-net

  dashboards:
    image: opensearchproject/opensearch-dashboards:3.0.0
    container_name: opensearch-dashboards
    environment:
      - OPENSEARCH_HOSTS=["https://opensearch-node1:9200","https://opensearch-node2:9200"]
    ports:
      - "5601:5601"
    networks:
      - opensearch-net
    depends_on:
      - opensearch-node1
      - opensearch-node2

volumes:
  opensearch-data1:
  opensearch-data2:

networks:
  opensearch-net:
</code></code></pre><p><strong>Key differences from single-node:</strong></p><p><strong>Single Node:</strong> Uses <code>discovery.type=single-node</code>, no cluster manager config, one volume, 2GB heap minimum</p><p><strong>Multi-Node Cluster:</strong> Uses <code>discovery.seed_hosts=...</code>, requires <code>cluster.initial_cluster_manager_nodes=...</code>, volume per node, 1GB per node minimum</p><div><hr></div><h2>Part 6: Troubleshooting Common Issues</h2><h3>&#8220;OpenSearch didn&#8217;t start&#8221;</h3><pre><code><code># Check container logs
docker logs opensearch-node

# Common fixes:
# 1. vm.max_map_count too low
sudo sysctl -w vm.max_map_count=262144

# 2. Not enough memory
# Reduce heap: -Xms512m -Xmx512m

# 3. Port already in use
docker ps -a  # Find conflicting container
lsof -i :9200  # Find process using port
</code></code></pre><h3>&#8220;Connection refused&#8221;</h3><pre><code><code># Is the container running?
docker ps

# Is OpenSearch ready? (check for "green" or "yellow")
curl -k -u admin:Dev#Pass123! https://localhost:9200/_cluster/health

# Check if port is exposed
docker port opensearch-node
</code></code></pre><h3>&#8220;Authentication failed&#8221;</h3><pre><code><code># Did you set the password correctly?
# Password must meet complexity requirements

# For fresh start (deletes all data):
docker rm -f opensearch-node
docker volume rm opensearch-data
# Then re-run docker run command
</code></code></pre><h3>&#8220;Dashboards can&#8217;t connect to OpenSearch&#8221;</h3><pre><code><code># Are they on the same network?
docker network inspect opensearch-net

# Is the URL correct?
# Use container name, not localhost:
# &#9989; OPENSEARCH_HOSTS=["https://opensearch:9200"]
# &#10060; OPENSEARCH_HOSTS=["https://localhost:9200"]
</code></code></pre><div><hr></div><h2>Quick Reference Card</h2><h3>Essential Commands</h3><pre><code><code># Start single node (OpenSearch 3.0.0)
docker run -d --name os -p 9200:9200 \
  -e "discovery.type=single-node" \
  -e "OPENSEARCH_INITIAL_ADMIN_PASSWORD=Pass#123" \
  opensearchproject/opensearch:latest

# Check health
curl -k -u admin:Pass#123 https://localhost:9200/_cluster/health?pretty

# Stop and remove
docker rm -f os

# Docker Compose
docker compose up -d
docker compose logs -f opensearch
docker compose down -v
</code></code></pre><h3>Environment Variables Cheat Sheet</h3><p><code>discovery.type=single-node</code> Solo node mode</p><p><code>OPENSEARCH_INITIAL_ADMIN_PASSWORD</code> Admin password (required)</p><p><code>DISABLE_SECURITY_PLUGIN=true</code> No security (dev only)</p><p><code>OPENSEARCH_JAVA_OPTS=-Xms2g -Xmx2g</code> Heap size</p><p><code>bootstrap.memory_lock=true</code> Prevent swapping</p><div><hr></div><h2>What&#8217;s Next: Day 3</h2><p>Tomorrow we dive into <strong>indexing your first document</strong> &#8212; the actual reason OpenSearch exists.</p><p>We&#8217;ll cover:</p><ul><li><p>Creating your first index</p></li><li><p>Understanding mappings</p></li><li><p>The document lifecycle (create, read, update, delete)</p></li><li><p>Bulk operations for real-world data</p></li></ul><p>You now have a running, properly configured OpenSearch 3.0.0 environment. Time to put data in it.</p><div><hr></div><p><em>Day 2 of 60 | OpenSearch Zero to Hero</em></p><p><strong>Resources:</strong></p><ul><li><p><a href="https://opensearch.org/docs/latest/install-and-configure/install-opensearch/docker/">OpenSearch Docker Documentation</a></p></li><li><p><a href="https://github.com/9cld/opensearch-guide">GitHub: Day 2 Code</a></p></li><li><p><a href="https://opensearch.9cld.com/">Interactive Guide: opensearch.9cld.com</a></p></li></ul><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://9cld.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">9Cloud is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Stop Optimizing Your Code. Fix the Route.]]></title><description><![CDATA[Why a 15-minute network change beat weeks of performance tuning]]></description><link>https://9cld.com/p/how-alibaba-cloud-global-accelerator</link><guid isPermaLink="false">https://9cld.com/p/how-alibaba-cloud-global-accelerator</guid><dc:creator><![CDATA[Bhavesh Panchal]]></dc:creator><pubDate>Thu, 08 Jan 2026 12:19:56 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!YGi2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6723ee8-7e2a-4d2a-9f0b-c55298741caf_1200x630.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!YGi2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6723ee8-7e2a-4d2a-9f0b-c55298741caf_1200x630.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!YGi2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6723ee8-7e2a-4d2a-9f0b-c55298741caf_1200x630.png 424w, https://substackcdn.com/image/fetch/$s_!YGi2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6723ee8-7e2a-4d2a-9f0b-c55298741caf_1200x630.png 848w, https://substackcdn.com/image/fetch/$s_!YGi2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6723ee8-7e2a-4d2a-9f0b-c55298741caf_1200x630.png 1272w, https://substackcdn.com/image/fetch/$s_!YGi2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6723ee8-7e2a-4d2a-9f0b-c55298741caf_1200x630.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!YGi2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6723ee8-7e2a-4d2a-9f0b-c55298741caf_1200x630.png" width="1200" height="630" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e6723ee8-7e2a-4d2a-9f0b-c55298741caf_1200x630.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:630,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:312070,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://9cld.com/i/183891684?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6723ee8-7e2a-4d2a-9f0b-c55298741caf_1200x630.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!YGi2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6723ee8-7e2a-4d2a-9f0b-c55298741caf_1200x630.png 424w, https://substackcdn.com/image/fetch/$s_!YGi2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6723ee8-7e2a-4d2a-9f0b-c55298741caf_1200x630.png 848w, https://substackcdn.com/image/fetch/$s_!YGi2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6723ee8-7e2a-4d2a-9f0b-c55298741caf_1200x630.png 1272w, https://substackcdn.com/image/fetch/$s_!YGi2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6723ee8-7e2a-4d2a-9f0b-c55298741caf_1200x630.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>A SaaS team asked me to solve a latency problem.</p><p>Backend in Tokyo. Users in Jakarta and Melbourne are churning. They spent weeks on CDN tuning, database optimization, and frontend rewrites.</p><p>Nothing worked.</p><p>I looked at their traceroutes. A request from Sydney was not going to Tokyo. It was bouncing through Hong Kong, hitting a congested peering point in LA, then looping back across the Pacific.</p><p>Their code was fine. The internet was betraying them.</p><div><hr></div><h2>The Fix Nobody Tries First</h2><p>We enabled Alibaba Cloud Global Accelerator.</p><p>Instead of riding the public internet, user traffic now enters Alibaba&#8217;s private backbone at the nearest edge&#8212;then travels direct to origin.</p><p><strong>24 hours later:</strong></p><p>Seoul: 280ms &#8594; 110ms <br>Sydney: 350ms &#8594; 130ms <br>Jakarta: 210ms &#8594; 95ms</p><p>Timeouts dropped 90%. Zero code changes.</p><div><hr></div><h2>Why This Works</h2><p>Public internet routing optimizes for cost, not speed. Your packets take the cheapest path, not the fastest.</p><p>Global Accelerator bypasses that entirely. Users connect to a nearby edge location. From there, traffic moves over private fiber&#8212;predictable, uncongested, direct.</p><p>Same destination. Different highway.</p><div><hr></div><h2>Setup Takes 15 Minutes</h2><ol><li><p>Create accelerator in Alibaba Cloud Console</p></li><li><p>Get your assigned global IP</p></li><li><p>Point your domain to that IP</p></li><li><p>Attach your backend endpoint</p></li></ol><p>Cost: ~$0.08/GB transferred. No hourly fees.</p><div><hr></div><h2>The Trap I Fell Into</h2><p>First deployment, latency dropped beautifully. Then a traffic spike hit, and errors spiked with it.</p><p>The backend had become CPU-bound.</p><p>Global Accelerator speeds up the network. Not your server.</p><p>Faster delivery means faster queuing if your origin can&#8217;t keep up. Pair this with auto-scaling, or you will just expose bottlenecks sooner.</p><div><hr></div><h2>When It Makes Sense</h2><p><strong>Use it for:</strong> APIs, dashboards, real-time apps, anything interactive across distributed users</p><p><strong>Skip it for:</strong> Static assets (use CDN), single-region users, batch workloads</p><div><hr></div><h2>One Thing to Try</h2><p>Measure your current latency from multiple regions using <a href="https://ping.pe">Ping.pe</a>.</p><p>If you see 200ms+ gaps between locations, the problem probably isn&#8217;t your code.</p><p>It&#8217;s the route.</p>]]></content:encoded></item><item><title><![CDATA[OpenSearch for Beginners: The Visual Guide That Official Docs Don’t Give You]]></title><description><![CDATA[Why this search engine exists, what problems it actually solves, and when you should (and shouldn&#8217;t) use it.]]></description><link>https://9cld.com/p/opensearch-for-beginners-the-visual</link><guid isPermaLink="false">https://9cld.com/p/opensearch-for-beginners-the-visual</guid><dc:creator><![CDATA[Ankit Mehta]]></dc:creator><pubDate>Wed, 07 Jan 2026 07:34:23 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!xkHf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F737511be-9add-4dea-b505-273bbc0448bc_1200x630.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!xkHf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F737511be-9add-4dea-b505-273bbc0448bc_1200x630.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!xkHf!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F737511be-9add-4dea-b505-273bbc0448bc_1200x630.png 424w, https://substackcdn.com/image/fetch/$s_!xkHf!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F737511be-9add-4dea-b505-273bbc0448bc_1200x630.png 848w, https://substackcdn.com/image/fetch/$s_!xkHf!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F737511be-9add-4dea-b505-273bbc0448bc_1200x630.png 1272w, https://substackcdn.com/image/fetch/$s_!xkHf!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F737511be-9add-4dea-b505-273bbc0448bc_1200x630.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!xkHf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F737511be-9add-4dea-b505-273bbc0448bc_1200x630.png" width="1200" height="630" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/737511be-9add-4dea-b505-273bbc0448bc_1200x630.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:630,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:108956,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://9cld.com/i/183765179?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F737511be-9add-4dea-b505-273bbc0448bc_1200x630.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!xkHf!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F737511be-9add-4dea-b505-273bbc0448bc_1200x630.png 424w, https://substackcdn.com/image/fetch/$s_!xkHf!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F737511be-9add-4dea-b505-273bbc0448bc_1200x630.png 848w, https://substackcdn.com/image/fetch/$s_!xkHf!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F737511be-9add-4dea-b505-273bbc0448bc_1200x630.png 1272w, https://substackcdn.com/image/fetch/$s_!xkHf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F737511be-9add-4dea-b505-273bbc0448bc_1200x630.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>I remember the first time I tried to learn OpenSearch.</p><p>I went to the official documentation, and within 30 seconds, I was drowning in terms I didn&#8217;t understand terms like <em>shards</em>, <em>replicas</em>, <em>inverted indexes</em>, <em>analyzers</em>, <em>nodes</em>, and <em>clusters</em>.</p><p>The docs told me <strong>how</strong> to install it. They told me <strong>how</strong> to write queries. But they never told me <strong>why</strong> I should care in the first place.</p><p>If you are in that same boat, curious about OpenSearch but confused by the documentation, this guide is for you.</p><p>By the end, you will understand:</p><ul><li><p>The specific problem OpenSearch was built to solve</p></li><li><p>How it&#8217;s fundamentally different from a traditional database</p></li><li><p>When to use it (and when to absolutely avoid it)</p></li><li><p>The one concept that makes everything else click</p></li></ul><p>Let&#8217;s start with the problem.</p><div><hr></div><h2>The Problem: Why Traditional Databases Fail at Search</h2><p>Imagine you&#8217;re building an e-commerce site. You have a million products. A user searches for &#8220;iphone.&#8221;</p><p>With a traditional database like PostgreSQL, you&#8217;d write:</p><pre><code><code>SELECT * FROM products 
WHERE name LIKE '%iphone%'
</code></code></pre><p>This works. But here is the problem, the database has to scan <strong>every single row</strong> to find matches.</p><ul><li><p>1,000 products? Fast.</p></li><li><p>100,000 products? Noticeable lag.</p></li><li><p>10,000,000 products? Your users are waiting 5+ seconds.</p></li></ul><p>This is called a <strong>full table scan</strong>. It&#8217;s O(n) complexity&#8212;meaning the more data you have, the slower it gets. Linearly.</p><p>But it gets worse.</p><div><hr></div><h3>The Other Problems with Database Search</h3><p>Beyond raw speed, traditional databases have other search limitations:</p><p><strong>No relevance ranking.</strong> If a user searches &#8220;blue running shoes,&#8221; the database returns every product that matches&#8212;but in no particular order. The perfect match shows up on page 47.</p><p><strong>No typo tolerance.</strong> Search for &#8220;iphon&#8221; (missing the &#8216;e&#8217;)? Zero results. Your user thinks you don&#8217;t sell iPhones.</p><p><strong>No fuzzy matching.</strong> &#8220;iPhone,&#8221; &#8220;iphone,&#8221; &#8220;I-Phone,&#8221; and &#8220;i phone&#8221; are all treated as completely different searches.</p><p><strong>Expensive analytics.</strong> Want to count how many products are in each category? Or show price distributions? These aggregations are computationally expensive on traditional databases.</p><p>Search engines like OpenSearch were explicitly built to solve these problems.</p><div><hr></div><h2>The Solution: How OpenSearch Works Differently</h2><p>OpenSearch uses a data structure called an <strong>inverted index</strong>.</p><p>Instead of storing documents and then scanning through them to find words, OpenSearch flips the relationship: it stores <strong>words</strong> and points them to <strong>documents</strong>.</p><p>Here is a simple example:</p><p><strong>Traditional approach (Document &#8594; Words):</strong></p><pre><code><code>Document 1: "The quick brown fox"
Document 2: "The lazy dog"
Document 3: "Quick brown fox jumps"
</code></code></pre><p>To find &#8220;fox,&#8221; you scan all three documents.</p><p><strong>Inverted index approach (Words &#8594; Documents):</strong></p><pre><code><code>"quick" &#8594; [Document 1, Document 3]
"brown" &#8594; [Document 1, Document 3]
"fox"   &#8594; [Document 1, Document 3]
"lazy"  &#8594; [Document 2]
"dog"   &#8594; [Document 2]
</code></code></pre><p>Now, to find &#8220;fox,&#8221; you do <strong>one dictionary lookup</strong>: O(1).</p><p>It doesn&#8217;t matter if you have 10 documents or 10 billion. The lookup time is essentially the same.</p><div><hr></div><h3>The Trade-Off</h3><p>Here is the key insight that took me too long to understand:</p><blockquote><p><strong>OpenSearch trades write speed for read speed.</strong></p></blockquote><p>When you add a document to OpenSearch, it does extra work&#8212;analyzing the text, breaking it into tokens, and updating the inverted index. This is slower than a simple database insert.</p><p>But when you search? Instant. Because all that work was front-loaded.</p><p>This trade-off defines when OpenSearch is the right tool and when it&#8217;s the wrong one.</p><div><hr></div><h2>What OpenSearch Actually Is</h2><p>Let me give you the complete picture.</p><p><strong>OpenSearch is a distributed search and analytics engine built on Apache Lucene.</strong></p><p>Let&#8217;s break that down:</p><ul><li><p><strong>Distributed</strong>: It runs on multiple servers (called <em>nodes</em>) that work together as a <em>cluster</em>. This lets it scale horizontally.</p></li><li><p><strong>Search engine</strong>: Its primary job is finding relevant documents fast. It has built-in support for relevance scoring, fuzzy matching, and typo tolerance.</p></li><li><p><strong>Analytics engine</strong>: Beyond search, it excels at real-time aggregations&#8212;counting, summing, grouping data across millions of records.</p></li><li><p><strong>Apache Lucene</strong>: The low-level indexing library that powers the inverted index. OpenSearch (and Elasticsearch before it) are essentially Lucene with a distributed layer and REST API on top.</p></li></ul><div><hr></div><h3>The Origin Story</h3><p>OpenSearch is a fork of Elasticsearch.</p><p>In 2021, Elastic (the company behind Elasticsearch) changed its licensing from Apache 2.0 to a more restrictive model. AWS, along with the community, responded by forking the last open-source version (7.10.2) and continuing development under the Apache 2.0 license.</p><p>That fork became OpenSearch.</p><p><strong>Practical implication</strong>: OpenSearch is API-compatible with Elasticsearch 7.10. If you have existing Elasticsearch code, it probably works with OpenSearch with minimal changes.</p><div><hr></div><h3>The Two Components</h3><p>OpenSearch actually has two main parts:</p><p><strong>1. OpenSearch (the engine)</strong></p><ul><li><p>Stores and indexes your data</p></li><li><p>Processes search queries</p></li><li><p>Runs aggregations and analytics</p></li><li><p>Exposes a REST API (default port: 9200)</p></li></ul><p><strong>2. OpenSearch Dashboards</strong></p><ul><li><p>Web interface for visualization</p></li><li><p>Build dashboards and charts</p></li><li><p>Dev Tools console for running queries</p></li><li><p>Alerting and monitoring</p></li></ul><p>Think of it like PostgreSQL (the database) and pgAdmin (the GUI). You can use the engine without the dashboards, but the dashboards make it much easier to explore your data.</p><div><hr></div><h2>When to Use OpenSearch</h2><p>OpenSearch shines in four main scenarios:</p><h3>1. Full-Text Search</h3><p><strong>The problem</strong>: Users need to find content fast&#8212;products, articles, documentation, people.</p><p><strong>Why OpenSearch</strong>: Instant results, relevance ranking, typo tolerance, autocomplete, faceted search.</p><p><strong>Examples</strong>: E-commerce product search, internal documentation search, Wikipedia-style search boxes.</p><div><hr></div><h3>2. Log Analytics</h3><p><strong>The problem</strong>: You have millions of log lines from servers, applications, and services. Finding a specific error is like finding a needle in a haystack.</p><p><strong>Why OpenSearch</strong>: Ingest logs in real-time, search across all sources instantly, visualize patterns and anomalies.</p><p><strong>Examples</strong>: Debugging errors across 100 servers, tracking API latencies, security log analysis.</p><div><hr></div><h3>3. Observability &amp; Metrics</h3><p><strong>The problem</strong>: You need visibility into system health&#8212;CPU usage, request counts, error rates&#8212;across your entire infrastructure.</p><p><strong>Why OpenSearch</strong>: Time-series aggregations, real-time dashboards, alerting on thresholds.</p><p><strong>Examples</strong>: Infrastructure monitoring, APM dashboards, SLA tracking.</p><div><hr></div><h3>4. AI &amp; Vector Search</h3><p><strong>The problem</strong>: Traditional keyword search doesn&#8217;t understand meaning. &#8220;automobile&#8221; and &#8220;car&#8221; are treated as different concepts.</p><p><strong>Why OpenSearch</strong>: Native support for vector embeddings, k-NN (nearest neighbor) search, hybrid search combining keywords and vectors.</p><p><strong>Examples</strong>: Semantic search, RAG (Retrieval-Augmented Generation) pipelines, image similarity, recommendation systems.</p><div><hr></div><h2>When NOT to Use OpenSearch</h2><p>This is the part most tutorials skip. But understanding when <strong>not</strong> to use a tool is just as important as knowing when to use it.</p><h3>&#10060; Don&#8217;t Use It As Your Primary Database</h3><p><strong>Why not</strong>: OpenSearch is not ACID-compliant. It prioritizes speed over data consistency guarantees.</p><p><strong>The problem</strong>: If a node fails mid-write, you might lose data or end up with inconsistent state. There&#8217;s no rollback mechanism like a traditional database transaction.</p><p><strong>What to do instead</strong>: Use PostgreSQL, MySQL, or another relational database as your source of truth. Sync data to OpenSearch for search capabilities. This is called the &#8220;dual-write&#8221; or &#8220;CQRS&#8221; pattern.</p><div><hr></div><h3>&#10060; Don&#8217;t Use It for Frequently Updated Records</h3><p><strong>Why not</strong>: Documents in OpenSearch are immutable. When you &#8220;update&#8221; a document, OpenSearch actually deletes the old version and reindexes a completely new one.</p><p><strong>The problem</strong>: If you&#8217;re updating a user&#8217;s last-login timestamp on every request, or incrementing a view counter frequently, you&#8217;re triggering delete+reindex operations constantly. This is very expensive.</p><p><strong>What to do instead</strong>: Keep frequently-changing data in a database optimized for updates. Only sync relatively stable data to OpenSearch.</p><div><hr></div><h3>&#10060; Don&#8217;t Use It for Multi-Document Transactions</h3><p><strong>Why not</strong>: OpenSearch has no concept of transactions spanning multiple documents. Each document write is independent.</p><p><strong>The problem</strong>: &#8220;Transfer $100 from Account A to Account B&#8221; requires two operations: debit A, credit B. In a database, this is one atomic transaction. In OpenSearch, A might be debited but B might fail to credit. Now your data is inconsistent.</p><p><strong>What to do instead</strong>: Handle any operation requiring transactional guarantees in your database first, then update OpenSearch.</p><div><hr></div><h3>The Right Mental Model</h3><p>Think of it this way:</p><blockquote><p><strong>Database</strong> = Source of truth, handles transactions, your data&#8217;s home</p><p><strong>OpenSearch</strong> = Specialized search layer that syncs from your database</p></blockquote><p>They work together. OpenSearch doesn&#8217;t replace your database; it complements it.</p><div><hr></div><h2>The One Concept That Makes Everything Click</h2><p>If you remember nothing else from this article, remember this:</p><p><strong>OpenSearch builds an index at write time so it can search at O(1) at read time.</strong></p><p>That&#8217;s the fundamental insight.</p><p>Every other concept&#8212;shards, replicas, analyzers, mappings&#8212;they all exist to make that indexing process more efficient, more distributed, or more customizable.</p><p>Once you understand that core trade-off, everything else starts to make sense:</p><ul><li><p><em>Why does OpenSearch need so much RAM?</em> To keep indexes in memory for fast lookup.</p></li><li><p><em>What&#8217;s a shard?</em> A way to split a large index across multiple machines.</p></li><li><p><em>What&#8217;s an analyzer?</em> A way to customize how text is broken into tokens for the index.</p></li><li><p><em>Why is updating slow?</em> Because it&#8217;s rebuilding the index, not just changing a value.</p></li></ul><div><hr></div><h2>Try It Yourself</h2><p>Want to see this in action? Here&#8217;s the fastest way to spin up OpenSearch locally:</p><pre><code><code>docker run -p 9200:9200 -p 9600:9600 \
  -e "discovery.type=single-node" \
  -e "OPENSEARCH_INITIAL_ADMIN_PASSWORD=MyPassword123!" \
  opensearchproject/opensearch:latest
</code></code></pre><p>Wait about 30 seconds, then visit https://localhost:9200 (accept the self-signed certificate warning). You should see JSON confirming OpenSearch is running.</p><p>Now you have a search engine to experiment with.</p><div><hr></div><h2>What&#8217;s Next</h2><p>This was the &#8220;why&#8221; and &#8220;what&#8221; of OpenSearch. In the next article, I&#8217;ll go deeper into the &#8220;how&#8221;:</p><ul><li><p>How text analysis actually works (tokenization, stemming, filters)</p></li><li><p>How relevance scoring determines which results come first</p></li><li><p>How to write your first search queries</p></li></ul><p>If you found this helpful, subscribe so you don&#8217;t miss it.</p><p>And if you have questions, drop them in the comments&#8212;I read everything.</p><div><hr></div><p><strong>Resources:</strong></p><ul><li><p><strong>Interactive Visual Guide for Day 1:</strong> <a href="https://opensearch.9cld.com/day/01-fundamentals">opensearch.9cld.com/day/01-fundamentals</a></p></li><li><p><strong>Complete Guide (all days):</strong> <a href="https://opensearch.9cld.com/">opensearch.9cld.com</a></p></li></ul><div><hr></div><p><em>This is part of my series breaking down OpenSearch from first principles. No assumed knowledge. No skipped steps. Just clear explanations of how search actually works.</em></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://9cld.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">9Cloud is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item></channel></rss>